Skip to content

Commit

Permalink
feat: 친구 맺기 API 개발 (#25)
Browse files Browse the repository at this point in the history
* feat: User도메인에 친구 필드와 친구 추가 메소드를 구현한다

* fix: ddl에 빠진 필드를 추가하고, UserFixture에 friends 를 기본삽입한다

* feat: 친구 맺기 API를 개발한다

* refactor: 유저 수정 API에서 LoginContext를 사용하도록 수정한다

* test: 도메인별로 흩어져있는 통합테스트를 하나로 합친다
  • Loading branch information
devxb authored Jan 3, 2024
1 parent a63dc55 commit 246a88d
Show file tree
Hide file tree
Showing 22 changed files with 324 additions and 222 deletions.
9 changes: 9 additions & 0 deletions src/main/java/net/teumteum/core/context/LoginContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.teumteum.core.context;

public interface LoginContext {

void setUserId(Long userId);

Long getUserId();

}
25 changes: 25 additions & 0 deletions src/main/java/net/teumteum/core/context/LoginContextImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package net.teumteum.core.context;

import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;

@Component
@Profile("prod")
@RequestScope(proxyMode = ScopedProxyMode.INTERFACES)
public class LoginContextImpl implements LoginContext {

private Long userId;

@Override
public void setUserId(Long userId) {
this.userId = userId;
}

@Override
public Long getUserId() {
return userId;
}

}
12 changes: 11 additions & 1 deletion src/main/java/net/teumteum/user/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import net.teumteum.core.context.LoginContext;
import net.teumteum.core.error.ErrorResponse;
import net.teumteum.user.domain.request.UserUpdateRequest;
import net.teumteum.user.domain.response.UserGetResponse;
Expand All @@ -11,6 +12,7 @@
import org.springframework.web.bind.annotation.ExceptionHandler;
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;
Expand All @@ -24,6 +26,7 @@
public class UserController {

private final UserService userService;
private final LoginContext loginContext;

@GetMapping("/{userId}")
@ResponseStatus(HttpStatus.OK)
Expand All @@ -44,9 +47,16 @@ public UsersGetByIdResponse getUsersById(@RequestParam("id") String userIds) {
@PutMapping
@ResponseStatus(HttpStatus.OK)
public void updateUser(@RequestBody UserUpdateRequest request) {
userService.updateUser(request);
userService.updateUser(loginContext.getUserId(), request);
}

@PostMapping("/{friendId}/friends")
@ResponseStatus(HttpStatus.OK)
public void addFriend(@PathVariable("friendId") Long friendId) {
userService.addFriends(loginContext.getUserId(), friendId);
}


@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(IllegalArgumentException illegalArgumentException) {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/net/teumteum/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import jakarta.persistence.Id;
import jakarta.persistence.PrePersist;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -69,6 +71,9 @@ public class User extends TimeBaseEntity {
@Embedded
private Terms terms;

@ElementCollection(fetch = FetchType.LAZY)
private Set<Long> friends = new HashSet<>();

@PrePersist
private void assertField() {
assertName();
Expand Down Expand Up @@ -98,4 +103,8 @@ private void assertMannerTemperature() {
Assert.isTrue(mannerTemperature >= 0, () -> "매너 온도는 0도 이상 이여야 합니다. \"" + mannerTemperature + "\"");
}

public void addFriend(User user) {
friends.add(user.id);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Set;
import net.teumteum.user.domain.ActivityArea;
import net.teumteum.user.domain.Job;
import net.teumteum.user.domain.JobStatus;
Expand All @@ -27,6 +28,7 @@ public record UserUpdateRequest(
private static final Oauth IGNORE_OAUTH = null;
private static final boolean NOT_CERTIFICATED = false;
private static final Terms IGNORE_TERMS = null;
private static final Set<Long> IGNORE_FRIENDS = Set.of();

public User toUser() {
return new User(
Expand All @@ -50,7 +52,8 @@ public User toUser() {
newJob.detailClass
),
newInterests,
IGNORE_TERMS
IGNORE_TERMS,
IGNORE_FRIENDS
);
}

Expand Down
21 changes: 16 additions & 5 deletions src/main/java/net/teumteum/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public class UserService {
private final UserRepository userRepository;

public UserGetResponse getUserById(Long userId) {
var existUser = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("userId에 해당하는 user를 찾을 수 없습니다. \"" + userId + "\""));
var existUser = getUser(userId);

return UserGetResponse.of(existUser);
}
Expand All @@ -39,10 +38,22 @@ private void assertIsAllUserExist(List<Long> userIds, List<User> existUsers) {
}

@Transactional
public void updateUser(UserUpdateRequest request) {
var existUser = userRepository.findById(request.id())
.orElseThrow(() -> new IllegalArgumentException("userId에 해당하는 user를 찾을 수 없습니다. \"" + request.id() + "\""));
public void updateUser(Long userId, UserUpdateRequest request) {
var existUser = getUser(userId);

existUser.update(request.toUser());
}

@Transactional
public void addFriends(Long myId, Long friendId) {
var me = getUser(myId);
var friend = getUser(friendId);

me.addFriend(friend);
}

private User getUser(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("userId에 해당하는 user를 찾을 수 없습니다. \"" + userId + "\""));
}
}
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
spring.profiles.active=prod

### SERVER CONFIG ###
server.port=8080
server.name=teum-teum-server
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/db/migration/V3__add_friends_to_users.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
create table if not exists users_friends(
users_id bigint not null,
friends bigint not null,
foreign key (users_id) references users(id)
);
85 changes: 85 additions & 0 deletions src/test/java/net/teumteum/integration/Api.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package net.teumteum.integration;

import net.teumteum.meeting.config.PageableHandlerMethodArgumentResolver;
import net.teumteum.meeting.domain.Topic;
import net.teumteum.user.domain.request.UserUpdateRequest;
import org.springframework.boot.test.context.TestComponent;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;

@TestComponent
class Api {

private final WebTestClient webTestClient;

public Api(ApplicationContext applicationContext) {
var controllers = applicationContext.getBeansWithAnnotation(Controller.class).values();
webTestClient = WebTestClient.bindToController(controllers.toArray())
.argumentResolvers(resolvers -> resolvers.addCustomResolver(new PageableHandlerMethodArgumentResolver()))
.build();
}

ResponseSpec getUser(String token, Long userId) {
return webTestClient.get()
.uri("/users/" + userId)
.header(HttpHeaders.AUTHORIZATION, token)
.exchange();
}

ResponseSpec getUsersById(String token, String userIds) {
return webTestClient.get()
.uri("/users?id=" + userIds)
.header(HttpHeaders.AUTHORIZATION, token)
.exchange();
}

ResponseSpec updateUser(String token, UserUpdateRequest userUpdateRequest) {
return webTestClient.put()
.uri("/users")
.header(HttpHeaders.AUTHORIZATION, token)
.bodyValue(userUpdateRequest)
.exchange();
}

ResponseSpec addFriends(String token, Long friendId) {
return webTestClient.post()
.uri("/users/" + friendId + "/friends")
.header(HttpHeaders.AUTHORIZATION, token)
.exchange();
}

ResponseSpec getOpenMeetings(String token, Long cursorId, int size) {
return webTestClient.get()
.uri("/meetings" +
"?cursorId=" + cursorId +
"&size=" + size)
.header(HttpHeaders.AUTHORIZATION, token)
.exchange();
}

ResponseSpec getMeetingById(String token, Long meetingId) {
return webTestClient.get()
.uri("/meetings/" + meetingId)
.header(HttpHeaders.AUTHORIZATION, token)
.exchange();
}

ResponseSpec getMeetingsByTopic(String token, Pageable pageable, boolean isOpen, Topic topic) {
String sort = pageable.getSort().toString().replace(": ", ",");
String uri = "/meetings?sort=" + sort +
"&page=" + pageable.getOffset() +
"&size=" + pageable.getPageSize() +
"&isOpen=" + isOpen +
"&topic=" + topic;

return webTestClient.get()
.uri(uri)
.header(HttpHeaders.AUTHORIZATION, token)
.exchange();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.teumteum.user.integration;
package net.teumteum.integration;

import net.teumteum.Application;
import net.teumteum.core.context.LoginContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -9,15 +10,18 @@
import org.springframework.test.context.ContextConfiguration;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = {Application.class, Api.class, Repository.class})
abstract class IntegrationTest {
@ContextConfiguration(classes = {Application.class, Api.class, Repository.class, TestLoginContext.class})
abstract public class IntegrationTest {

@Autowired
protected Api api;

@Autowired
protected Repository repository;

@Autowired
protected LoginContext loginContext;

@AfterEach
@BeforeEach
void clearAll() {
Expand Down
Loading

0 comments on commit 246a88d

Please sign in to comment.