Skip to content

Commit

Permalink
Merge pull request #12 from edinfazlic/005-user-overview
Browse files Browse the repository at this point in the history
User overview
  • Loading branch information
edinfazlic committed Mar 20, 2020
2 parents 239af7f + 8b152a0 commit e4b039b
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.minitwitter.controller;

import com.minitwitter.domain.dto.ErrorMessage;
import com.minitwitter.service.ExceptionHandlingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.http.HttpStatus.BAD_REQUEST;

@RestController
@Slf4j
public abstract class ExceptionHandlingController {

@ExceptionHandler
@ResponseStatus(BAD_REQUEST)
public ErrorMessage handleUnknownUsernameException(ExceptionHandlingService.UnknownUsernameException e) {
log.warn("", e);
return new ErrorMessage(String.format("Unknown user '%s'", e.getUsername()));
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/minitwitter/controller/OverviewController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.minitwitter.controller;

import com.minitwitter.domain.dto.UserOverviewDTO;
import com.minitwitter.service.OverviewService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("overview")
@Slf4j
public class OverviewController extends ExceptionHandlingController{

private OverviewService overviewService;

public OverviewController(OverviewService overviewService) {
this.overviewService = overviewService;
}

@GetMapping(value = "{username}")
public UserOverviewDTO followers(@PathVariable String username) {
return overviewService.getUsersFollowers(username);
}

}
12 changes: 2 additions & 10 deletions src/main/java/com/minitwitter/controller/TweetController.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.minitwitter.controller;

import com.minitwitter.domain.dto.ErrorMessage;
import com.minitwitter.domain.dto.TweetDTO;
import com.minitwitter.service.TweetService;
import com.minitwitter.service.TweetService.InvalidTweetException;
import com.minitwitter.service.TweetService.UnknownUsernameException;
import com.minitwitter.domain.dto.TweetDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -24,7 +23,7 @@
@RestController
@RequestMapping("tweets")
@Slf4j
public class TweetController {
public class TweetController extends ExceptionHandlingController {

private TweetService tweetService;

Expand All @@ -48,13 +47,6 @@ public Collection<TweetDTO> tweetsFromUser(@PathVariable String username) {
return tweetService.tweetsFromUser(username);
}

@ExceptionHandler
@ResponseStatus(BAD_REQUEST)
public ErrorMessage handleUnknownUsernameException(UnknownUsernameException e) {
log.warn("", e);
return new ErrorMessage(String.format("Unknown user '%s'", e.getUsername()));
}

@ExceptionHandler
@ResponseStatus(BAD_REQUEST)
public ErrorMessage handleInvalidTweetException(InvalidTweetException e) {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/minitwitter/domain/dto/UserOverviewDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.minitwitter.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import static lombok.AccessLevel.PRIVATE;

@Getter
@AllArgsConstructor
@NoArgsConstructor(access = PRIVATE)
public class UserOverviewDTO {
private int tweets;
private int followers;
private int following;
}
2 changes: 2 additions & 0 deletions src/main/java/com/minitwitter/repository/TweetRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
@Repository
public interface TweetRepository extends JpaRepository<Tweet, Long> {
List<Tweet> findAllByAuthor(User author);

int countByAuthorUsername(String username);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.minitwitter.service;

import com.minitwitter.domain.User;
import com.minitwitter.repository.UserRepository;
import lombok.Getter;
import org.springframework.stereotype.Service;

@Service
public abstract class ExceptionHandlingService {

private UserRepository userRepository;

public ExceptionHandlingService(UserRepository userRepository) {
this.userRepository = userRepository;
}

protected User getUser(String username) {
User user = userRepository.findOneByUsername(username);
if (user != null) {
return user;
}
throw new UnknownUsernameException(username);
}

public static class UnknownUsernameException extends RuntimeException {
@Getter
private String username;

private UnknownUsernameException(String username) {
super(username);
this.username = username;
}
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/minitwitter/service/OverviewService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.minitwitter.service;

import com.minitwitter.domain.User;
import com.minitwitter.domain.dto.UserOverviewDTO;
import com.minitwitter.repository.TweetRepository;
import com.minitwitter.repository.UserRepository;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
public class OverviewService extends ExceptionHandlingService {

private TweetRepository tweetRepository;

public OverviewService(TweetRepository tweetRepository, UserRepository userRepository) {
super(userRepository);
this.tweetRepository = tweetRepository;
}

@Transactional
public UserOverviewDTO getUsersFollowers(String username) {
int tweets = this.tweetRepository.countByAuthorUsername(username);

User user = this.getUser(username);
int followers = user.getFollowers().size();
int following = user.getFollowing().size();

return new UserOverviewDTO(tweets, followers, following);
}

}
23 changes: 2 additions & 21 deletions src/main/java/com/minitwitter/service/TweetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
import static java.util.stream.Collectors.toList;

@Service
public class TweetService {
public class TweetService extends ExceptionHandlingService {

private TweetRepository tweetRepository;
private UserRepository userRepository;

public TweetService(TweetRepository tweetRepository, UserRepository userRepository) {
super(userRepository);
this.tweetRepository = tweetRepository;
this.userRepository = userRepository;
}

public Collection<TweetDTO> followingUsersTweets(Principal principal) {
Expand Down Expand Up @@ -53,24 +52,6 @@ private List<TweetDTO> tweetsFromUser(User user) {
return tweetRepository.findAllByAuthor(user).stream().map(TweetDTO::new).collect(toList());
}

private User getUser(String username) {
User user = userRepository.findOneByUsername(username);
if (user != null) {
return user;
}
throw new UnknownUsernameException(username);
}

public static class UnknownUsernameException extends RuntimeException {
@Getter
private String username;

private UnknownUsernameException(String username) {
super(username);
this.username = username;
}
}

public static class InvalidTweetException extends RuntimeException {

private InvalidTweetException(String tweet) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.minitwitter.controller;

import com.minitwitter.domain.dto.ErrorMessage;
import com.minitwitter.domain.dto.UserOverviewDTO;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.util.Collections;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

public class OverviewControllerIntegrationTest extends RestIntegrationTest {

@Test
public void overviewOfUser_onlyThatUserTweetsAreReturned() {
ResponseEntity<UserOverviewDTO> response = withAuthTestRestTemplate().getForEntity("/overview/{username}",
UserOverviewDTO.class, Collections.singletonMap("username", getUsernameOfAuthUser()));
assertThat(response.getStatusCode().is2xxSuccessful(), is(true));
UserOverviewDTO overview = response.getBody();
assertThat(overview.getTweets(), equalTo(ownTweetsCount()));
assertThat(overview.getFollowers(), equalTo(followersCount()));
assertThat(overview.getFollowing(), equalTo(followingUsers().length));
}

@Test
public void overviewOfUserWithWrongUsername_badRequestReturned() {
ResponseEntity<ErrorMessage> response = withAuthTestRestTemplate().getForEntity("/overview/{username}",
ErrorMessage.class, Collections.singletonMap("username", "unknown"));
assertThat(response.getStatusCode(), is(HttpStatus.BAD_REQUEST));
assertThat(response.getBody().getMessage(), containsString("unknown"));
}

}
24 changes: 21 additions & 3 deletions src/test/java/com/minitwitter/controller/RestIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,36 @@
@SpringBootTest(webEnvironment = RANDOM_PORT)
public abstract class RestIntegrationTest {

private static final String AUTH_USERNAME = "aantonop";
private static final int OWN_TWEETS_COUNT = 3;
private static final int FOLLOWERS_COUNT = 1;
private static final String[] FOLLOWING_USERS = new String[]{"rogerkver", "satoshiNakamoto", "SatoshiLite", "VitalikButerin"};
private static final String[] FOLLOWERS = new String[]{"rogerkver"};

@Autowired
private TestRestTemplate testRestTemplate;

TestRestTemplate withAuthTestRestTemplate() {
return testRestTemplate.withBasicAuth("aantonop", "password");
return testRestTemplate.withBasicAuth(AUTH_USERNAME, "password");
}

String getUsernameOfAuthUser() {
return "aantonop";
return AUTH_USERNAME;
}

String[] followingUsers() {
return new String[]{"rogerkver", "satoshiNakamoto", "SatoshiLite", "VitalikButerin"};
return FOLLOWING_USERS;
}

String[] followers() {
return FOLLOWERS;
}

int ownTweetsCount() {
return OWN_TWEETS_COUNT;
}

int followersCount() {
return FOLLOWERS_COUNT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void createTweet_tweetIsSaved() {
Tweet fromDb = tweetRepository.findOne(tweet.getId());
assertThat(fromDb, notNullValue());
assertThat(fromDb.getContent(), equalTo(tweet.getContent()));
assertThat(fromDb.getAuthor().getUsername(), equalTo(tweet.getAuthor()));
assertThat(fromDb.getAuthor().getUsername(), equalTo(tweet.getAuthorUsername()));
}

@Test
Expand Down Expand Up @@ -76,6 +76,6 @@ public void tweetsFromUserWithWrongUsername_badRequestReturned() {
}

private List<String> extractAuthorNames(TweetDTO[] body) {
return Arrays.stream(body).map(TweetDTO::getAuthor).distinct().collect(toList());
return Arrays.stream(body).map(TweetDTO::getAuthorUsername).distinct().collect(toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ public void followersRequested_allFollowersReturned() {
ResponseEntity<UserDTO[]> response = withAuthTestRestTemplate().getForEntity("/followers", UserDTO[].class);
assertThat(response.getStatusCode().is2xxSuccessful(), is(true));
List<UserDTO> followers = Arrays.asList(response.getBody());
assertThat(followers, hasSize(1));
assertThat(extractUsernames(followers), contains("rogerkver"));
assertThat(followers, hasSize(followers().length));
assertThat(extractUsernames(followers), containsInAnyOrder(followers()));
}

@Test
public void getFollowingFromFirstPage() {
ResponseEntity<UserDTO[]> response = withAuthTestRestTemplate().getForEntity("/following", UserDTO[].class);
assertThat(response.getStatusCode().is2xxSuccessful(), is(true));
List<UserDTO> following = Arrays.asList(response.getBody());
assertThat(following, hasSize(4));
assertThat(following, hasSize(followingUsers().length));
assertThat(extractUsernames(following), containsInAnyOrder(followingUsers()));
}

Expand Down

0 comments on commit e4b039b

Please sign in to comment.