diff --git a/.gitignore b/.gitignore index 97adfa9..c2065bc 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,3 @@ out/ ### VS Code ### .vscode/ - -/src/main/resources/properties/env.properties -/src/main/java/com/example/feeda/config/PropertyConfig.java diff --git a/README.md b/README.md index 7814902..283b096 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,2 @@ -# 피다(Feeda) -개발자, 학생, 그리고 자기 계발에 관심 있는 모두가 모여 서로의 스터디 정보와 노하우를 나누는 커뮤니티 SNS입니다. - -- 스터디 모집 및 경험 공유 -- 질문과 답변을 통한 지식 나눔 -- 성장과 동기부여를 위한 소통 공간 -- 함께 배우고, 함께 성장하는 즐거움을 경험 시켜 드릴수가 있을 것 같습니다. - -
- -## 👨‍💻 Team -- 팀명: 6pring (식스프링) -- 소개: 4명의 아기자기한 팀 -- 팀원 및 역할 분담 - -| 이름 | 역할 | 주요 담당 업무 | -|-----|----|------------------------------------------------------------------------------------------| -| 최경진 | 팀장 | - 발표 ✨
- 프로필 관련 API 개발
- 게시글 댓글 관련 API 개발 | -| 김나경 | 팀원 | - ERD 작성 및 DB 설계
- JWT 인증/인가 관련 기능 구현
- 회원 관리 관련 API 개발
- 게시글 댓글 좋아요 관련 API 개발 | -| 안요한 | 팀원 | - 와이어 프레임 작성
- 게시글 관련 API 개발
- 게시글 좋아요 관련 API 개발 | -| 이의현 | 팀원 | - API 명세서 작성
- 팔로우(친구 관리) 관련 API 개발
- 전역 예외 처리 핸들러 개발
- 테스트 코드 작성 | - - -
- -## 🛠 사용 기술 -- Java 17 -- Gradle 8.5 -- Spring Boot 3.5.0 -- Spring Data JPA (Hibernate 6.6.13.Final) -- Spring Security -- MySQL 8.0 이상 -- Redis Cloud - -
- -## 💻 개발 도구 -- IntelliJ IDEA -- Redis Insight -- Git -- Postman - -
- -## 📃 프로젝트 설계 -
-API 명세서 - -Postman: [document](https://documenter.getpostman.com/view/44635744/2sB2qgeyJ7) - -Notion -- [필수기능 명세서](https://www.notion.so/2002dc3ef5148050b741cdfba818f530?pvs=21) -- [도전기능 명세서](https://www.notion.so/2022dc3ef51481939541e86c62aa7864?pvs=21) -
- -
-와이어 프레임 - -![와이어프레임](./images/wireframe.png) -
- -
-DB 설계 - -- 개념적 설계: - - ![er](./images/er.png) -- 논리적 설계(ERD): - - ![erd](./images/erd.png) -- 물리적 설계(SQL): - - [newsFeed2.sql](./newsFeed2.sql) - - ![erdE](./images/erdE.png) -
- -
- -## 📁 폴더 구조 -```bash -src -├──── main.java.com.example.feeda -│ ├──── config # 설정 관련 -│ ├──── domain # 도메인별 기능 분류 -│ │ ├──── account -│ │ ├──── comment -│ │ ├──── follow -│ │ ├──── post -│ │ └──── profile -│ ├──── exception # 예외 클래스 및 처리 -│ ├──── filter # 인증 필터 -│ ├──── security # 보안 관련 (PasswordEncoder, JWT) -│ └──── FeedaApplication.java -└──── test # 테스트 코드 -``` - -
- -## 🔍 새로운 지식 - - -## 🧰 문제 해결 (트러블 슈팅) - - - +# spring-feeda +모두가 모여 서로의 스터디 정보와 노하우를 나누는 커뮤니티 SNS입니다. diff --git a/build.gradle b/build.gradle index 6a3fa01..d630d05 100644 --- a/build.gradle +++ b/build.gradle @@ -1,50 +1,40 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.5.0' - id 'io.spring.dependency-management' version '1.1.7' + id 'java' + id 'org.springframework.boot' version '3.5.0' + id 'io.spring.dependency-management' version '1.1.7' } group = 'com.example' version = '0.0.1-SNAPSHOT' java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'mysql:mysql-connector-java:8.0.33' // 추가 - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.security:spring-security-test' - runtimeOnly 'com.mysql:mysql-connector-j' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - - // jwt - compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' - runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' - runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' - - testImplementation '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-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/images/er.png b/images/er.png deleted file mode 100644 index 816fc2b..0000000 Binary files a/images/er.png and /dev/null differ diff --git a/images/erd.png b/images/erd.png deleted file mode 100644 index cf35035..0000000 Binary files a/images/erd.png and /dev/null differ diff --git a/images/erdE.png b/images/erdE.png deleted file mode 100644 index d2c6fb4..0000000 Binary files a/images/erdE.png and /dev/null differ diff --git a/images/wireframe.png b/images/wireframe.png deleted file mode 100644 index c8ec456..0000000 Binary files a/images/wireframe.png and /dev/null differ diff --git a/newsFeed.sql b/newsFeed.sql deleted file mode 100644 index c0cd7ba..0000000 --- a/newsFeed.sql +++ /dev/null @@ -1,49 +0,0 @@ -DROP DATABASE IF EXISTS newsFeed; -CREATE DATABASE IF NOT EXISTS newsFeed; -USE newsFeed; - --- 사용자 인증(accounts) 테이블 생성 -CREATE TABLE accounts ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '사용자 인증 ID (PK)', - email VARCHAR(100) NOT NULL UNIQUE COMMENT '이메일', - password VARCHAR(255) NOT NULL COMMENT '비밀번호', - created_at DATETIME COMMENT '생성일', - updated_at DATETIME COMMENT '수정일' -) COMMENT = '사용자 인증 Table'; - --- 사용자 정보(profile) 테이블 생성 -CREATE TABLE profiles ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '사용자 정보 ID (PK)', - account_id BIGINT COMMENT '사용자 인증 ID (FK)', - nickname VARCHAR(50) NOT NULL UNIQUE COMMENT '닉네임', - birth DATE COMMENT '생년월일', - bio TEXT COMMENT '자기소개', - created_at DATETIME COMMENT '생성일', - updated_at DATETIME COMMENT '수정일', - - FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE -) COMMENT = '사용자 정보 Table'; - --- 게시글(posts) 테이블 생성 -CREATE TABLE posts ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '게시글 ID (PK)', - profile_id BIGINT NOT NULL COMMENT '작성자 ID (FK)', - title VARCHAR(100) NOT NULL COMMENT '제목', - content TEXT NOT NULL COMMENT '내용', - category VARCHAR(50) COMMENT '카테고리', - created_at DATETIME COMMENT '생성일', - updated_at DATETIME COMMENT '수정일', - - FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE -) COMMENT = '게시글 Table'; - --- 팔로우 목록(follows) 테이블 생성 -CREATE TABLE follows ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '팔로우 ID (PK)', - follower_id BIGINT NOT NULL COMMENT '팔로우한 사람 ID (PK)', - following_id BIGINT NOT NULL COMMENT '팔로잉된 사람 ID (FK)', - created_at DATETIME COMMENT '생성일', - - FOREIGN KEY (follower_id) REFERENCES profiles(id) ON DELETE CASCADE, - FOREIGN KEY (following_id) REFERENCES profiles(id) ON DELETE CASCADE -) COMMENT = '팔로우 목록 Table' \ No newline at end of file diff --git a/newsFeed2.sql b/newsFeed2.sql deleted file mode 100644 index e5baf2a..0000000 --- a/newsFeed2.sql +++ /dev/null @@ -1,84 +0,0 @@ -DROP DATABASE IF EXISTS newsFeed; -CREATE DATABASE IF NOT EXISTS newsFeed; -USE newsFeed; - --- 사용자 인증(accounts) 테이블 생성 -CREATE TABLE accounts ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '사용자 인증 ID (PK)', - email VARCHAR(100) NOT NULL UNIQUE COMMENT '이메일', - password VARCHAR(255) NOT NULL COMMENT '비밀번호', - created_at DATETIME COMMENT '생성일', - updated_at DATETIME COMMENT '수정일' -) COMMENT = '사용자 인증 Table'; - --- 사용자 정보(profile) 테이블 생성 -CREATE TABLE profiles ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '사용자 정보 ID (PK)', - account_id BIGINT COMMENT '사용자 인증 ID (FK)', - nickname VARCHAR(50) NOT NULL UNIQUE COMMENT '닉네임', - birth DATE COMMENT '생년월일', - bio TEXT COMMENT '자기소개', - created_at DATETIME COMMENT '생성일', - updated_at DATETIME COMMENT '수정일', - - FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE -) COMMENT = '사용자 정보 Table'; - --- 팔로우 목록(follows) 테이블 생성 -CREATE TABLE follows ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '팔로우 ID (PK)', - follower_id BIGINT NOT NULL COMMENT '팔로우한 사람 ID (PK)', - following_id BIGINT NOT NULL COMMENT '팔로잉된 사람 ID (FK)', - created_at DATETIME COMMENT '생성일', - - FOREIGN KEY (follower_id) REFERENCES profiles(id) ON DELETE CASCADE, - FOREIGN KEY (following_id) REFERENCES profiles(id) ON DELETE CASCADE -) COMMENT = '팔로우 목록 Table'; - --- 게시글(posts) 테이블 생성 -CREATE TABLE posts ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '게시글 ID (PK)', - profile_id BIGINT NOT NULL COMMENT '작성자 ID (FK)', - title VARCHAR(100) NOT NULL COMMENT '제목', - content TEXT NOT NULL COMMENT '내용', - category VARCHAR(50) COMMENT '카테고리', - created_at DATETIME COMMENT '생성일', - updated_at DATETIME COMMENT '수정일', - - FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE -) COMMENT = '게시글 Table'; - --- 게시글 댓글(post_comments) 테이블 생성 -CREATE TABLE post_comments ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '댓글 ID (PK)', - post_id BIGINT NOT NULL COMMENT '게시글 ID (FK)', - profile_id BIGINT NOT NULL COMMENT '작성자 ID (FK)', - content TEXT NOT NULL COMMENT '내용', - created_at DATETIME COMMENT '생성일', - updated_at DATETIME COMMENT '수정일', - - FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, - FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE -) COMMENT = '게시글 댓글 Table'; - --- 게시글 좋아요(post_likes) 테이블 생성 -CREATE TABLE post_likes ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '댓글 ID (PK)', - post_id BIGINT NOT NULL COMMENT '게시글 ID (FK)', - profile_id BIGINT NOT NULL COMMENT '사용자 ID (FK)', - created_at DATETIME COMMENT '생성일', - - FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, - FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE -) COMMENT = '게시글 좋아요 Table'; - --- 게시글 댓글 좋아요(post_comment_likes) 테이블 생성 -CREATE TABLE post_comment_likes ( - id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '댓글 ID (PK)', - post_comment_id BIGINT NOT NULL COMMENT '게시글 ID (FK)', - profile_id BIGINT NOT NULL COMMENT '사용자 ID (FK)', - created_at DATETIME COMMENT '생성일', - - FOREIGN KEY (post_comment_id) REFERENCES post_comments(id) ON DELETE CASCADE, - FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE -) COMMENT = '게시글 댓글 좋아요 Table'; \ No newline at end of file diff --git a/src/main/java/com/example/feeda/config/.gitkeep b/src/main/java/com/example/feeda/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/example/feeda/config/RedisConfig.java b/src/main/java/com/example/feeda/config/RedisConfig.java deleted file mode 100644 index 65c3c4e..0000000 --- a/src/main/java/com/example/feeda/config/RedisConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.example.feeda.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; - - -@Configuration -@EnableRedisRepositories -public class RedisConfig { - - @Value("${spring.data.redis.host}") - private String host; - - @Value("${spring.data.redis.port}") - private int port; - - @Value("${spring.data.redis.password:}") - private String password; - - @Bean - public RedisConnectionFactory redisConnectionFactory() { - RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(); - config.setHostName(host); - config.setPort(port); - config.setPassword(password); - - return new LettuceConnectionFactory(config); - } -} diff --git a/src/main/java/com/example/feeda/config/SecurityConfig.java b/src/main/java/com/example/feeda/config/SecurityConfig.java deleted file mode 100644 index 80ac914..0000000 --- a/src/main/java/com/example/feeda/config/SecurityConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.example.feeda.config; - -import com.example.feeda.filter.JwtFilter; -import com.example.feeda.security.handler.CustomAccessDeniedHandler; -import com.example.feeda.security.handler.CustomAuthenticationEntryPoint; -import com.example.feeda.security.jwt.JwtBlacklistService; -import com.example.feeda.security.jwt.JwtUtil; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; -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.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - - -@Configuration -@EnableWebSecurity -@RequiredArgsConstructor -public class SecurityConfig { - private final JwtBlacklistService jwtBlacklistService; - private final JwtUtil jwtUtil; - private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; - private final CustomAccessDeniedHandler customAccessDeniedHandler; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { - - return httpSecurity - .csrf(AbstractHttpConfigurer::disable) - .cors(Customizer.withDefaults()) - .httpBasic(AbstractHttpConfigurer::disable) - .formLogin(AbstractHttpConfigurer::disable) - - // 세션 비활성화 - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - - // 접근 제어 - .authorizeHttpRequests(auth -> auth - // 회원가입, 로그인은 인증 제외 - .requestMatchers("/api/accounts", "/api/accounts/login").permitAll() - - .requestMatchers("/error").permitAll() - .requestMatchers("/api/**").authenticated() - - .anyRequest().denyAll() - ) - - // 필터 등록 - .addFilterBefore(new JwtFilter(jwtBlacklistService, jwtUtil), UsernamePasswordAuthenticationFilter.class) - - .exceptionHandling(configurer -> - configurer - .authenticationEntryPoint(customAuthenticationEntryPoint) - .accessDeniedHandler(customAccessDeniedHandler) - ) - - .build(); - } -} diff --git a/src/main/java/com/example/feeda/domain/account/controller/AccountController.java b/src/main/java/com/example/feeda/domain/account/controller/AccountController.java deleted file mode 100644 index 2773b65..0000000 --- a/src/main/java/com/example/feeda/domain/account/controller/AccountController.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.example.feeda.domain.account.controller; - -import com.example.feeda.domain.account.sevice.AccountServiceImpl; -import com.example.feeda.domain.account.dto.*; -import com.example.feeda.security.jwt.JwtBlacklistService; -import com.example.feeda.security.jwt.JwtPayload; -import com.example.feeda.security.jwt.JwtUtil; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api") -@RequiredArgsConstructor -public class AccountController { - private final AccountServiceImpl accountService; - private final JwtBlacklistService jwtBlacklistService; - private final JwtUtil jwtUtil; - - @PostMapping("/accounts") - public ResponseEntity signup(@RequestBody @Valid SignUpRequestDTO requestDTO) { - return new ResponseEntity<>(accountService.signup(requestDTO), HttpStatus.CREATED); - } - - @DeleteMapping("/accounts/me") - public ResponseEntity deleteAccount( - @RequestHeader("Authorization") String bearerToken, - @AuthenticationPrincipal JwtPayload jwtPayload, - @RequestBody @Valid DeleteAccountRequestDTO requestDTO - ) { - accountService.deleteAccount(jwtPayload.getAccountId(), requestDTO.getPassword()); - - // 토큰 무효화 - invalidateToken(bearerToken); - - return new ResponseEntity<>(HttpStatus.NO_CONTENT); - } - - @PatchMapping("/accounts/password") - public ResponseEntity updatePassword( - @AuthenticationPrincipal JwtPayload jwtPayload, - @RequestBody @Valid UpdatePasswordRequestDTO requestDTO - ) { - return new ResponseEntity<>(accountService.updatePassword(jwtPayload.getAccountId(), requestDTO), HttpStatus.OK); - } - - @PostMapping("/accounts/login") - public ResponseEntity login( - @RequestBody @Valid LogInRequestDTO requestDTO - ) { - UserResponseDTO responseDTO = accountService.login(requestDTO); - - JwtPayload payload = new JwtPayload( - responseDTO.getAccountId(), - responseDTO.getProfileId(), - responseDTO.getEmail(), - responseDTO.getNickName() - ); - - String jwt = jwtUtil.createToken(payload); - - HttpHeaders headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + jwt); - - return new ResponseEntity<>(responseDTO, headers, HttpStatus.OK); - } - - @PostMapping("/accounts/logout") - public ResponseEntity logout(@RequestHeader("Authorization") String bearerToken) { - // 토큰 무효화 - invalidateToken(bearerToken); - - return new ResponseEntity<>(HttpStatus.NO_CONTENT); - } - - - private void invalidateToken(String bearerToken) { - String token = jwtUtil.extractToken(bearerToken); - jwtBlacklistService.addBlacklist(token); - } -} diff --git a/src/main/java/com/example/feeda/domain/account/dto/UserResponseDTO.java b/src/main/java/com/example/feeda/domain/account/dto/UserResponseDTO.java deleted file mode 100644 index 70f23a6..0000000 --- a/src/main/java/com/example/feeda/domain/account/dto/UserResponseDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.feeda.domain.account.dto; - -import com.example.feeda.domain.account.entity.Account; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -@AllArgsConstructor -public class UserResponseDTO { - private final Long accountId; - private final Long profileId; - private final String email; - private String nickName; - private final LocalDateTime createdAt; - private final LocalDateTime updatedAt; - - public UserResponseDTO(Account account) { - this.accountId = account.getId(); - this.profileId = account.getProfile().getId(); - this.email = account.getEmail(); - this.nickName = account.getProfile().getNickname(); - this.createdAt = account.getCreatedAt(); - this.updatedAt = account.getUpdatedAt(); - } -} diff --git a/src/main/java/com/example/feeda/domain/account/entity/Account.java b/src/main/java/com/example/feeda/domain/account/entity/Account.java deleted file mode 100644 index 725e2eb..0000000 --- a/src/main/java/com/example/feeda/domain/account/entity/Account.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.example.feeda.domain.account.entity; - -import com.example.feeda.domain.profile.entity.Profile; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import java.time.LocalDateTime; - -@Entity -@Table(name = "accounts") -@Getter -@AllArgsConstructor -@EntityListeners(AuditingEntityListener.class) -public class Account { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false, unique = true) - private String email; - - @Setter - private String password; - - @CreatedDate - @Column(updatable = false, name = "created_at") - private LocalDateTime createdAt; - - @LastModifiedDate - @Column(name = "updated_at") - private LocalDateTime updatedAt; - - @Setter - @OneToOne(mappedBy = "account", cascade = CascadeType.ALL, optional = false) - private Profile profile; - - public Account() { - // @Entity: no-arg 생성자가 포함되어야 함 - } - - public Account(String email, String password) { - this.email = email; - this.password = password; - } -} diff --git a/src/main/java/com/example/feeda/domain/account/repository/AccountRepository.java b/src/main/java/com/example/feeda/domain/account/repository/AccountRepository.java deleted file mode 100644 index 8e3cfb2..0000000 --- a/src/main/java/com/example/feeda/domain/account/repository/AccountRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.feeda.domain.account.repository; - -import com.example.feeda.domain.account.entity.Account; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface AccountRepository extends JpaRepository { - Optional findByEmail(String email); -} diff --git a/src/main/java/com/example/feeda/domain/account/sevice/AccountService.java b/src/main/java/com/example/feeda/domain/account/sevice/AccountService.java deleted file mode 100644 index 9c18c4a..0000000 --- a/src/main/java/com/example/feeda/domain/account/sevice/AccountService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.feeda.domain.account.sevice; - -import com.example.feeda.domain.account.dto.LogInRequestDTO; -import com.example.feeda.domain.account.dto.SignUpRequestDTO; -import com.example.feeda.domain.account.dto.UpdatePasswordRequestDTO; -import com.example.feeda.domain.account.dto.UserResponseDTO; - -public interface AccountService { - UserResponseDTO signup(SignUpRequestDTO requestDTO); - - void deleteAccount(Long id, String password); - - UserResponseDTO updatePassword(Long id, UpdatePasswordRequestDTO requestDTO); - - UserResponseDTO login(LogInRequestDTO requestDTO); -} diff --git a/src/main/java/com/example/feeda/domain/account/sevice/AccountServiceImpl.java b/src/main/java/com/example/feeda/domain/account/sevice/AccountServiceImpl.java deleted file mode 100644 index fb2c7aa..0000000 --- a/src/main/java/com/example/feeda/domain/account/sevice/AccountServiceImpl.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.example.feeda.domain.account.sevice; - -import com.example.feeda.domain.account.dto.LogInRequestDTO; -import com.example.feeda.domain.account.dto.UpdatePasswordRequestDTO; -import com.example.feeda.domain.account.dto.UserResponseDTO; -import com.example.feeda.domain.account.dto.SignUpRequestDTO; -import com.example.feeda.domain.account.entity.Account; -import com.example.feeda.domain.account.repository.AccountRepository; -import com.example.feeda.domain.profile.entity.Profile; -import com.example.feeda.domain.profile.repository.ProfileRepository; -import com.example.feeda.exception.CustomResponseException; -import com.example.feeda.exception.enums.ResponseError; -import com.example.feeda.security.PasswordEncoder; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - - -@Service -@RequiredArgsConstructor -public class AccountServiceImpl implements AccountService { - private final AccountRepository accountRepository; - private final PasswordEncoder passwordEncoder; - private final ProfileRepository profileRepository; - - @Override - @Transactional - public UserResponseDTO signup(SignUpRequestDTO requestDTO) { - if(accountRepository.findByEmail(requestDTO.getEmail()).isPresent()) { - throw new CustomResponseException(ResponseError.EMAIL_ALREADY_EXISTS); - } - - if(profileRepository.findByNickname(requestDTO.getNickName()).isPresent()) { - throw new CustomResponseException(ResponseError.NICKNAME_ALREADY_EXISTS); - } - - Account account = new Account(requestDTO.getEmail(), requestDTO.getPassword()); - account.setPassword(passwordEncoder.encode(account.getPassword())); - - Profile profile = new Profile(requestDTO.getNickName(), requestDTO.getBirth(), requestDTO.getBio()); - - // 양방향 연결 - account.setProfile(profile); - profile.setAccount(account); - - Account saveProfile = accountRepository.save(account); - - return new UserResponseDTO(saveProfile); - } - - @Override - @Transactional - public void deleteAccount(Long id, String password) { - Account account = getAccountById(id); - - if(!passwordEncoder.matches(password, account.getPassword())) { - throw new CustomResponseException(ResponseError.INVALID_PASSWORD); - } - - accountRepository.delete(account); - } - - @Override - @Transactional - public UserResponseDTO updatePassword(Long id, UpdatePasswordRequestDTO requestDTO) { - Account account = getAccountById(id); - - if(!passwordEncoder.matches(requestDTO.getOldPassword(), account.getPassword())) { - throw new CustomResponseException(ResponseError.INVALID_PASSWORD); - } - - account.setPassword(passwordEncoder.encode(requestDTO.getNewPassword())); - - // DB 에 변경 사항 강제 반영 - accountRepository.flush(); - - return new UserResponseDTO(account); - } - - @Override - public UserResponseDTO login(LogInRequestDTO requestDTO) { - return new UserResponseDTO(accountRepository.findByEmail(requestDTO.getEmail()) - .filter(findAccount -> passwordEncoder.matches(requestDTO.getPassword(), findAccount.getPassword())) - .orElseThrow(() -> new CustomResponseException(ResponseError.INVALID_EMAIL_OR_PASSWORD)) - ); - } - - - /* 유틸(?): 서비스 내에서만 사용 */ - - public Account getAccountById(Long id) { - return accountRepository.findById(id).orElseThrow(() -> - new CustomResponseException(ResponseError.ACCOUNT_NOT_FOUND) - ); - } -} diff --git a/src/main/java/com/example/feeda/domain/comment/controller/CommentController.java b/src/main/java/com/example/feeda/domain/comment/controller/CommentController.java deleted file mode 100644 index b00ac8f..0000000 --- a/src/main/java/com/example/feeda/domain/comment/controller/CommentController.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.example.feeda.domain.comment.controller; - -import com.example.feeda.domain.comment.dto.CommentResponse; -import com.example.feeda.domain.comment.dto.CreateCommentRequest; -import com.example.feeda.domain.comment.dto.UpdateCommentRequest; -import com.example.feeda.domain.comment.service.CommentService; -import com.example.feeda.domain.comment.dto.LikeCommentResponseDTO; -import com.example.feeda.domain.comment.service.CommentLikeService; -import com.example.feeda.security.jwt.JwtPayload; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api") -@RequiredArgsConstructor -@Validated -public class CommentController { - - private final CommentLikeService commentLikeService; - private final CommentService commentService; - - // 댓글 작성 - @PostMapping("/comments/post/{postId}") - public ResponseEntity createComment( - @PathVariable Long postId, - @AuthenticationPrincipal JwtPayload jwtPayload, - @RequestBody @Valid CreateCommentRequest request - ) { - Long profileId = jwtPayload.getProfileId(); - CommentResponse response = commentService.createComment(postId, profileId, request); - return ResponseEntity.ok(response); - } - - // 댓글 전체 조회 (게시글 기준, 정렬/필터 가능) - @GetMapping("/comments/post/{postId}") - public ResponseEntity> getCommentsByPostId( - @PathVariable Long postId, - @RequestParam(defaultValue = "latest") String sort // latest 또는 oldest - ) { - List comments = commentService.getCommentsByPostId(postId, sort); - return ResponseEntity.ok(comments); - } - - // 댓글 단건 조회 - @GetMapping("/comments/{commentId}") - public ResponseEntity getCommentById(@PathVariable Long commentId) { - CommentResponse response = commentService.getCommentById(commentId); - return ResponseEntity.ok(response); - } - - - // 댓글 수정 - @PutMapping("/comments/{commentId}") - public ResponseEntity updateComment( - @PathVariable Long commentId, - @AuthenticationPrincipal JwtPayload jwtPayload, - @Valid @RequestBody UpdateCommentRequest request - ) { - Long profileId = jwtPayload.getProfileId(); - CommentResponse response = commentService.updateComment(commentId, profileId, request); - return ResponseEntity.ok(response); - } - - // 댓글 삭제 - @DeleteMapping("/comments/{commentId}") - public ResponseEntity deleteComment( - @PathVariable Long commentId, - @AuthenticationPrincipal JwtPayload jwtPayload - ) { - Long profileId = jwtPayload.getProfileId(); - commentService.deleteComment(commentId, profileId); - return ResponseEntity.noContent().build(); - } - - // 댓글 좋아요 - @PostMapping("/comments/{id}/likes") - public ResponseEntity likeComment( - @PathVariable Long id, - @AuthenticationPrincipal JwtPayload jwtPayload - ) { - Long profileId = jwtPayload.getProfileId(); - return new ResponseEntity<>( - commentLikeService.likeComment(id, profileId), - HttpStatus.OK - ); - } - - // 댓글 좋아요 취소 - @DeleteMapping("/comments/{id}/likes") - public ResponseEntity unlikeComment( - @PathVariable Long id, - @AuthenticationPrincipal JwtPayload jwtPayload - ) { - commentLikeService.unlikeComment(id, jwtPayload.getProfileId()); - return new ResponseEntity<>(HttpStatus.NO_CONTENT); - } -} diff --git a/src/main/java/com/example/feeda/domain/comment/dto/CommentResponse.java b/src/main/java/com/example/feeda/domain/comment/dto/CommentResponse.java deleted file mode 100644 index ae28b6c..0000000 --- a/src/main/java/com/example/feeda/domain/comment/dto/CommentResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.feeda.domain.comment.dto; - -import com.example.feeda.domain.comment.entity.Comment; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -@AllArgsConstructor(staticName = "of") -public class CommentResponse { - private final Long id; - private final String content; - private final String profileName; - private final LocalDateTime createdAt; - - public static CommentResponse from(Comment comment) { - return CommentResponse.of( - comment.getId(), - comment.getContent(), - comment.getProfile().getNickname(), - comment.getCreatedAt() - ); - } -} diff --git a/src/main/java/com/example/feeda/domain/comment/dto/CreateCommentRequest.java b/src/main/java/com/example/feeda/domain/comment/dto/CreateCommentRequest.java deleted file mode 100644 index f41596e..0000000 --- a/src/main/java/com/example/feeda/domain/comment/dto/CreateCommentRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.feeda.domain.comment.dto; - -import jakarta.validation.constraints.NotBlank; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor() -public class CreateCommentRequest { - @NotBlank(message = "내용을 입력해주세요.") - private String content; -} diff --git a/src/main/java/com/example/feeda/domain/comment/dto/LikeCommentResponseDTO.java b/src/main/java/com/example/feeda/domain/comment/dto/LikeCommentResponseDTO.java deleted file mode 100644 index 48a1535..0000000 --- a/src/main/java/com/example/feeda/domain/comment/dto/LikeCommentResponseDTO.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.feeda.domain.comment.dto; - -import com.example.feeda.domain.comment.entity.CommentLike; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -@AllArgsConstructor -public class LikeCommentResponseDTO { - private final Long id; - private final Long commentId; - private final Long profileId; - private final LocalDateTime createdAt; - - public LikeCommentResponseDTO(CommentLike commentLike) { - this.id = commentLike.getId(); - this.commentId = commentLike.getComment().getId(); - this.profileId = commentLike.getProfile().getId(); - this.createdAt = commentLike.getCreatedAt(); - } -} diff --git a/src/main/java/com/example/feeda/domain/comment/dto/UpdateCommentRequest.java b/src/main/java/com/example/feeda/domain/comment/dto/UpdateCommentRequest.java deleted file mode 100644 index 0379e0c..0000000 --- a/src/main/java/com/example/feeda/domain/comment/dto/UpdateCommentRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.feeda.domain.comment.dto; - -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class UpdateCommentRequest { - @NotBlank(message = "내용을 입력해주세요.") - private String content; -} diff --git a/src/main/java/com/example/feeda/domain/comment/entity/Comment.java b/src/main/java/com/example/feeda/domain/comment/entity/Comment.java deleted file mode 100644 index 4f2d2e2..0000000 --- a/src/main/java/com/example/feeda/domain/comment/entity/Comment.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.example.feeda.domain.comment.entity; - -import com.example.feeda.domain.post.entity.Post; -import com.example.feeda.domain.profile.entity.Profile; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import java.time.LocalDateTime; - -@Entity -@Table(name = "post_comments") -@Getter -@AllArgsConstructor -@EntityListeners(AuditingEntityListener.class) -@NoArgsConstructor -public class Comment { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "post_id", nullable = false) - private Post post; - - @ManyToOne - @JoinColumn(name = "profile_id", nullable = false) - private Profile profile; - - @Column(nullable = false) - private String content; - - @CreatedDate - @Column(updatable = false, name = "created_at") - private LocalDateTime createdAt; - - @LastModifiedDate - @Column(name = "updated_at") - private LocalDateTime updatedAt; - - public Comment(Post post, Profile profile, String content) { - this.post = post; - this.profile = profile; - this.content = content; - } - - public void updateContent(String content) { - this.content = content; - } -} diff --git a/src/main/java/com/example/feeda/domain/comment/entity/CommentLike.java b/src/main/java/com/example/feeda/domain/comment/entity/CommentLike.java deleted file mode 100644 index 6cfb13a..0000000 --- a/src/main/java/com/example/feeda/domain/comment/entity/CommentLike.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.example.feeda.domain.comment.entity; - -import com.example.feeda.domain.profile.entity.Profile; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import java.time.LocalDateTime; - -@Entity -@Table(name = "post_comment_likes") -@Getter -@AllArgsConstructor -@EntityListeners(AuditingEntityListener.class) -@NoArgsConstructor -public class CommentLike { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "post_comment_id", nullable = false) - private Comment comment; - - @ManyToOne - @JoinColumn(name = "profile_id", nullable = false) - private Profile profile; - - @CreatedDate - @Column(updatable = false, name = "created_at") - private LocalDateTime createdAt; - - public CommentLike(Comment comment, Profile profile) { - this.comment = comment; - this.profile = profile; - } -} diff --git a/src/main/java/com/example/feeda/domain/comment/repository/CommentLikeRepository.java b/src/main/java/com/example/feeda/domain/comment/repository/CommentLikeRepository.java deleted file mode 100644 index e8a85c5..0000000 --- a/src/main/java/com/example/feeda/domain/comment/repository/CommentLikeRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.feeda.domain.comment.repository; - -import com.example.feeda.domain.comment.entity.CommentLike; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface CommentLikeRepository extends JpaRepository { - Optional findByComment_IdAndProfile_Id(Long commentId, Long profileId); -} diff --git a/src/main/java/com/example/feeda/domain/comment/repository/CommentRepository.java b/src/main/java/com/example/feeda/domain/comment/repository/CommentRepository.java deleted file mode 100644 index 61835b4..0000000 --- a/src/main/java/com/example/feeda/domain/comment/repository/CommentRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.feeda.domain.comment.repository; - -import com.example.feeda.domain.comment.entity.Comment; -import com.example.feeda.domain.post.entity.Post; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CommentRepository extends JpaRepository { - - List findByPostIdOrderByCreatedAtDesc(Long postId); // 최신순 - - List findByPostIdOrderByCreatedAtAsc(Long postId); - - Long countByPost(Post findPost); -} diff --git a/src/main/java/com/example/feeda/domain/comment/service/CommentLikeService.java b/src/main/java/com/example/feeda/domain/comment/service/CommentLikeService.java deleted file mode 100644 index 0746e92..0000000 --- a/src/main/java/com/example/feeda/domain/comment/service/CommentLikeService.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.example.feeda.domain.comment.service; - -import com.example.feeda.domain.comment.dto.LikeCommentResponseDTO; -import com.example.feeda.domain.comment.entity.Comment; -import com.example.feeda.domain.comment.entity.CommentLike; -import com.example.feeda.domain.comment.repository.CommentLikeRepository; -import com.example.feeda.domain.comment.repository.CommentRepository; -import com.example.feeda.domain.profile.entity.Profile; -import com.example.feeda.domain.profile.repository.ProfileRepository; -import com.example.feeda.exception.CustomResponseException; -import com.example.feeda.exception.enums.ResponseError; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.Optional; - -@Service -@RequiredArgsConstructor -public class CommentLikeService { - private final CommentRepository commentRepository; - private final CommentLikeRepository commentLikeRepository; - private final ProfileRepository profileRepository; - - - public LikeCommentResponseDTO likeComment(Long commentId, Long profileId) { - Optional findCommentLike = commentLikeRepository.findByComment_IdAndProfile_Id(commentId, profileId); - if(findCommentLike.isPresent()) { - throw new CustomResponseException(ResponseError.ALREADY_LIKED_COMMENT); - } - - Comment findComment = commentRepository.findById(commentId).orElseThrow(() -> - new CustomResponseException(ResponseError.COMMENT_NOT_FOUND) - ); - - Profile findProfile = profileRepository.findById(profileId).orElseThrow(() -> - new CustomResponseException(ResponseError.PROFILE_NOT_FOUND) - ); - - CommentLike commentLike = new CommentLike(findComment, findProfile); - commentLikeRepository.save(commentLike); - - return new LikeCommentResponseDTO(commentLike); - } - - public void unlikeComment(Long commentId, Long profileId) { - Optional findCommentLikeOptional = commentLikeRepository.findByComment_IdAndProfile_Id(commentId, profileId); - if(findCommentLikeOptional.isEmpty()) { - throw new CustomResponseException(ResponseError.NOT_YET_LIKED_COMMENT); - } - - CommentLike commentLike = findCommentLikeOptional.get(); - - commentLikeRepository.delete(commentLike); - } -} diff --git a/src/main/java/com/example/feeda/domain/comment/service/CommentService.java b/src/main/java/com/example/feeda/domain/comment/service/CommentService.java deleted file mode 100644 index 765d8b5..0000000 --- a/src/main/java/com/example/feeda/domain/comment/service/CommentService.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.example.feeda.domain.comment.service; - -import com.example.feeda.domain.comment.dto.CommentResponse; -import com.example.feeda.domain.comment.dto.CreateCommentRequest; -import com.example.feeda.domain.comment.dto.UpdateCommentRequest; -import com.example.feeda.domain.comment.entity.Comment; -import com.example.feeda.domain.comment.repository.CommentRepository; -import com.example.feeda.domain.post.entity.Post; -import com.example.feeda.domain.post.repository.PostRepository; -import com.example.feeda.domain.profile.entity.Profile; -import com.example.feeda.domain.profile.repository.ProfileRepository; -import com.example.feeda.exception.CustomResponseException; -import com.example.feeda.exception.enums.ResponseError; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class CommentService { - - private final CommentRepository commentRepository; - private final PostRepository postRepository; - private final ProfileRepository profileRepository; - - @Transactional - public CommentResponse createComment(Long postId, Long profileId, CreateCommentRequest request) { - Post post = postRepository.findById(postId) - .orElseThrow(() -> new CustomResponseException(ResponseError.POST_NOT_FOUND)); - Profile profile = profileRepository.findById(profileId) - .orElseThrow(() -> new CustomResponseException(ResponseError.PROFILE_NOT_FOUND)); - - Comment comment = new Comment(post, profile, request.getContent()); - commentRepository.save(comment); - return CommentResponse.from(comment); - } - - public List getCommentsByPostId(Long postId, String sort) { - List comments; - - if (sort.equalsIgnoreCase("oldest")) { - comments = commentRepository.findByPostIdOrderByCreatedAtAsc(postId); - } else { - comments = commentRepository.findByPostIdOrderByCreatedAtDesc(postId); - } - - return comments.stream() - .map(CommentResponse::from) - .toList(); - } - - public CommentResponse getCommentById(Long commentId) { - Comment comment = commentRepository.findById(commentId) - .orElseThrow(() -> new CustomResponseException(ResponseError.COMMENT_NOT_FOUND)); - return CommentResponse.from(comment); - } - - @Transactional - public CommentResponse updateComment(Long commentId, Long requesterProfileId, UpdateCommentRequest request) { - Comment comment = commentRepository.findById(commentId) - .orElseThrow(() -> new CustomResponseException(ResponseError.COMMENT_NOT_FOUND)); - - if (!comment.getProfile().getId().equals(requesterProfileId)) { - throw new CustomResponseException(ResponseError.NO_PERMISSION_TO_EDIT); - } - - comment.updateContent(request.getContent()); - return CommentResponse.from(comment); - } - - @Transactional - public void deleteComment(Long commentId, Long requesterProfileId) { - Comment comment = commentRepository.findById(commentId) - .orElseThrow(() -> new CustomResponseException(ResponseError.COMMENT_NOT_FOUND)); - - Long authorId = comment.getProfile().getId(); - Long postOwnerId = comment.getPost().getProfile().getId(); - - if (!authorId.equals(requesterProfileId) && !postOwnerId.equals(requesterProfileId)) { - throw new CustomResponseException(ResponseError.NO_PERMISSION_TO_DELETE); - } - - commentRepository.delete(comment); - } -} diff --git a/src/main/java/com/example/feeda/domain/follow/controller/FollowsController.java b/src/main/java/com/example/feeda/domain/follow/controller/FollowsController.java deleted file mode 100644 index f3a04c8..0000000 --- a/src/main/java/com/example/feeda/domain/follow/controller/FollowsController.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.example.feeda.domain.follow.controller; - -import com.example.feeda.domain.follow.dto.FollowsResponseDto; -import com.example.feeda.domain.follow.service.FollowsService; -import com.example.feeda.domain.profile.dto.ProfileListResponseDto; -import com.example.feeda.security.jwt.JwtPayload; -import jakarta.validation.constraints.Min; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort.Direction; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@Validated -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/follows") -public class FollowsController { - - private final FollowsService followsService; - - @PostMapping("/{profileId}") - public FollowsResponseDto follow(@PathVariable Long profileId, - @AuthenticationPrincipal JwtPayload jwtPayload) { - - return followsService.follow(jwtPayload, profileId); - } - - @DeleteMapping("/{followingId}") - public ResponseEntity unfollow(@PathVariable Long followingId, - @AuthenticationPrincipal JwtPayload jwtPayload) { - - followsService.unfollow(jwtPayload, followingId); - - return ResponseEntity.ok().build(); - } - - @GetMapping("/{profileId}/followings") - public ProfileListResponseDto getFollowings(@PathVariable Long profileId, - @RequestParam(defaultValue = "1") @Min(1) Integer page, - @RequestParam(defaultValue = "10") @Min(5) Integer size, - @AuthenticationPrincipal JwtPayload jwtPayload) { - - Pageable pageable = PageRequest - .of(page - 1, size, Direction.DESC, "createdAt"); - - return followsService.findFollowingsPage(profileId, jwtPayload, pageable); - } - - @GetMapping("/{profileId}/followers") - public ProfileListResponseDto getFollowers(@PathVariable Long profileId, - @RequestParam(defaultValue = "1") @Min(1) Integer page, - @RequestParam(defaultValue = "10") @Min(5) Integer size, - @AuthenticationPrincipal JwtPayload jwtPayload) { - - Pageable pageable = PageRequest - .of(page - 1, size, Direction.DESC, "createdAt"); - - return followsService.findFollowersPage(profileId, jwtPayload, pageable); - } - - @GetMapping("/followings") - public ProfileListResponseDto getMyFollowings( - @AuthenticationPrincipal JwtPayload jwtPayload, - @RequestParam(defaultValue = "1") @Min(1) Integer page, - @RequestParam(defaultValue = "10") @Min(5) Integer size) { - - Pageable pageable = PageRequest - .of(page - 1, size, Direction.DESC, "createdAt"); - - return followsService.findFollowingsPage(jwtPayload.getProfileId(), jwtPayload, pageable); - } - - @GetMapping("/followers") - public ProfileListResponseDto getMyFollowers( - @AuthenticationPrincipal JwtPayload jwtPayload, - @RequestParam(defaultValue = "1") @Min(1) Integer page, - @RequestParam(defaultValue = "10") @Min(5) Integer size) { - - Pageable pageable = PageRequest - .of(page - 1, size, Direction.DESC, "createdAt"); - - return followsService.findFollowersPage(jwtPayload.getProfileId(), jwtPayload, pageable); - } -} diff --git a/src/main/java/com/example/feeda/domain/follow/entity/Follows.java b/src/main/java/com/example/feeda/domain/follow/entity/Follows.java deleted file mode 100644 index f5676c2..0000000 --- a/src/main/java/com/example/feeda/domain/follow/entity/Follows.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.example.feeda.domain.follow.entity; - -import com.example.feeda.domain.profile.entity.Profile; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EntityListeners; -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 java.time.LocalDateTime; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -@Entity -@Getter -@Builder -@AllArgsConstructor -@EntityListeners(AuditingEntityListener.class) -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "follows") -public class Follows { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "follower_id", nullable = false) - private Profile followers; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "following_id", nullable = false) - private Profile followings; - - @CreatedDate - @Column(updatable = false, name = "created_at") - private LocalDateTime createdAt; - -} diff --git a/src/main/java/com/example/feeda/domain/follow/repository/FollowsRepository.java b/src/main/java/com/example/feeda/domain/follow/repository/FollowsRepository.java deleted file mode 100644 index cc9f6f0..0000000 --- a/src/main/java/com/example/feeda/domain/follow/repository/FollowsRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.feeda.domain.follow.repository; - -import com.example.feeda.domain.follow.entity.Follows; -import com.example.feeda.domain.profile.entity.Profile; -import java.util.Optional; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface FollowsRepository extends JpaRepository { - - Optional findByFollowersAndFollowings(Profile followers, Profile followings); - - Page findAllByFollowings_Id(Long followingsId, Pageable pageable); - - Page findAllByFollowers_Id(Long followersId, Pageable pageable); - - // 팔로워 목록 - Long countByFollowings_Id(Long followingsId); - - // 팔로잉 목록 - Long countByFollowers_Id(Long followersId); -} diff --git a/src/main/java/com/example/feeda/domain/follow/service/FollowsService.java b/src/main/java/com/example/feeda/domain/follow/service/FollowsService.java deleted file mode 100644 index 4148c92..0000000 --- a/src/main/java/com/example/feeda/domain/follow/service/FollowsService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.feeda.domain.follow.service; - -import com.example.feeda.domain.follow.dto.FollowsResponseDto; -import com.example.feeda.domain.profile.dto.ProfileListResponseDto; -import com.example.feeda.security.jwt.JwtPayload; -import org.springframework.data.domain.Pageable; - -public interface FollowsService { - - FollowsResponseDto follow(JwtPayload jwtPayload, Long profileId); - - void unfollow(JwtPayload jwtPayload, Long followingId); - - ProfileListResponseDto findFollowingsPage(Long profileId, JwtPayload jwtPayload, - Pageable pageable); - - ProfileListResponseDto findFollowersPage(Long profileId, JwtPayload jwtPayload, - Pageable pageable); -} diff --git a/src/main/java/com/example/feeda/domain/follow/service/FollowsServiceImpl.java b/src/main/java/com/example/feeda/domain/follow/service/FollowsServiceImpl.java deleted file mode 100644 index ea37010..0000000 --- a/src/main/java/com/example/feeda/domain/follow/service/FollowsServiceImpl.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.example.feeda.domain.follow.service; - -import com.example.feeda.domain.follow.dto.FollowsResponseDto; -import com.example.feeda.domain.follow.entity.Follows; -import com.example.feeda.domain.follow.repository.FollowsRepository; -import com.example.feeda.domain.profile.dto.GetProfileResponseDto; -import com.example.feeda.domain.profile.dto.ProfileListResponseDto; -import com.example.feeda.domain.profile.entity.Profile; -import com.example.feeda.domain.profile.repository.ProfileRepository; -import com.example.feeda.exception.CustomResponseException; -import com.example.feeda.exception.enums.ResponseError; -import com.example.feeda.security.jwt.JwtPayload; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Slf4j -@RequiredArgsConstructor -public class FollowsServiceImpl implements FollowsService { - - private final FollowsRepository followsRepository; - private final ProfileRepository profileRepository; - - @Override - @Transactional - public FollowsResponseDto follow(JwtPayload jwtPayload, Long profileId) { - - Profile myProfile = getProfileOrThrow(jwtPayload.getProfileId()); - validateNotSelf(myProfile, profileId); - Profile followingProfile = getProfileOrThrow(profileId); - - Optional follow = - followsRepository.findByFollowersAndFollowings(myProfile, followingProfile); - if (follow.isPresent()) { - throw new CustomResponseException(ResponseError.ALREADY_FOLLOWED); - } - - Follows newFollow = Follows.builder() - .followers(myProfile) - .followings(followingProfile) - .build(); - - followsRepository.save(newFollow); - - return FollowsResponseDto.of(newFollow); - } - - @Override - @Transactional - public void unfollow(JwtPayload jwtPayload, Long followingId) { - - Profile myProfile = getProfileOrThrow(jwtPayload.getProfileId()); - Profile followingProfile = getProfileOrThrow(followingId); - validateNotSelf(myProfile, followingId); - - Optional follows = - followsRepository.findByFollowersAndFollowings(myProfile, followingProfile); - if (follows.isEmpty()) { - throw new CustomResponseException(ResponseError.FOLLOW_NOT_FOUND); - } - - followsRepository.delete(follows.get()); - } - - @Override - public ProfileListResponseDto findFollowingsPage( - Long profileId, - JwtPayload jwtPayload, - Pageable pageable) { - - Page profiles = followsRepository.findAllByFollowers_Id(profileId, pageable).map( - Follows::getFollowings); - - List responseDtoList = profiles.stream() - .map(profile -> GetProfileResponseDto.of( - profile.getId(), - profile.getNickname(), - profile.getBirth(), - profile.getBio(), - profile.getCreatedAt(), - profile.getUpdatedAt() - )) - .toList(); - - return ProfileListResponseDto.of( - responseDtoList, - profiles.getNumber() + 1, // 다시 1부터 시작하는 번호로 반환 - profiles.getTotalPages(), - profiles.getTotalElements() - ); - } - - @Override - public ProfileListResponseDto findFollowersPage( - Long profileId, - JwtPayload jwtPayload, - Pageable pageable) { - - Page profiles = followsRepository.findAllByFollowings_Id(profileId, pageable) - .map(Follows::getFollowers); - - List responseDtoList = profiles.stream() - .map(profile -> GetProfileResponseDto.of( - profile.getId(), - profile.getNickname(), - profile.getBirth(), - profile.getBio(), - profile.getCreatedAt(), - profile.getUpdatedAt() - )) - .toList(); - - return ProfileListResponseDto.of( - responseDtoList, - profiles.getNumber() + 1, // 다시 1부터 시작하는 번호로 반환 - profiles.getTotalPages(), - profiles.getTotalElements() - ); - } - - private Profile getProfileOrThrow(Long profileId) { - Optional optionalProfile = - profileRepository.findById(profileId); - - if (optionalProfile.isEmpty()) { - throw new CustomResponseException(ResponseError.PROFILE_NOT_FOUND); - } - - return optionalProfile.get(); - } - - private void validateNotSelf(Profile me, Long profileId) { - if (me.getId().equals(profileId)) { - throw new CustomResponseException(ResponseError.CANNOT_FOLLOW_SELF); - } - } -} diff --git a/src/main/java/com/example/feeda/domain/post/controller/PostController.java b/src/main/java/com/example/feeda/domain/post/controller/PostController.java deleted file mode 100644 index 9182ae4..0000000 --- a/src/main/java/com/example/feeda/domain/post/controller/PostController.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.example.feeda.domain.post.controller; - -import com.example.feeda.domain.post.dto.PostLikeResponseDTO; -import com.example.feeda.domain.post.dto.PostRequestDto; -import com.example.feeda.domain.post.dto.PostResponseDto; -import com.example.feeda.domain.post.service.PostService; -import com.example.feeda.security.jwt.JwtPayload; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - - -@RestController -@Validated -@RequestMapping("/api/posts") -@RequiredArgsConstructor -public class PostController { - - private final PostService postService; - - @PostMapping - public ResponseEntity createPost(@RequestBody @Valid PostRequestDto requestDto, - @AuthenticationPrincipal JwtPayload jwtPayload) { - - PostResponseDto post = postService.createPost(requestDto, jwtPayload); - - return new ResponseEntity<>(post, HttpStatus.CREATED); - } - - @PostMapping("/{id}/likes") - public ResponseEntity makeLikes( - @PathVariable Long id, - @AuthenticationPrincipal JwtPayload jwtPayload) { - - return new ResponseEntity<>(postService.makeLikes(id, jwtPayload), HttpStatus.OK); - } - - @DeleteMapping("/{id}/likes") - public ResponseEntity deleteLikes( - @PathVariable Long id, - @AuthenticationPrincipal JwtPayload jwtPayload - ) { - Long profileId = jwtPayload.getProfileId(); - postService.deleteLikes(id, profileId); - - return ResponseEntity.ok().build(); - } - - @GetMapping("/{id}") - public ResponseEntity findPostById(@PathVariable @NotNull Long id) { - return new ResponseEntity<>(postService.findPostById(id), HttpStatus.OK); - } - - @GetMapping - public ResponseEntity> findAllPost( - @RequestParam(defaultValue = "1") @Min(1) int page, - @RequestParam(defaultValue = "10") @Min(1) int size, - @RequestParam(defaultValue = "") String keyword, - @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startUpdatedAt, - @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endUpdatedAt - ) { - - Pageable pageable = PageRequest.of(page - 1, size, Sort.Direction.DESC, "updatedAt"); - - return new ResponseEntity<>( - postService.findAll(pageable, keyword, startUpdatedAt, endUpdatedAt), HttpStatus.OK); - } - - @GetMapping("/followings") - public ResponseEntity> findFollowingAllPost( - @RequestParam(defaultValue = "1") @Min(1) int page, - @RequestParam(defaultValue = "10") @Min(1) int size, - @AuthenticationPrincipal JwtPayload jwtPayload - ) { - Pageable pageable = PageRequest.of(page - 1, size); - - return new ResponseEntity<>(postService.findFollowingAllPost(pageable, jwtPayload), - HttpStatus.OK); - } - - @PutMapping("/{id}") - public ResponseEntity updatePost( - @PathVariable @NotNull Long id, - @RequestBody PostRequestDto requestDto, @AuthenticationPrincipal JwtPayload jwtPayload) { - - PostResponseDto post = postService.updatePost(id, requestDto, jwtPayload); - return new ResponseEntity<>(post, HttpStatus.OK); - } - - @DeleteMapping("/{id}") - public ResponseEntity deletePost(@PathVariable @NotNull Long id, - @AuthenticationPrincipal JwtPayload jwtPayload) { - - postService.deletePost(id, jwtPayload); - return new ResponseEntity<>(HttpStatus.OK); - } -} diff --git a/src/main/java/com/example/feeda/domain/post/dto/PostLikeResponseDTO.java b/src/main/java/com/example/feeda/domain/post/dto/PostLikeResponseDTO.java deleted file mode 100644 index adba5b0..0000000 --- a/src/main/java/com/example/feeda/domain/post/dto/PostLikeResponseDTO.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.feeda.domain.post.dto; - -import com.example.feeda.domain.post.entity.PostLike; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -public class PostLikeResponseDTO { - - private final Long id; - private final Long postId; - private final Long profileId; - private final LocalDateTime createdAt; - - public PostLikeResponseDTO(PostLike postLike) { - this.id = postLike.getId(); - this.postId = postLike.getPost().getId(); - this.profileId = postLike.getProfile().getId(); - this.createdAt = postLike.getCreatedAt(); - } -} diff --git a/src/main/java/com/example/feeda/domain/post/dto/PostRequestDto.java b/src/main/java/com/example/feeda/domain/post/dto/PostRequestDto.java deleted file mode 100644 index 17824e0..0000000 --- a/src/main/java/com/example/feeda/domain/post/dto/PostRequestDto.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.feeda.domain.post.dto; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Getter; - -@Getter -public class PostRequestDto { - - @NotNull(message = "제목은 필수 항목입니다.") - private final String title; - - @NotNull(message = "내용은 필수 항목입니다.") - private final String content; - - @Size(max = 50, message = "카테고리는 50자 이하로 입력하세요.") - private final String category; - - public PostRequestDto(String title, String content, String category) { - this.title = title; - this.content = content; - this.category = category; - } - -} diff --git a/src/main/java/com/example/feeda/domain/post/dto/PostResponseDto.java b/src/main/java/com/example/feeda/domain/post/dto/PostResponseDto.java deleted file mode 100644 index fdcfa93..0000000 --- a/src/main/java/com/example/feeda/domain/post/dto/PostResponseDto.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.example.feeda.domain.post.dto; - -import com.example.feeda.domain.post.entity.Post; -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -public class PostResponseDto { - - private final Long id; - - private final String title; - - private final String content; - - private final String category; - - private final Long likes; - - private final Long comments; - - private final LocalDateTime createdAt; - - private final LocalDateTime updatedAt; - - public PostResponseDto(Post post, Long likes, Long comments) { - this.id = post.getId(); - this.title = post.getTitle(); - this.content = post.getContent(); - this.category = post.getCategory(); - this.likes = likes; - this.comments = comments; - this.createdAt = post.getCreatedAt(); - this.updatedAt = post.getUpdatedAt(); - } -} diff --git a/src/main/java/com/example/feeda/domain/post/entity/BaseEntity.java b/src/main/java/com/example/feeda/domain/post/entity/BaseEntity.java deleted file mode 100644 index d787554..0000000 --- a/src/main/java/com/example/feeda/domain/post/entity/BaseEntity.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.feeda.domain.post.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.EntityListeners; -import jakarta.persistence.MappedSuperclass; -import lombok.Getter; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import java.time.LocalDateTime; - -@Getter -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -public abstract class BaseEntity { - - @CreatedDate - @Column(updatable = false) - private LocalDateTime createdAt; - - @LastModifiedDate - private LocalDateTime updatedAt; - -} diff --git a/src/main/java/com/example/feeda/domain/post/entity/Post.java b/src/main/java/com/example/feeda/domain/post/entity/Post.java deleted file mode 100644 index 71592e2..0000000 --- a/src/main/java/com/example/feeda/domain/post/entity/Post.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.example.feeda.domain.post.entity; - -import com.example.feeda.domain.profile.entity.Profile; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -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 lombok.Getter; - - -@Getter -@Entity -@Table(name = "posts") -public class Post extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(length = 100, nullable = false) - private String title; - - @Column(columnDefinition = "longtext", nullable = false) - private String content; - - @Column(length = 50) - private String category; - - @ManyToOne - @JoinColumn(name = "profile_id") - private Profile profile; - - public Post(String title, String content, String category, Profile profile) { - this.title = title; - this.content = content; - this.category = category; - this.profile = profile; - } - - protected Post() { - super(); - } - - public void update(String title, String content, String category) { - this.title = title; - this.content = content; - this.category = category; - } -} diff --git a/src/main/java/com/example/feeda/domain/post/entity/PostLike.java b/src/main/java/com/example/feeda/domain/post/entity/PostLike.java deleted file mode 100644 index b708d80..0000000 --- a/src/main/java/com/example/feeda/domain/post/entity/PostLike.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.example.feeda.domain.post.entity; - -import com.example.feeda.domain.profile.entity.Profile; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import java.time.LocalDateTime; - -@Entity -@Table(name = "post_likes") -@Getter -@AllArgsConstructor -@EntityListeners(AuditingEntityListener.class) -@NoArgsConstructor -public class PostLike { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "post_id", nullable = false) - private Post post; - - @ManyToOne - @JoinColumn(name = "profile_id", nullable = false) - private Profile profile; - - @CreatedDate - @Column(updatable = false, name = "created_at") - private LocalDateTime createdAt; - - public PostLike(Post post, Profile profile) { - this.post = post; - this.profile = profile; - } -} diff --git a/src/main/java/com/example/feeda/domain/post/repository/PostLikeRepository.java b/src/main/java/com/example/feeda/domain/post/repository/PostLikeRepository.java deleted file mode 100644 index 799142e..0000000 --- a/src/main/java/com/example/feeda/domain/post/repository/PostLikeRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.feeda.domain.post.repository; - -import com.example.feeda.domain.post.entity.Post; -import com.example.feeda.domain.post.entity.PostLike; -import com.example.feeda.domain.profile.entity.Profile; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface PostLikeRepository extends JpaRepository { - Optional findByPostAndProfile(Post post, Profile profile); - long countByPost(Post post); // PostLike 갯수 -} diff --git a/src/main/java/com/example/feeda/domain/post/repository/PostRepository.java b/src/main/java/com/example/feeda/domain/post/repository/PostRepository.java deleted file mode 100644 index ff944dc..0000000 --- a/src/main/java/com/example/feeda/domain/post/repository/PostRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.feeda.domain.post.repository; - -import com.example.feeda.domain.post.entity.Post; -import java.time.LocalDateTime; -import java.util.List; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface PostRepository extends JpaRepository { - - Page findAllByTitleContaining(String title, Pageable pageable); - - Page findAllByProfile_IdIn(List followingProfileIds, Pageable pageable); - - Page findAllByTitleContainingAndUpdatedAtBetween(String title, - LocalDateTime startUpdatedAt, LocalDateTime endUpdatedAt, - Pageable pageable); -} diff --git a/src/main/java/com/example/feeda/domain/post/service/PostService.java b/src/main/java/com/example/feeda/domain/post/service/PostService.java deleted file mode 100644 index 463d697..0000000 --- a/src/main/java/com/example/feeda/domain/post/service/PostService.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.feeda.domain.post.service; - -import com.example.feeda.domain.post.dto.PostLikeResponseDTO; -import com.example.feeda.domain.post.dto.PostRequestDto; -import com.example.feeda.domain.post.dto.PostResponseDto; -import com.example.feeda.security.jwt.JwtPayload; -import java.time.LocalDate; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -public interface PostService { - - PostResponseDto createPost(PostRequestDto postRequestDto, - JwtPayload jwtPayload); - - PostResponseDto findPostById(Long id); - - Page findAll(Pageable pageable, String keyword, LocalDate startUpdatedAt, - LocalDate endUpdatedAt); - - Page findFollowingAllPost(Pageable pageable, JwtPayload jwtPayload); - - PostResponseDto updatePost(Long id, PostRequestDto requestDto, JwtPayload jwtPayload); - - void deletePost(Long id, JwtPayload jwtPayload); - - PostLikeResponseDTO makeLikes(Long id, JwtPayload jwtPayload); - - void deleteLikes(Long id, Long profileId); - -} \ No newline at end of file diff --git a/src/main/java/com/example/feeda/domain/post/service/PostServiceImpl.java b/src/main/java/com/example/feeda/domain/post/service/PostServiceImpl.java deleted file mode 100644 index f30cf02..0000000 --- a/src/main/java/com/example/feeda/domain/post/service/PostServiceImpl.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.example.feeda.domain.post.service; - -import com.example.feeda.domain.comment.repository.CommentRepository; -import com.example.feeda.domain.follow.entity.Follows; -import com.example.feeda.domain.follow.repository.FollowsRepository; -import com.example.feeda.domain.post.dto.PostLikeResponseDTO; -import com.example.feeda.domain.post.dto.PostRequestDto; -import com.example.feeda.domain.post.dto.PostResponseDto; -import com.example.feeda.domain.post.entity.Post; -import com.example.feeda.domain.post.entity.PostLike; -import com.example.feeda.domain.post.repository.PostLikeRepository; -import com.example.feeda.domain.post.repository.PostRepository; -import com.example.feeda.domain.profile.entity.Profile; -import com.example.feeda.domain.profile.repository.ProfileRepository; -import com.example.feeda.exception.CustomResponseException; -import com.example.feeda.exception.enums.ResponseError; -import com.example.feeda.security.jwt.JwtPayload; -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class PostServiceImpl implements PostService { - - private final PostRepository postRepository; - private final ProfileRepository profileRepository; // 현재 로그인 사용자 정보를 찾기 위해 필요 - private final FollowsRepository followsRepository; - private final PostLikeRepository postLikeRepository; - private final CommentRepository commentRepository; - - @Override - public PostResponseDto createPost(PostRequestDto postRequestDto, JwtPayload jwtPayload) { - - Profile profile = profileRepository.findById(jwtPayload.getProfileId()) - .orElseThrow( - () -> new CustomResponseException(ResponseError.PROFILE_NOT_FOUND)); - - Post post = new Post(postRequestDto.getTitle(), postRequestDto.getContent(), - postRequestDto.getCategory(), profile); - - Post savedPost = postRepository.save(post); - - return new PostResponseDto(savedPost, 0L, 0L); - } - - @Override - @Transactional - public PostLikeResponseDTO makeLikes(Long id, JwtPayload jwtPayload) { - Post post = postRepository.findById(id).orElseThrow(() -> new CustomResponseException(ResponseError.POST_NOT_FOUND)); - Profile profile = profileRepository.findById(jwtPayload.getProfileId()).orElseThrow(() -> new CustomResponseException(ResponseError.PROFILE_NOT_FOUND)); - - // 중복 좋아요 방지 - postLikeRepository.findByPostAndProfile(post, profile).ifPresent(like -> { - throw new CustomResponseException(ResponseError.ALREADY_LIKED_POST); - }); - - PostLike savePost = postLikeRepository.save(new PostLike(post, profile)); - - return new PostLikeResponseDTO(savePost); - } - - @Override - public void deleteLikes(Long id, Long profileId) { - Post post = postRepository.findById(id) - .orElseThrow(() -> new CustomResponseException(ResponseError.POST_NOT_FOUND)); - - Profile profile = profileRepository.findById(profileId) - .orElseThrow(() -> new CustomResponseException(ResponseError.PROFILE_NOT_FOUND)); - - PostLike postLike = postLikeRepository.findByPostAndProfile(post, profile) - .orElseThrow(() -> new CustomResponseException(ResponseError.NOT_YET_LIKED_POST)); - - postLikeRepository.delete(postLike); - } - - @Override - @Transactional(readOnly = true) - public PostResponseDto findPostById(Long id) { - Optional optionalPost = postRepository.findById(id); - - if (optionalPost.isEmpty()) { - throw new CustomResponseException(ResponseError.POST_NOT_FOUND); - } - - Post findPost = optionalPost.get(); - - Long likeCount = postLikeRepository.countByPost(findPost); - Long commentCount = commentRepository.countByPost(findPost); - - return new PostResponseDto(findPost, likeCount, commentCount); - } - - @Override - @Transactional(readOnly = true) - public Page findAll(Pageable pageable, String keyword, - LocalDate startUpdatedAt, LocalDate endUpdatedAt) { - - if ((startUpdatedAt == null && endUpdatedAt != null) || (startUpdatedAt != null - && endUpdatedAt == null)) { - throw new CustomResponseException(ResponseError.INVALID_DATE_PARAMETERS); - } - - if (startUpdatedAt != null) { - return postRepository.findAllByTitleContainingAndUpdatedAtBetween( - keyword, startUpdatedAt.atStartOfDay(), endUpdatedAt.atTime(23, 59, 59), pageable) - .map(post -> new PostResponseDto( - post, - postLikeRepository.countByPost(post), - commentRepository.countByPost(post) - )); - } - - return postRepository.findAllByTitleContaining(keyword, pageable) - .map(post -> new PostResponseDto( - post, - postLikeRepository.countByPost(post), - commentRepository.countByPost(post) - )); - } - - @Override - @Transactional(readOnly = true) - public Page findFollowingAllPost(Pageable pageable, JwtPayload jwtPayload) { - - Page followings = followsRepository.findAllByFollowers_Id( - jwtPayload.getProfileId(), pageable); - - List followingProfileIds = followings.stream() - .map(following -> following.getFollowings().getId()) - .toList(); - - Pageable newPageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), - Sort.Direction.DESC, "updatedAt"); - - return postRepository.findAllByProfile_IdIn(followingProfileIds, newPageable) - .map(post -> new PostResponseDto( - post, - postLikeRepository.countByPost(post), - commentRepository.countByPost(post) - )); - } - - @Override - @Transactional - public PostResponseDto updatePost(Long id, PostRequestDto requestDto, JwtPayload jwtPayload) { - - Post findPost = postRepository.findById(id) - .orElseThrow(() -> new CustomResponseException(ResponseError.POST_NOT_FOUND)); - - if (!findPost.getProfile().getId().equals(jwtPayload.getProfileId())) { - throw new CustomResponseException(ResponseError.NO_PERMISSION_TO_EDIT); - } - - findPost.update(requestDto.getTitle(), requestDto.getContent(), requestDto.getCategory()); - Post savedPost = postRepository.save(findPost); - - Long likeCount = postLikeRepository.countByPost(findPost); - Long commentCount = commentRepository.countByPost(findPost); - - return new PostResponseDto(savedPost, likeCount, commentCount); - } - - @Override - @Transactional - public void deletePost(Long id, JwtPayload jwtPayload) { - - Post findPost = postRepository.findById(id) - .orElseThrow(() -> new CustomResponseException(ResponseError.POST_NOT_FOUND)); - - if (!findPost.getProfile().getId().equals(jwtPayload.getProfileId())) { - throw new CustomResponseException(ResponseError.NO_PERMISSION_TO_DELETE); - } - - postRepository.delete(findPost); - } -} diff --git a/src/main/java/com/example/feeda/domain/profile/controller/ProfileController.java b/src/main/java/com/example/feeda/domain/profile/controller/ProfileController.java deleted file mode 100644 index d67d55e..0000000 --- a/src/main/java/com/example/feeda/domain/profile/controller/ProfileController.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.example.feeda.domain.profile.controller; - -import com.example.feeda.domain.profile.dto.*; -import com.example.feeda.domain.profile.service.ProfileServiceImpl; -import com.example.feeda.security.jwt.JwtPayload; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api") -public class ProfileController { - - private final ProfileServiceImpl profileService; - - public ProfileController(ProfileServiceImpl profileService) { - this.profileService = profileService; - } - - /** - * 프로필 단건 조회 API - */ - - @GetMapping("/profiles/{id}") - public ResponseEntity getProfile(@PathVariable Long id) { - GetProfileWithFollowCountResponseDto responseDto = profileService.getProfile(id); - return new ResponseEntity<>(responseDto, HttpStatus.OK); - } - - /** - * 프로필 전체 조회 API (검색, 페이징) - */ - - @GetMapping("/profiles") - public ResponseEntity getProfiles( - @RequestParam(required = false) String keyword, - @RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "10") int size) { - - ProfileListResponseDto response = profileService.getProfiles(keyword, page, size); - return new ResponseEntity<>(response, HttpStatus.OK); - } - - /** - * 프로필 전체 수정 API (검색, 페이징) - */ - - @PutMapping("/profiles/{id}") - public ResponseEntity updateProfile( - @AuthenticationPrincipal JwtPayload jwtPayload, - @PathVariable Long id, - @Valid @RequestBody UpdateProfileRequestDto requestDto) { - - Long accountId = jwtPayload.getAccountId(); - UpdateProfileResponseDto responseDto = profileService.updateProfile(accountId, id, requestDto); - - return new ResponseEntity<>(responseDto, HttpStatus.OK); - } -} diff --git a/src/main/java/com/example/feeda/domain/profile/dto/GetProfileWithFollowCountResponseDto.java b/src/main/java/com/example/feeda/domain/profile/dto/GetProfileWithFollowCountResponseDto.java deleted file mode 100644 index c3f9228..0000000 --- a/src/main/java/com/example/feeda/domain/profile/dto/GetProfileWithFollowCountResponseDto.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.example.feeda.domain.profile.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.time.LocalDateTime; -import java.util.Date; - - -@AllArgsConstructor(staticName = "of") -@Getter -public class GetProfileWithFollowCountResponseDto { - - // 계정 ID - private final Long id; - - // 닉네임 - private final String nickname; - - // 생일 - private final Date birth; - - // 자기소개 - private final String bio; - - // 팔로워 수 - private final Long followerCount; - - // 팔로잉 수 - private final Long followingCount; - - //생성 시간 - private final LocalDateTime createdAt; - - //마지막 수정 시간 - private final LocalDateTime updatedAt; -} - diff --git a/src/main/java/com/example/feeda/domain/profile/entity/Profile.java b/src/main/java/com/example/feeda/domain/profile/entity/Profile.java deleted file mode 100644 index 6456941..0000000 --- a/src/main/java/com/example/feeda/domain/profile/entity/Profile.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.example.feeda.domain.profile.entity; - -import com.example.feeda.domain.account.entity.Account; -import jakarta.persistence.*; -import lombok.*; - -import java.time.LocalDateTime; -import java.util.Date; - -@Entity -@Table(name = "profiles") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor -public class Profile extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false) - private String nickname; - - private Date birth; - - private String bio; - - @Setter - @OneToOne - @JoinColumn(name = "account_id", unique = true) - private Account account; - - //프로필 생성해줄때 사용 - public static Profile create(String nickname, Date birth, String bio, Account account) { - return new Profile(null, nickname, birth, bio, null, null, account); - } - - public Profile(String nickname, Date birth, String bio) { - this.nickname = nickname; - this.birth = birth; - this.bio = bio; - } - - public Profile(Long id, String nickname, Date birth, String bio, LocalDateTime createdAt, LocalDateTime updatedAt, Account account) { - super(updatedAt, createdAt); - this.id = id; - this.nickname = nickname; - this.birth = birth; - this.bio = bio; - this.account = account; - } - - public void updateProfile(String nickname, Date birth, String bio) { - this.nickname = nickname; - this.birth = birth; - this.bio = bio; - } - } diff --git a/src/main/java/com/example/feeda/domain/profile/repository/ProfileRepository.java b/src/main/java/com/example/feeda/domain/profile/repository/ProfileRepository.java deleted file mode 100644 index 1ae5472..0000000 --- a/src/main/java/com/example/feeda/domain/profile/repository/ProfileRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.feeda.domain.profile.repository; - -import com.example.feeda.domain.profile.entity.Profile; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface ProfileRepository extends JpaRepository { - - /** - * 닉네임으로 목록 검색(페이징) - */ - - Page findByNicknameContaining(String nickname, Pageable pageable); - - Optional findByNickname(String nickname); -} diff --git a/src/main/java/com/example/feeda/domain/profile/service/ProfileService.java b/src/main/java/com/example/feeda/domain/profile/service/ProfileService.java deleted file mode 100644 index 098637e..0000000 --- a/src/main/java/com/example/feeda/domain/profile/service/ProfileService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.feeda.domain.profile.service; - -import com.example.feeda.domain.profile.dto.GetProfileWithFollowCountResponseDto; -import com.example.feeda.domain.profile.dto.ProfileListResponseDto; -import com.example.feeda.domain.profile.dto.UpdateProfileRequestDto; -import com.example.feeda.domain.profile.dto.UpdateProfileResponseDto; - -public interface ProfileService { - GetProfileWithFollowCountResponseDto getProfile(Long id); - - ProfileListResponseDto getProfiles(String keyword, int page, int size); - - UpdateProfileResponseDto updateProfile(Long userId, Long profileId, UpdateProfileRequestDto requestDto); -} diff --git a/src/main/java/com/example/feeda/domain/profile/service/ProfileServiceImpl.java b/src/main/java/com/example/feeda/domain/profile/service/ProfileServiceImpl.java deleted file mode 100644 index 0941ac1..0000000 --- a/src/main/java/com/example/feeda/domain/profile/service/ProfileServiceImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.example.feeda.domain.profile.service; - -import com.example.feeda.domain.follow.repository.FollowsRepository; -import com.example.feeda.domain.profile.dto.*; -import com.example.feeda.domain.profile.entity.Profile; -import com.example.feeda.domain.profile.repository.ProfileRepository; -import com.example.feeda.exception.CustomResponseException; -import com.example.feeda.exception.enums.ResponseError; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Service -@RequiredArgsConstructor -public class ProfileServiceImpl implements ProfileService { - private final ProfileRepository profileRepository; - private final FollowsRepository followsRepository; - - /** - * 프로필 단건 조회 기능 - */ - @Override - @Transactional(readOnly = true) - public GetProfileWithFollowCountResponseDto getProfile(Long id) { - - Profile profile = profileRepository.findById(id) - .orElseThrow(() -> new CustomResponseException(ResponseError.PROFILE_NOT_FOUND)); - - Long followerCount = followsRepository.countByFollowings_Id(id); - Long followingCount = followsRepository.countByFollowers_Id(id); - - return GetProfileWithFollowCountResponseDto.of( - profile.getId(), - profile.getNickname(), - profile.getBirth(), - profile.getBio(), - followerCount, - followingCount, - profile.getCreatedAt(), - profile.getUpdatedAt() - ); - } - - /** - * 프로필 다건 조회 기능(검색,페이징) - */ - @Override - @Transactional(readOnly = true) - public ProfileListResponseDto getProfiles(String keyword, int page, int size) { - - if (page < 1 || size < 1) { - throw new CustomResponseException(ResponseError.INVALID_PAGINATION_PARAMETERS); - } - - Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").ascending()); - - Page profilePage; - if (keyword == null || keyword.trim().isEmpty()) { - profilePage = profileRepository.findAll(pageable); - } else { - profilePage = profileRepository.findByNicknameContaining(keyword, pageable); - } - - List responseDtoList = profilePage.stream() - .map(profile -> GetProfileResponseDto.of( - profile.getId(), - profile.getNickname(), - profile.getBirth(), - profile.getBio(), - profile.getCreatedAt(), - profile.getUpdatedAt() - )) - .toList(); - - return ProfileListResponseDto.of( - responseDtoList, - profilePage.getNumber() + 1, // 다시 1부터 시작하는 번호로 반환 - profilePage.getTotalPages(), - profilePage.getTotalElements() - ); - } - - - /** - * 프로필 수정 기능 - */ - @Override - @Transactional - public UpdateProfileResponseDto updateProfile(Long userId, Long profileId, UpdateProfileRequestDto requestDto) { - - Profile profile = profileRepository.findById(profileId) - .orElseThrow(() -> new CustomResponseException(ResponseError.PROFILE_NOT_FOUND)); - - if (!profile.getAccount().getId().equals(userId)) { - throw new CustomResponseException(ResponseError.NO_PERMISSION_TO_EDIT); - } - - if (requestDto.getNickname() != null || requestDto.getBirth() != null || requestDto.getBio() != null) { - profile.updateProfile( - requestDto.getNickname(), - requestDto.getBirth(), - requestDto.getBio() - ); - } - - profileRepository.save(profile); - - return UpdateProfileResponseDto.from("프로필이 성공적으로 수정되었습니다."); - } -} diff --git a/src/main/java/com/example/feeda/exception/CustomResponseException.java b/src/main/java/com/example/feeda/exception/CustomResponseException.java deleted file mode 100644 index efcf233..0000000 --- a/src/main/java/com/example/feeda/exception/CustomResponseException.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.feeda.exception; - -import com.example.feeda.exception.enums.ResponseError; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -public class CustomResponseException extends RuntimeException { - - private final HttpStatus httpStatus; - private final String errorMessage; - - public CustomResponseException(ResponseError responseError) { - this.httpStatus = responseError.getHttpStatus(); - this.errorMessage = responseError.getMessage(); - } -} \ No newline at end of file diff --git a/src/main/java/com/example/feeda/exception/GlobalExceptionHandler.java b/src/main/java/com/example/feeda/exception/GlobalExceptionHandler.java deleted file mode 100644 index 35314fd..0000000 --- a/src/main/java/com/example/feeda/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.example.feeda.exception; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.ConstraintViolationException; -import org.springframework.context.support.DefaultMessageSourceResolvable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindException; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import java.time.LocalDateTime; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; - -@RestControllerAdvice -public class GlobalExceptionHandler { - - @ExceptionHandler({ - MethodArgumentNotValidException.class, - BindException.class, - ConstraintViolationException.class - }) - public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) { - String message = ex.getBindingResult().getFieldErrors() - .stream() - .map(DefaultMessageSourceResolvable::getDefaultMessage) - .collect(Collectors.joining(", ")); - - Map body = new LinkedHashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("status", HttpStatus.BAD_REQUEST.value()); - body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase()); - body.put("message", message); - body.put("path", request.getRequestURI()); - - return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(CustomResponseException.class) - public ResponseEntity> handleCustomResponseException(CustomResponseException ex, HttpServletRequest request) { - Map body = new LinkedHashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("status", ex.getHttpStatus().value()); - body.put("error", ex.getHttpStatus().getReasonPhrase()); - body.put("message", ex.getErrorMessage()); - body.put("path", request.getRequestURI()); - - return new ResponseEntity<>(body, ex.getHttpStatus()); - } - - @ExceptionHandler(TokenNotFoundException.class) - public ResponseEntity> handleTokenNotFoundException(TokenNotFoundException ex, HttpServletRequest request) { - Map body = new LinkedHashMap<>(); - body.put("timestamp", LocalDateTime.now()); - body.put("status", HttpStatus.UNAUTHORIZED.value()); - body.put("error", HttpStatus.UNAUTHORIZED.getReasonPhrase()); - body.put("message", ex.getMessage()); - body.put("path", request.getRequestURI()); - - return new ResponseEntity<>(body, HttpStatus.UNAUTHORIZED); - } -} - - diff --git a/src/main/java/com/example/feeda/exception/enums/ResponseError.java b/src/main/java/com/example/feeda/exception/enums/ResponseError.java deleted file mode 100644 index a442fad..0000000 --- a/src/main/java/com/example/feeda/exception/enums/ResponseError.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.example.feeda.exception.enums; - -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -public enum ResponseError { - // 회원 관리 관련 오류 - EMAIL_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 이메일 입니다."), - INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."), - INVALID_EMAIL_OR_PASSWORD(HttpStatus.UNAUTHORIZED, "이메일 또는 비밀번호가 일치하지 않습니다."), - ACCOUNT_NOT_FOUND(HttpStatus.NOT_FOUND, "계정이 존재하지 않습니다."), - - // 프로필 관련 오류 - NICKNAME_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 닉네임 입니다."), - PROFILE_NOT_FOUND(HttpStatus.NOT_FOUND, "프로필이 존재하지 않습니다."), - - // 팔로우 관련 오류 - ALREADY_FOLLOWED(HttpStatus.BAD_REQUEST, "이미 팔로우한 계정입니다."), - FOLLOW_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 팔로우입니다."), - CANNOT_FOLLOW_SELF(HttpStatus.BAD_REQUEST, "본인 프로필은 팔로우/언팔로우 할 수 없습니다"), - - // 게시글 관련 오류 - POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시글이 존재하지 않습니다"), - COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글이 존재하지 않습니다"), - ALREADY_LIKED_POST(HttpStatus.BAD_REQUEST, "이미 좋아요한 게시글 입니다."), - NOT_YET_LIKED_POST(HttpStatus.BAD_REQUEST, "아직 좋아요 하지 않은 게시글 입니다."), - ALREADY_LIKED_COMMENT(HttpStatus.BAD_REQUEST, "이미 좋아요한 댓글입니다."), - NOT_YET_LIKED_COMMENT(HttpStatus.BAD_REQUEST, "아직 좋아요 하지 않은 댓글 입니다."), - - // 페이징 & 검색 관련 오류 - INVALID_PAGINATION_PARAMETERS(HttpStatus.BAD_REQUEST, "페이지 번호는 1 이상, 페이지 크기는 1 이상이어야 합니다."), - INVALID_DATE_PARAMETERS(HttpStatus.BAD_REQUEST, "startUpdatedAt과 endUpdatedAt은 둘 다 있어야 하거나 둘 다 없어야 합니다."), - - // 권한 관련 오류 - NO_PERMISSION_TO_EDIT(HttpStatus.FORBIDDEN, "수정 권한이 없습니다."), - NO_PERMISSION_TO_DELETE(HttpStatus.FORBIDDEN, "삭제 권한이 없습니다."); - - private final HttpStatus httpStatus; - private final String message; - - ResponseError(HttpStatus httpStatus, String message) { - this.httpStatus = httpStatus; - this.message = message; - } -} diff --git a/src/main/java/com/example/feeda/exception/enums/ServletResponseError.java b/src/main/java/com/example/feeda/exception/enums/ServletResponseError.java deleted file mode 100644 index 57a16a7..0000000 --- a/src/main/java/com/example/feeda/exception/enums/ServletResponseError.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.feeda.exception.enums; - -import jakarta.servlet.http.HttpServletResponse; -import lombok.Getter; - -@Getter -public enum ServletResponseError { - // JWT 관련 오류 - INVALID_JWT_SIGNATURE(HttpServletResponse.SC_UNAUTHORIZED, "유효하지 않는 JWT 서명입니다."), - EXPIRED_JWT_TOKEN(HttpServletResponse.SC_UNAUTHORIZED, "만료된 JWT 토큰입니다."), - UNSUPPORTED_JWT(HttpServletResponse.SC_BAD_REQUEST, "지원되지 않는 JWT 토큰입니다."), - INVALID_JWT(HttpServletResponse.SC_BAD_REQUEST, "잘못된 JWT 토큰입니다."), - - // Security 관련 오류 - UNAUTHORIZED(HttpServletResponse.SC_UNAUTHORIZED, "인증이 필요합니다."), - ACCESS_DENIED(HttpServletResponse.SC_FORBIDDEN, "접근이 거부되었습니다."), - - // 내부 서버 오류 - INTERNAL_SERVER_ERROR(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "내부 서버 오류입니다."); - - private final int httpStatus; - private final String message; - - ServletResponseError(int httpStatus, String message) { - this.httpStatus = httpStatus; - this.message = message; - } -} diff --git a/src/main/java/com/example/feeda/filter/JwtFilter.java b/src/main/java/com/example/feeda/filter/JwtFilter.java deleted file mode 100644 index 33d4cd6..0000000 --- a/src/main/java/com/example/feeda/filter/JwtFilter.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.example.feeda.filter; - -import com.example.feeda.exception.enums.ServletResponseError; -import com.example.feeda.exception.TokenNotFoundException; -import com.example.feeda.security.jwt.JwtBlacklistService; -import com.example.feeda.security.jwt.JwtPayload; -import com.example.feeda.security.jwt.JwtUtil; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.UnsupportedJwtException; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; -import java.util.List; - -@RequiredArgsConstructor -public class JwtFilter extends OncePerRequestFilter { - private final JwtBlacklistService jwtBlacklistService; - private final JwtUtil jwtUtil; - - @Override - protected void doFilterInternal( - HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain chain - ) throws ServletException, IOException { - - String bearerJwt = request.getHeader("Authorization"); - - if (bearerJwt == null || !bearerJwt.matches("^Bearer\\s+[A-Za-z0-9-_.]+$")) { - chain.doFilter(request, response); - return; - } - - String jwt = jwtUtil.extractToken(bearerJwt); - - try { - // JWT 유효성 검사와 claims 추출 - Claims claims = jwtUtil.extractClaims(jwt); - if (claims == null) { - response.sendError(ServletResponseError.INVALID_JWT.getHttpStatus(), ServletResponseError.INVALID_JWT.getMessage()); - return; - } - - // 블랙리스트 검증 - if (jwtBlacklistService.isBlacklisted(jwt)) { - response.sendError(ServletResponseError.EXPIRED_JWT_TOKEN.getHttpStatus(), ServletResponseError.EXPIRED_JWT_TOKEN.getMessage()); - return; - } - - Long accountId = jwtUtil.getAccountId(jwt); - Long profileId = jwtUtil.getProfileId(jwt); - String nickName = jwtUtil.getNickName(jwt); - String email = jwtUtil.getEmail(jwt); - JwtPayload payload = new JwtPayload(accountId, profileId, email, nickName); - - // 인증 객체 생성: 사용자 정보(payload), 패스워드(""), 권한 목록(empty) - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( - payload, null, List.of(new SimpleGrantedAuthority("ROLE_USER")) - ); - - // 인증 정보 등록 - SecurityContextHolder.getContext().setAuthentication(authenticationToken); - - chain.doFilter(request, response); - - } catch (SecurityException | MalformedJwtException | TokenNotFoundException e) { - response.sendError(ServletResponseError.INVALID_JWT_SIGNATURE.getHttpStatus(), ServletResponseError.INVALID_JWT_SIGNATURE.getMessage()); - } catch (ExpiredJwtException e) { - response.sendError(ServletResponseError.EXPIRED_JWT_TOKEN.getHttpStatus(), ServletResponseError.EXPIRED_JWT_TOKEN.getMessage()); - } catch (UnsupportedJwtException e) { - response.sendError(ServletResponseError.UNSUPPORTED_JWT.getHttpStatus(), ServletResponseError.UNSUPPORTED_JWT.getMessage()); - } catch (Exception e) { - response.sendError(ServletResponseError.INTERNAL_SERVER_ERROR.getHttpStatus(), ServletResponseError.INTERNAL_SERVER_ERROR.getMessage()); - } - } -} diff --git a/src/main/java/com/example/feeda/security/handler/CustomAccessDeniedHandler.java b/src/main/java/com/example/feeda/security/handler/CustomAccessDeniedHandler.java deleted file mode 100644 index a7f77ab..0000000 --- a/src/main/java/com/example/feeda/security/handler/CustomAccessDeniedHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.example.feeda.security.handler; - -import com.example.feeda.exception.enums.ServletResponseError; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.HttpStatus; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.LinkedHashMap; -import java.util.Map; - -@Component -public class CustomAccessDeniedHandler implements AccessDeniedHandler { - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { - response.setStatus(ServletResponseError.ACCESS_DENIED.getHttpStatus()); - response.setContentType("application/json;charset=UTF-8"); - - Map body = new LinkedHashMap<>(); - body.put("timestamp", LocalDateTime.now().toString()); - body.put("status", ServletResponseError.ACCESS_DENIED.getHttpStatus()); - body.put("error", HttpStatus.FORBIDDEN.getReasonPhrase()); - body.put("message", ServletResponseError.ACCESS_DENIED.getMessage()); - body.put("path", request.getRequestURI()); - - String jsonBody = objectMapper.writeValueAsString(body); - - response.getWriter().write(jsonBody); - } -} diff --git a/src/main/java/com/example/feeda/security/handler/CustomAuthenticationEntryPoint.java b/src/main/java/com/example/feeda/security/handler/CustomAuthenticationEntryPoint.java deleted file mode 100644 index 5bf4a37..0000000 --- a/src/main/java/com/example/feeda/security/handler/CustomAuthenticationEntryPoint.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.example.feeda.security.handler; - -import com.example.feeda.exception.enums.ServletResponseError; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.LinkedHashMap; -import java.util.Map; - -@Component -public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - response.setStatus(ServletResponseError.UNAUTHORIZED.getHttpStatus()); - response.setContentType("application/json;charset=UTF-8"); - - Map body = new LinkedHashMap<>(); - body.put("timestamp", LocalDateTime.now().toString()); - body.put("status", ServletResponseError.UNAUTHORIZED.getHttpStatus()); - body.put("error", HttpStatus.UNAUTHORIZED.getReasonPhrase()); - body.put("message", ServletResponseError.UNAUTHORIZED.getMessage()); - body.put("path", request.getRequestURI()); - - String jsonBody = objectMapper.writeValueAsString(body); - - response.getWriter().write(jsonBody); - } -} diff --git a/src/main/java/com/example/feeda/security/jwt/JwtBlacklistService.java b/src/main/java/com/example/feeda/security/jwt/JwtBlacklistService.java deleted file mode 100644 index 61caeed..0000000 --- a/src/main/java/com/example/feeda/security/jwt/JwtBlacklistService.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.example.feeda.security.jwt; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.util.concurrent.TimeUnit; - -@Service -@RequiredArgsConstructor -public class JwtBlacklistService { - private static final String BLACKLIST_PREFIX = "BL:"; - - private final RedisTemplate redisTemplate; - private final JwtUtil jwtUtil; - - - // 토큰을 블랙리스트에 저장 - public void addBlacklist(String token) { - long expirationMillis = jwtUtil.getRemainingExpiration(token); - - redisTemplate.opsForValue() - .set(getKey(token), "true", expirationMillis, TimeUnit.MILLISECONDS); - } - - // 요청 시 토큰이 블랙리스트인지 확인 - public boolean isBlacklisted(String token) { - return Boolean.TRUE.equals(redisTemplate.hasKey(getKey(token))); - } - - private String getKey(String token) { - return BLACKLIST_PREFIX + token; - } -} diff --git a/src/main/java/com/example/feeda/security/jwt/JwtPayload.java b/src/main/java/com/example/feeda/security/jwt/JwtPayload.java deleted file mode 100644 index ae6a166..0000000 --- a/src/main/java/com/example/feeda/security/jwt/JwtPayload.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.feeda.security.jwt; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class JwtPayload { - private final Long accountId; - private final Long profileId; - private final String email; - private final String nickName; -} diff --git a/src/main/java/com/example/feeda/security/jwt/JwtUtil.java b/src/main/java/com/example/feeda/security/jwt/JwtUtil.java deleted file mode 100644 index ba73898..0000000 --- a/src/main/java/com/example/feeda/security/jwt/JwtUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.example.feeda.security.jwt; - -import com.example.feeda.exception.TokenNotFoundException; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -import java.security.Key; -import java.util.Base64; -import java.util.Date; - -@Component -public class JwtUtil { - private static final String BEARER_PREFIX = "Bearer "; - private static final long TOKEN_TIME = 60 * 60 * 1000L; // 60분 - - @Value("${jwt.secret.key}") - private String secretKey; - private Key key; - private static final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; - - @PostConstruct - public void init() { - byte[] bytes = Base64.getDecoder().decode(secretKey); - key = Keys.hmacShaKeyFor(bytes); - } - - public String createToken(JwtPayload payload) { - Date date = new Date(); - - return Jwts.builder() - .setSubject(String.valueOf(payload.getAccountId())) - .claim("profileId", payload.getProfileId()) - .claim("nickName", payload.getNickName()) - .claim("email", payload.getEmail()) - .setExpiration(new Date(date.getTime() + TOKEN_TIME)) - .setIssuedAt(date) // 발급일 - .signWith(key, signatureAlgorithm) // 암호화 알고리즘 - .compact(); - } - - public String extractToken(String tokenValue) { - if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) { - return tokenValue.substring(BEARER_PREFIX.length()); // "Bearer " 제거 후 반환 - } - - throw new TokenNotFoundException("토큰을 찾을 수 없습니다."); - } - - public Claims extractClaims(String token) { - return Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); - } - - public long getRemainingExpiration(String token) { - Date expiration = extractClaims(token).getExpiration(); - return expiration.getTime() - System.currentTimeMillis(); - } - - public Long getAccountId(String token) { - return Long.parseLong(extractClaims(token).getSubject()); - } - - public Long getProfileId(String token) { - return extractClaims(token).get("profileId", Long.class); - } - - public String getNickName(String token) { - return extractClaims(token).get("nickName", String.class); - } - - public String getEmail(String token) { - return extractClaims(token).get("email", String.class); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3f45db5..dd0f008 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,29 +1,3 @@ spring: application: - name: feeda - - datasource: - url: ${DB_URL} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - - data: - redis: - host: ${REDIS_HOST} - port: ${REDIS_PORT} - password: ${REDIS_PASSWORD} - -server: - error: - include-message: always - -jwt: - secret: - key: ${SECRET_KEY} - - -#logging: -# level: -# org: -# springframework: -# security: trace \ No newline at end of file + name: feeda \ No newline at end of file diff --git a/src/test/java/com/example/feeda/unit/service/FollowServiceTest.java b/src/test/java/com/example/feeda/unit/service/FollowServiceTest.java deleted file mode 100644 index 7815fa2..0000000 --- a/src/test/java/com/example/feeda/unit/service/FollowServiceTest.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.example.feeda.unit.service; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.example.feeda.domain.account.entity.Account; -import com.example.feeda.domain.follow.dto.FollowsResponseDto; -import com.example.feeda.domain.follow.entity.Follows; -import com.example.feeda.domain.follow.repository.FollowsRepository; -import com.example.feeda.domain.follow.service.FollowsServiceImpl; -import com.example.feeda.domain.profile.dto.ProfileListResponseDto; -import com.example.feeda.domain.profile.entity.Profile; -import com.example.feeda.domain.profile.repository.ProfileRepository; -import com.example.feeda.security.jwt.JwtPayload; -import java.time.LocalDateTime; -import java.util.Date; -import java.util.List; -import java.util.Optional; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; - -@ExtendWith(MockitoExtension.class) -public class FollowServiceTest { - - @InjectMocks - FollowsServiceImpl followsServiceimpl; - - @Mock - FollowsRepository followsRepository; - - @Mock - ProfileRepository profileRepository; - - @Test - void 계정_팔로우_단위_테스트() { - - //given - Long myAccountId = 1L; - Long myProfileId = 1L; - Long targetProfileId = 2L; - LocalDateTime now = LocalDateTime.now(); - - JwtPayload jwtPayload = new JwtPayload(myAccountId, myProfileId, "test@naver.com", "test"); - - Account myAccount = new Account(); - Profile myProfile = new Profile(myProfileId, "test", new Date(), "intro myself", now, now, - myAccount); - - Account targetAccount = new Account(); - Profile targetProfile = new Profile(targetProfileId, "target", new Date(), "intro target", - now, now, targetAccount); - - when(profileRepository.findById(myProfileId)).thenReturn(Optional.of(myProfile)); - when(profileRepository.findById(targetProfileId)).thenReturn(Optional.of(targetProfile)); - when(followsRepository.findByFollowersAndFollowings(myProfile, targetProfile)) - .thenReturn(Optional.empty()); - - Follows newFollow = Follows.builder() - .followers(myProfile) - .followings(targetProfile) - .build(); - - when(followsRepository.save(any(Follows.class))).thenReturn(newFollow); - - //when - FollowsResponseDto followsResponseDto = followsServiceimpl.follow(jwtPayload, - targetProfileId); - - //then - assertThat(followsResponseDto.getFollowerId()).isEqualTo(myProfileId); - assertThat(followsResponseDto.getFollowingId()).isEqualTo(targetProfileId); - } - - @Test - void 계정_언팔로우_단위_테스트() { - - //given - Long myAccountId = 1L; - Long myProfileId = 1L; - Long followingProfileId = 2L; - LocalDateTime now = LocalDateTime.now(); - - JwtPayload jwtPayload = new JwtPayload(myAccountId, myProfileId, "test@naver.com", "test"); - - Account myAccount = new Account(); - Profile myProfile = new Profile(myProfileId, "test", new Date(), "intro myself", now, now, - myAccount); - - Account targetAccount = new Account(); - Profile targetProfile = new Profile(followingProfileId, "target", new Date(), - "intro target", - now, now, targetAccount); - - Follows follow = Follows.builder() - .followers(myProfile) - .followings(targetProfile) - .build(); - - when(profileRepository.findById(myProfileId)).thenReturn(Optional.of(myProfile)); - when(profileRepository.findById(followingProfileId)).thenReturn(Optional.of(targetProfile)); - when(followsRepository.findByFollowersAndFollowings(myProfile, targetProfile)) - .thenReturn(Optional.of(follow)); - doNothing().when(followsRepository).delete(follow); - - //when - followsServiceimpl.unfollow(jwtPayload, - followingProfileId); - - // then - verify(followsRepository, times(1)).delete(follow); - } - - @Test - void 팔로워_목록_조회_단위_테스트() { - Long myAccountId = 1L; - Long firstProfileId = 1L; - Long secondProfileId = 2L; - Long followingProfileId = 3L; - LocalDateTime now = LocalDateTime.now(); - Pageable pageable = PageRequest.of(0, 10); - - JwtPayload jwtPayload = new JwtPayload(myAccountId, firstProfileId, "test@naver.com", - "firstNickname"); - - Account myAccount = new Account(); - Profile firstProfile = new Profile(firstProfileId, "firstNickname", new Date(), - "intro myself", now, - now, - myAccount); - - Account secondAccount = new Account(); - Profile secondProfile = new Profile(secondProfileId, "secondNickname", new Date(), - "intro myself", now, - now, - secondAccount); - - Account targetAccount = new Account(); - Profile targetProfile = new Profile(followingProfileId, "targetNickname", new Date(), - "intro target", - now, now, targetAccount); - - Follows firstFollow = Follows.builder().followers(firstProfile).followings(targetProfile) - .build(); - Follows secondFollow = Follows.builder().followers(secondProfile).followings(targetProfile) - .build(); - - Page followsPage = new PageImpl<>(List.of(firstFollow, secondFollow), pageable, 2); - when(followsRepository.findAllByFollowings_Id(followingProfileId, pageable)).thenReturn( - followsPage); - - // when - ProfileListResponseDto result = followsServiceimpl.findFollowersPage(followingProfileId, - jwtPayload, pageable); - - // then - assertThat(result.getProfiles().get(0).getNickname()).isEqualTo("firstNickname"); - assertThat(result.getProfiles().get(1).getNickname()).isEqualTo("secondNickname"); - assertThat(result.getCurrentPage()).isEqualTo(1); - } - - @Test - void 팔로잉_목록_조회_단위_테스트() { - Long myAccountId = 1L; - Long firstProfileId = 1L; - Long secondProfileId = 2L; - Long followingProfileId = 3L; - LocalDateTime now = LocalDateTime.now(); - Pageable pageable = PageRequest.of(0, 10); - - JwtPayload jwtPayload = new JwtPayload(myAccountId, firstProfileId, "test@naver.com", - "firstNickname"); - - Account myAccount = new Account(); - Profile firstProfile = new Profile(firstProfileId, "firstNickname", new Date(), - "intro myself", now, - now, - myAccount); - - Account secondAccount = new Account(); - Profile secondProfile = new Profile(secondProfileId, "secondNickname", new Date(), - "intro myself", now, - now, - secondAccount); - - Account targetAccount = new Account(); - Profile targetProfile = new Profile(followingProfileId, "targetNickname", new Date(), - "intro target", - now, now, targetAccount); - - Follows firstFollow = Follows.builder().followers(firstProfile).followings(secondProfile) - .build(); - Follows secondFollow = Follows.builder().followers(firstProfile).followings(targetProfile) - .build(); - - Page followsPage = new PageImpl<>(List.of(firstFollow, secondFollow), pageable, 2); - when(followsRepository.findAllByFollowers_Id(firstProfileId, pageable)).thenReturn( - followsPage); - - // when - ProfileListResponseDto result = followsServiceimpl.findFollowingsPage(firstProfileId, - jwtPayload, pageable); - - // then - assertThat(result.getProfiles().get(0).getNickname()).isEqualTo("secondNickname"); - assertThat(result.getProfiles().get(1).getNickname()).isEqualTo("targetNickname"); - assertThat(result.getCurrentPage()).isEqualTo(1); - } -} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml deleted file mode 100644 index 412d302..0000000 --- a/src/test/resources/application-test.yml +++ /dev/null @@ -1,11 +0,0 @@ -spring: - datasource: - url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false - driver-class-name: org.h2.Driver - username: test - password: - - jpa: - hibernate: - ddl-auto: create-drop - database-platform: org.hibernate.dialect.H2Dialect