Skip to content

Commit 7893de3

Browse files
authored
Merge pull request #28 from prgrms-web-devcourse-final-project/feature/profile
프로필 기능 구현 앞서 팔로우 기능 간단 구현
2 parents b46c370 + a4ea8da commit 7893de3

File tree

18 files changed

+514
-45
lines changed

18 files changed

+514
-45
lines changed

.github/workflows/dev-build.yml

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: log4u-dev-build
1+
name: dev-build
22
on:
33
pull_request:
44
branches:
@@ -8,6 +8,7 @@ on:
88
jobs:
99
build:
1010
name: Build and analyze
11+
environment: develop-test
1112
runs-on: ubuntu-latest
1213
steps:
1314
- uses: actions/checkout@v4
@@ -40,25 +41,52 @@ jobs:
4041
docker run --name log4u-mysql \
4142
-e MYSQL_ROOT_PASSWORD=root \
4243
-e MYSQL_DATABASE=log4u \
43-
-e MYSQL_USER=dev \
44-
-e MYSQL_PASSWORD=devcos4-team08 \
44+
-e MYSQL_USER=${{ secrets.DB_USERNAME }} \
45+
-e MYSQL_PASSWORD=${{ secrets.DB_PASSWORD}} \
4546
-d \
4647
-p 3307:3306 \
4748
mysql:8.0.33
4849
49-
- name: Build and analyze
50+
- name: Wait for MySQL to be ready
51+
run: |
52+
for i in {1..10}; do
53+
if docker exec log4u-mysql mysqladmin ping -h "127.0.0.1" --silent; then
54+
echo "MySQL is ready!"
55+
break
56+
fi
57+
echo "Waiting for MySQL to start..."
58+
sleep 1
59+
done
60+
if ! docker exec log4u-mysql mysqladmin ping -h "127.0.0.1" --silent; then
61+
echo "MySQL did not start in time!"
62+
exit 1
63+
fi
64+
65+
- name: Test and analyze
5066
env:
51-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67+
#GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5268
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
53-
DB_URL: jdbc:mysql://localhost:3307/log4u
54-
DB_USERNAME: dev
55-
DB_PASSWORD: devcos4-team08
69+
DB_URL: ${{ secrets.DB_URL }}
70+
DB_USERNAME: ${{ secrets.DB_USERNAME }}
71+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
72+
NAVER_DEV_CLIENT_ID: ${{ secrets.NAVER_DEV_CLIENT_ID }}
73+
NAVER_DEV_CLIENT_SECRET: ${{ secrets.NAVER_DEV_CLIENT_SECRET }}
74+
NAVER_DEV_REDIRECT_URI: ${{ secrets.NAVER_DEV_REDIRECT_URI }}
75+
GOOGLE_DEV_CLIENT_ID: ${{ secrets.GOOGLE_DEV_CLIENT_ID }}
76+
GOOGLE_DEV_CLIENT_SECRET: ${{ secrets.GOOGLE_DEV_CLIENT_SECRET }}r
77+
GOOGLE_DEV_REDIRECT_URI: ${{ secrets.GOOGLE_DEV_REDIRECT_URI }}
78+
KAKAO_DEV_CLIENT_ID: ${{ secrets.KAKAO_DEV_CLIENT_ID }}
79+
KAKAO_DEV_CLIENT_SECRET: ${{ secrets.KAKAO_DEV_CLIENT_SECRET }}
80+
KAKAO_DEV_REDIRECT_URI: ${{ secrets.KAKAO_DEV_REDIRECT_URI }}
81+
JWT_SECRET: ${{ secrets.JWT_SECRET }}
82+
JWT_ACCESS_TOKEN_EXPIRE_TIME_SECONDS: ${{ secrets.JWT_ACCESS_TOKEN_EXPIRE_TIME_SECONDS }}
83+
JWT_REFRESH_TOKEN_EXPIRE_TIME_SECONDS: ${{ secrets.JWT_ACCESS_TOKEN_EXPIRE_TIME_SECONDS }}
5684

5785
# dev 프로필 사용
5886
run: |
5987
chmod +x ./gradlew
6088
# 소나클라우드 임시 비활성화 ./gradlew build jacocoTestReport sonar --info -Pprofile=dev -Dsonar.branch.name=${{ github.ref_name }}
61-
./gradlew build jacocoTestReport -Pprofile=dev
89+
./gradlew build -i jacocoTestReport -Pprofile=dev
6290
6391
- name: Docker MySQL 종료 및 제거
6492
run: |

src/main/java/com/example/log4u/domain/diary/service/DiaryService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ private List<VisibilityType> determineAccessibleVisibilities(Long userId, Long t
169169
return List.of(VisibilityType.PUBLIC, VisibilityType.PRIVATE, VisibilityType.FOLLOWER);
170170
}
171171

172-
if (followRepository.existsByFollowerIdAndFollowingId(userId, targetUserId)) {
172+
if (followRepository.existsByInitiatorIdAndTargetId(userId, targetUserId)) {
173173
return List.of(VisibilityType.PUBLIC, VisibilityType.FOLLOWER);
174174
}
175175

@@ -186,7 +186,7 @@ private void validateDiaryAccess(Diary diary, Long userId) {
186186

187187
if (diary.getVisibility() == VisibilityType.FOLLOWER) {
188188
if (!diary.getUserId().equals(userId)
189-
&& !followRepository.existsByFollowerIdAndFollowingId(userId, diary.getUserId())) {
189+
&& !followRepository.existsByInitiatorIdAndTargetId(userId, diary.getUserId())) {
190190
throw new NotFoundDiaryException();
191191
}
192192
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.example.log4u.domain.follow.controller;
2+
3+
import org.springframework.http.ResponseEntity;
4+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
5+
import org.springframework.stereotype.Controller;
6+
import org.springframework.web.bind.annotation.DeleteMapping;
7+
import org.springframework.web.bind.annotation.PathVariable;
8+
import org.springframework.web.bind.annotation.PostMapping;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
11+
import com.example.log4u.common.oauth2.dto.CustomOAuth2User;
12+
import com.example.log4u.domain.follow.service.FollowService;
13+
14+
import io.swagger.v3.oas.annotations.tags.Tag;
15+
import lombok.RequiredArgsConstructor;
16+
17+
@Tag(name = "팔로우 API")
18+
@Controller
19+
@RequiredArgsConstructor
20+
@RequestMapping("/users")
21+
public class FollowController {
22+
private final FollowService followService;
23+
24+
@PostMapping("/{nickname}/follow")
25+
public ResponseEntity<Void> createFollow(
26+
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
27+
@PathVariable("nickname") String nickname
28+
) {
29+
followService.createFollow(customOAuth2User.getUserId(), nickname);
30+
return ResponseEntity.ok().build();
31+
}
32+
33+
@DeleteMapping("/{nickname}/follow")
34+
public ResponseEntity<Void> deleteFollow(
35+
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
36+
@PathVariable("nickname") String nickname
37+
) {
38+
followService.deleteFollow(customOAuth2User.getUserId(), nickname);
39+
return ResponseEntity.ok().build();
40+
}
41+
42+
}

src/main/java/com/example/log4u/domain/follow/entitiy/Follow.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import lombok.Getter;
1313
import lombok.NoArgsConstructor;
1414

15-
@Entity
15+
@Entity(name = "follow")
1616
@Getter
1717
@Builder
1818
@AllArgsConstructor
@@ -22,7 +22,18 @@ public class Follow extends BaseEntity {
2222
@GeneratedValue(strategy = GenerationType.IDENTITY)
2323
private Long id;
2424

25-
private Long followerId;
25+
// 팔로우를 누른 주체
26+
private Long initiatorId;
2627

27-
private Long followingId;
28+
// 팔로우를 받은 대상
29+
private Long targetId;
30+
31+
// dto 넣기 애매해서 엔티티 내부에 static 함수 구현
32+
public static Follow of(Long initiatorId, Long targetId) {
33+
return new Follow(
34+
null,
35+
initiatorId,
36+
targetId
37+
);
38+
}
2839
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.example.log4u.domain.follow.exception;
2+
3+
import org.springframework.http.HttpStatus;
4+
5+
import com.example.log4u.common.exception.base.ErrorCode;
6+
7+
import lombok.Getter;
8+
import lombok.RequiredArgsConstructor;
9+
10+
@Getter
11+
@RequiredArgsConstructor
12+
public enum FollowErrorCode implements ErrorCode {
13+
FOLLOW_NOT_FOUND(HttpStatus.NOT_FOUND, "팔로우 정보가 존재하지 않습니다."),
14+
FOLLOWER_NOT_FOUND(HttpStatus.NOT_FOUND, "팔로워 정보가 존재하지 않습니다.");
15+
16+
private final HttpStatus httpStatus;
17+
private final String message;
18+
19+
@Override
20+
public HttpStatus getHttpStatus() {
21+
return httpStatus;
22+
}
23+
24+
@Override
25+
public String getErrorMessage() {
26+
return message;
27+
}
28+
}
29+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example.log4u.domain.follow.exception;
2+
3+
import com.example.log4u.common.exception.base.ServiceException;
4+
5+
public class FollowNotFoundException extends ServiceException {
6+
public FollowNotFoundException() {
7+
super(FollowErrorCode.FOLLOW_NOT_FOUND);
8+
}
9+
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
package com.example.log4u.domain.follow.repository;
22

33
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.springframework.stereotype.Repository;
45

56
import com.example.log4u.domain.follow.entitiy.Follow;
67

8+
@Repository
79
public interface FollowRepository extends JpaRepository<Follow, Long> {
10+
boolean existsByInitiatorIdAndTargetId(Long initiatorId, Long targetId);
811

9-
boolean existsByFollowerIdAndFollowingId(Long followerId, Long followingId);
12+
void deleteByInitiatorIdAndTargetId(Long initiatorId, Long targetId);
13+
14+
// 기능 구현 초기용, 데이터 쌓이면 개선 필요
15+
Long countByInitiatorId(Long initiatorId);
16+
17+
Long countByTargetId(Long targetId);
1018
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.example.log4u.domain.follow.service;
2+
3+
import org.springframework.stereotype.Service;
4+
import org.springframework.transaction.annotation.Transactional;
5+
6+
import com.example.log4u.domain.follow.entitiy.Follow;
7+
import com.example.log4u.domain.follow.exception.FollowNotFoundException;
8+
import com.example.log4u.domain.follow.repository.FollowRepository;
9+
import com.example.log4u.domain.user.service.UserService;
10+
11+
import lombok.RequiredArgsConstructor;
12+
13+
@Service
14+
@RequiredArgsConstructor
15+
public class FollowService {
16+
private final FollowRepository followRepository;
17+
private final UserService userService;
18+
19+
@Transactional
20+
public void createFollow(Long initiatorId, String nickname) {
21+
validateTargetUser(nickname);
22+
followRepository.save(Follow.of(
23+
initiatorId,
24+
userService.getUserIdByNickname(nickname)));
25+
}
26+
27+
@Transactional
28+
public void deleteFollow(Long userId, String nickname) {
29+
Long targetId = userService.getUserIdByNickname(nickname);
30+
validateFollow(userId, targetId);
31+
followRepository.deleteByInitiatorIdAndTargetId(userId, targetId);
32+
}
33+
34+
private void validateTargetUser(String nickname) {
35+
// USER NOT FOUND EXCEPTION
36+
userService.getUserByNickname(nickname);
37+
}
38+
39+
private void validateFollow(Long userId, Long targetId) {
40+
if (!followRepository.existsByInitiatorIdAndTargetId(userId, targetId)) {
41+
throw new FollowNotFoundException();
42+
}
43+
}
44+
45+
/**
46+
* 나를 팔로우 하는 사람 수 조회
47+
* */
48+
@Transactional(readOnly = true)
49+
public Long getFollowerCount(Long userId) {
50+
return followRepository.countByTargetId(userId);
51+
}
52+
53+
/**
54+
* 내가 팔로우하는 사람 수 조회
55+
* */
56+
@Transactional(readOnly = true)
57+
public Long getFollowingCount(Long userId) {
58+
return followRepository.countByInitiatorId(userId);
59+
}
60+
}

src/main/java/com/example/log4u/domain/user/controller/UserController.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package com.example.log4u.domain.user.controller;
22

3+
import org.springframework.http.ResponseEntity;
34
import org.springframework.security.core.annotation.AuthenticationPrincipal;
45
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.PathVariable;
7+
import org.springframework.web.bind.annotation.PutMapping;
58
import org.springframework.web.bind.annotation.RequestMapping;
69
import org.springframework.web.bind.annotation.RestController;
710

811
import com.example.log4u.common.oauth2.dto.CustomOAuth2User;
12+
import com.example.log4u.domain.user.dto.NicknameValidationResponseDto;
13+
import com.example.log4u.domain.user.dto.UserProfileResponseDto;
14+
import com.example.log4u.domain.user.dto.UserProfileUpdateRequestDto;
15+
import com.example.log4u.domain.user.service.UserService;
916

1017
import lombok.extern.slf4j.Slf4j;
1118

@@ -14,11 +21,54 @@
1421
@Slf4j
1522
public class UserController {
1623

24+
private final UserService userService;
25+
26+
public UserController(UserService userService) {
27+
this.userService = userService;
28+
}
29+
1730
@GetMapping("")
1831
public String modifyUserProfile(
1932
@AuthenticationPrincipal CustomOAuth2User customOAuth2User
2033
) {
2134
log.info("테스트 GET DATA user = " + customOAuth2User.getUserId());
2235
return "test";
2336
}
37+
38+
@GetMapping("/me")
39+
public ResponseEntity<UserProfileResponseDto> getMyProfile(
40+
@AuthenticationPrincipal CustomOAuth2User customOAuth2User
41+
) {
42+
UserProfileResponseDto userProfileResponseDto =
43+
userService.getMyProfile(customOAuth2User.getUserId());
44+
return ResponseEntity.ok(userProfileResponseDto);
45+
}
46+
47+
@GetMapping("/{nickname}")
48+
public ResponseEntity<UserProfileResponseDto> getUserProfile(
49+
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
50+
@PathVariable String nickname
51+
) {
52+
UserProfileResponseDto userProfileResponseDto =
53+
userService.getUserProfile(nickname);
54+
return ResponseEntity.ok(userProfileResponseDto);
55+
}
56+
57+
@PutMapping("/me")
58+
public ResponseEntity<UserProfileResponseDto> updateMyProfile(
59+
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
60+
UserProfileUpdateRequestDto userProfileUpdateRequestDto
61+
) {
62+
UserProfileResponseDto userProfileResponseDto =
63+
userService.updateMyProfile(customOAuth2User.getUserId(), userProfileUpdateRequestDto);
64+
return ResponseEntity.ok(userProfileResponseDto);
65+
}
66+
67+
@GetMapping("/validation/{nickname}")
68+
public ResponseEntity<NicknameValidationResponseDto> validateNickname(
69+
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
70+
@PathVariable String nickname
71+
) {
72+
return ResponseEntity.ok(userService.validateNickname(nickname));
73+
}
2474
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.example.log4u.domain.user.dto;
2+
3+
public record NicknameValidationResponseDto(Boolean available) {
4+
}

0 commit comments

Comments
 (0)