Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: 0.5.2 #223

Merged
merged 8 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,55 @@
# tmtm-server
<img src="https://github.com/depromeet/teum-teum-server/assets/91249216/70e21198-48c2-4ea1-9d87-3e619c4c3217" width="120" height="120" align="left"/>

# Teum-Teum

> 사람 사이의 **틈**을 이어주는 IT 커리어 네트워킹 서비스 `repo:server`

![Build](https://img.shields.io/github/actions/workflow/status/depromeet/teum-teum-server/integration-tester.yml?branch=develop&style=for-the-badge&logo=github&logoColor=white&color=36B2FF) ![](https://img.shields.io/sonar/test_success_density/depromeet_teum-teum-server?server=https%3A%2F%2Fsonarcloud.io&style=for-the-badge&logo=sonar&logoColor=white&color=36B2FF) ![](https://img.shields.io/sonar/quality_gate/depromeet_teum-teum-server/develop?server=https%3A%2F%2Fsonarcloud.io&style=for-the-badge&logo=sonar&logoColor=white&color=36B2FF) ![](https://img.shields.io/github/v/release/depromeet/teum-teum-server?include_prereleases&style=for-the-badge&color=36B2FF)

[![download](https://img.shields.io/badge/playstore-download-brightgreen?style=social&logo=googleplay&color=36B2FF)](https://play.google.com/store/apps/details?id=com.teumteum.teumteum&pcampaignid=web_share) [![instagram](https://img.shields.io/badge/instagram-click-brightgreen?style=social&logo=instagram&color=36B2FF)](https://www.instagram.com/teumteum_official/) [![behance](https://img.shields.io/badge/behance-click-brightgreen?style=social&logo=behance&color=36B2FF)](https://www.behance.net/gallery/191510163/%08TEUMTEUM-IT-Career-Growth-Networking-Service)

---

<p align="center" width="100%">
<img width="49%" alt="MOBILE_1" src="https://github.com/depromeet/teum-teum-server/assets/91249216/c8c97700-b1a0-4393-ab14-edcf077627cb">
<img width="49%" alt="MOBILE_2" src="https://github.com/depromeet/teum-teum-server/assets/91249216/52a606d7-77dd-48fc-9f2f-3bb1a130ad75">
</p>
<p align="center" width="100%">
<img width="49%" alt="MOBILE_3" src="https://github.com/depromeet/teum-teum-server/assets/91249216/015f52bb-7e76-45f5-ba8f-aa9d92d133dd">
<img width="49%" alt="MOBILE_4" src="https://github.com/depromeet/teum-teum-server/assets/91249216/589184e0-bd3d-4583-9315-2b1ad3c624ad">
</p>
<p align="center" width="100%">
<img width="49%" alt="MOBILE_5" src="https://github.com/depromeet/teum-teum-server/assets/91249216/3dd859ef-ed72-45c7-8f82-6c5a92f33fa5">
<img width="49%" alt="MOBILE_6" src="https://github.com/depromeet/teum-teum-server/assets/91249216/642bff83-30ae-4ff6-aa01-53561ce00db3">
</p>
<p align="center" width="100%">
<img width="49%" alt="MOBILE_7" src="https://github.com/depromeet/teum-teum-server/assets/91249216/7d66866d-b07f-4ebf-8699-feffa8c6b8fd">
<img width="49%" alt="MOBILE_8" src="https://github.com/depromeet/teum-teum-server/assets/91249216/5aa5c9e9-6e02-43b0-a2e3-81f6aff298b5">
</p>

## 🌐 Architecture

<p align="center" width="100%">
<img width="80%" src="https://github.com/depromeet/teum-teum-server/assets/91249216/5ffde24a-fb5d-4b85-a71d-e108a3254451">
</p>

## ⚒️ Tech Stack

<img src="https://img.shields.io/badge/Framework-555555?style=for-the-badge">![SpringBoot](https://img.shields.io/badge/springboot-%236DB33F.svg?style=for-the-badge&logo=springboot&logoColor=white)![spring_data_JPA](https://img.shields.io/badge/spring_data_JPA-%236DB33F?style=for-the-badge&logo=databricks&logoColor=white)![SpringSecurity](https://img.shields.io/badge/spring_security-%236DB33F.svg?style=for-the-badge&logo=springsecurity&logoColor=white) <img src="https://img.shields.io/badge/build-555555?style=for-the-badge">![Gradle](https://img.shields.io/badge/Gradle-02303A.svg?style=for-the-badge&logo=Gradle&logoColor=white)

<img src="https://img.shields.io/badge/Test-555555?style=for-the-badge">![junit5](https://img.shields.io/badge/junit5-25A162?style=for-the-badge&logo=junit5&logoColor=white)![test_containers](https://img.shields.io/badge/test_containers-328ba3?style=for-the-badge&logo=reasonstudios&logoColor=white) <img src="https://img.shields.io/badge/performance-555555?style=for-the-badge">![gatling](https://img.shields.io/badge/gatling-FF9E2A?style=for-the-badge&logo=gatling&logoColor=white)

<img src="https://img.shields.io/badge/Database-555555?style=for-the-badge">![MySQL](https://img.shields.io/badge/mysql-4479A1.svg?style=for-the-badge&logo=mysql&logoColor=white)![Redis](https://img.shields.io/badge/redis-%23DD0031.svg?style=for-the-badge&logo=redis&logoColor=white)![Firebase](https://img.shields.io/badge/Firebase-039BE5?style=for-the-badge&logo=Firebase&logoColor=white) <img src="https://img.shields.io/badge/migration-555555?style=for-the-badge">![flyway](https://img.shields.io/badge/flyway-CC0200?style=for-the-badge&logo=flyway&logoColor=white)

<img src="https://img.shields.io/badge/Infrastructure-555555?style=for-the-badge">![Amazon Ec2](https://img.shields.io/badge/amazon_ec2-FF9900.svg?style=for-the-badge&logo=amazonec2&logoColor=white)![Amazon S3](https://img.shields.io/badge/AWS_S3-569A31.svg?style=for-the-badge&logo=amazons3&logoColor=white)![Amazon RDS](https://img.shields.io/badge/amazon_RDS-527FFF.svg?style=for-the-badge&logo=amazonrds&logoColor=white)![Amazon ElastiCache](https://img.shields.io/badge/amazon_elasticache-FF9900.svg?style=for-the-badge&logo=amazondocumentdb&logoColor=white)![Nginx](https://img.shields.io/badge/nginx-%23009639.svg?style=for-the-badge&logo=nginx&logoColor=white)

<img src="https://img.shields.io/badge/CICD-555555?style=for-the-badge">![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)![GitHub Actions](https://img.shields.io/badge/github%20actions-%232671E5.svg?style=for-the-badge&logo=githubactions&logoColor=white)![github container](https://img.shields.io/badge/github_container-181717.svg?style=for-the-badge&logo=github&logoColor=white) <img src="https://img.shields.io/badge/domain-555555?style=for-the-badge">![squarespace](https://img.shields.io/badge/squarespace-000000?style=for-the-badge&logo=squarespace&logoColor=white)

<img src="https://img.shields.io/badge/Code_Analysis-555555?style=for-the-badge">![SonarCloud](https://img.shields.io/badge/SonarCloud-F3702A?style=for-the-badge&logo=SonarCloud&logoColor=white) <img src="https://img.shields.io/badge/Monitering-555555?style=for-the-badge">![sentry](https://img.shields.io/badge/sentry-362D59?style=for-the-badge&logo=sentry&logoColor=white) <img src="https://img.shields.io/badge/API-555555?style=for-the-badge">![ChatGPT](https://img.shields.io/badge/chatGPT-74aa9c?style=for-the-badge&logo=openai&logoColor=white)

## 👥 Contributors

| Server | Server | Server |
|:----------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------:|
| <img src="https://github.com/depromeet/teum-teum-server/assets/91249216/cb77a10c-09f8-42bc-94b8-23c89337fef3" width="160" height="160"/> | <img src="https://github.com/depromeet/teum-teum-server/assets/91249216/e809343c-129c-45bd-99c7-61804955338e" width="160" height="160"/> | <img src="https://github.com/depromeet/teum-teum-server/assets/91249216/1074cb99-888d-4429-a9e9-c37f95dab722" width="160" height="160"/> |
| [choidongkuen](https://github.com/choidongkuen) | [xb205](https://github.com/devxb) | [ddingmin](https://github.com/ddingmin) |
4 changes: 2 additions & 2 deletions src/main/java/net/teumteum/alert/app/AlertHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void handleBeforeMeetingAlerts(BeforeMeetingAlerted alerted) {

@Async(ALERT_EXECUTOR)
@EventListener(EndMeetingAlerted.class)
public void handleEndMeetingAlerts(EndMeetingAlerted alerted) {
public void handleStartMeetingAlerts(EndMeetingAlerted alerted) {
userAlertService.findAllByUserId(alerted.userIds())
.stream()
.map(userAlert -> Pair.of(userAlert.getToken(),
Expand All @@ -64,7 +64,7 @@ private String toCommaString(List<Long> ids) {
for (int i = 0; i < ids.size() - 1; i++) {
stringBuilder.append(ids.get(i)).append(",");
}
stringBuilder.append(ids.get(ids.size() - 1));
stringBuilder.append(ids.getLast());
return stringBuilder.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private void saveUserAuthentication(User user) {

private String resolveTokenFromRequest(HttpServletRequest request) {
String token = request.getHeader(jwtProperty.getAccess().getHeader());
if (token.toLowerCase().startsWith(jwtProperty.getBearer().toLowerCase())) {
if (StringUtils.hasText(token) && token.toLowerCase().startsWith(jwtProperty.getBearer().toLowerCase())) {
return token.substring(7);
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import lombok.RequiredArgsConstructor;
import net.teumteum.meeting.domain.BeforeMeetingAlerted;
import net.teumteum.meeting.domain.EndMeetingAlerted;
Expand Down Expand Up @@ -49,4 +48,21 @@ public void alertEndMeeting() {
new EndMeetingAlerted(meeting.getId(), meeting.getTitle(), meeting.getParticipantUserIds())
));
}

@Scheduled(cron = EVERY_ONE_MINUTES)
public void alertEndMeetingForQa() {
var today = LocalDateTime.now(ZoneId.of("Asia/Seoul"))
.withNano(0)
.withSecond(0)
.withMinute(0)
.withHour(0);

var future = today.plusDays(365);
var yesterday = today.minusDays(365);

var alertTargets = meetingRepository.findAlertMeetings(yesterday, future);
alertTargets.forEach(meeting -> eventPublisher.publishEvent(
new EndMeetingAlerted(meeting.getId(), meeting.getTitle(), meeting.getParticipantUserIds())
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ public void registerReview(
userService.registerReview(meetingId, getCurrentUserId(), request);
}

@GetMapping("/reviews")
@GetMapping("/{userId}/reviews")
@ResponseStatus(HttpStatus.OK)
public List<UserReviewsResponse> getUserReviews() {
return userService.getUserReviews(getCurrentUserId());
public UserReviewsResponse getUserReviews(@PathVariable("userId") Long userId) {
return userService.getUserReviews(userId);
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/net/teumteum/user/domain/UserRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.List;
import java.util.Optional;
import net.teumteum.core.security.Authenticated;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.domain.response.UserReviewResponse;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -15,7 +15,8 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByAuthenticatedAndOAuthId(@Param("authenticated") Authenticated authenticated,
@Param("oAuthId") String oAuthId);

@Query("select new net.teumteum.user.domain.response.UserReviewsResponse(r,count(r)) "
+ "from users u join u.reviews r where u.id = :userId group by r")
List<UserReviewsResponse> countUserReviewsByUserId(@Param("userId") Long userId);
@Query("select new net.teumteum.user.domain.response.UserReviewResponse(r, count(r)) "
+ "from users u join u.reviews r where u = :user group by r")
List<UserReviewResponse> countUserReviewsByUser(@Param("user") User user);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.teumteum.user.domain.response;

import net.teumteum.user.domain.Review;

public record UserReviewResponse(
Review review,
long count
) {

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package net.teumteum.user.domain.response;

import net.teumteum.user.domain.Review;
import java.util.List;

public record UserReviewsResponse(
Review review,
long count
List<UserReviewResponse> reviews
) {

public static UserReviewsResponse of(List<UserReviewResponse> reviews) {
return new UserReviewsResponse(reviews);
}
}
6 changes: 4 additions & 2 deletions src/main/java/net/teumteum/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ public void registerReview(Long meetingId, Long currentUserId, ReviewRegisterReq
});
}

public List<UserReviewsResponse> getUserReviews(Long userId) {
return userRepository.countUserReviewsByUserId(userId);
public UserReviewsResponse getUserReviews(Long userId) {
var user = getUser(userId);

return UserReviewsResponse.of(userRepository.countUserReviewsByUser(user));
}

public FriendsResponse findFriendsByUserId(Long userId) {
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/net/teumteum/integration/Api.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,10 @@ ResponseSpec deleteMeeting(String accessToken, Long meetingId) {
.exchange();
}

ResponseSpec getUserReviews(String accessToken) {
ResponseSpec getUserReviews(Long userId, String accessToken) {
return webTestClient
.get()
.uri("/users/reviews")
.uri("/users/" + userId + "/reviews")
.header(HttpHeaders.AUTHORIZATION, accessToken)
.exchange();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,12 @@ void Get_user_reviews() {
// given
var existUser = repository.saveAndGetUser();

var userId = existUser.getId();

securityContextSetting.set(existUser.getId());

// when
var expected = api.getUserReviews(VALID_TOKEN);
var expected = api.getUserReviews(userId, VALID_TOKEN);

// then
Assertions.assertThat(expected.expectStatus().isOk()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import net.teumteum.user.domain.request.UserRegisterRequest;
import net.teumteum.user.domain.request.UserWithdrawRequest;
import net.teumteum.user.domain.response.UserRegisterResponse;
import net.teumteum.user.domain.response.UserReviewResponse;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.service.UserService;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -238,27 +239,25 @@ void Register_reviews_with_400_bad_request() throws Exception {
class Get_user_reviews_api_unit {

@Test
@DisplayName("로그인한 회원 id 에 해당하는 회원 리뷰와 200 OK을 반환한다.")
@DisplayName("user id 에 해당하는 회원 리뷰와 200 OK을 반환한다.")
void Get_user_reviews_with_200_ok() throws Exception {
// given
var userId = 1L;

given(securityService.getCurrentUserId()).willReturn(userId);

given(userService.getUserReviews(anyLong()))
.willReturn(List.of(new UserReviewsResponse(별로에요, 2L),
new UserReviewsResponse(최고에요, 3L)));
.willReturn(UserReviewsResponse.of(List.of(new UserReviewResponse(별로에요, 2L),
new UserReviewResponse(최고에요, 3L))));

// when & then
mockMvc.perform(get("/users/reviews")
mockMvc.perform(get("/users/{userId}/reviews", 1)
.with(csrf())
.header(AUTHORIZATION, VALID_ACCESS_TOKEN))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$", notNullValue()))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].count", is(2)))
.andExpect(jsonPath("$[0].review").value("별로에요"));
.andExpect(jsonPath("$.reviews", notNullValue()))
.andExpect(jsonPath("$.reviews", hasSize(2)))
.andExpect(jsonPath("$.reviews[0].count", is(2)))
.andExpect(jsonPath("$.reviews[0].review").value("별로에요"));
}
}

Expand Down
21 changes: 11 additions & 10 deletions src/test/java/net/teumteum/unit/user/service/UserServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import net.teumteum.user.domain.request.UserRegisterRequest;
import net.teumteum.user.domain.request.UserWithdrawRequest;
import net.teumteum.user.domain.response.UserRegisterResponse;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.domain.response.UserReviewResponse;
import net.teumteum.user.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -251,23 +251,24 @@ class Get_user_reviews_api_unit {
void Return_user_reviews_with_200_ok() {
// given
var userId = 1L;
var existUser = UserFixture.getIdUser();

var response = List.of(new UserReviewsResponse(최고에요, 2L)
, new UserReviewsResponse(별로에요, 3L));
var response = List.of(new UserReviewResponse(최고에요, 2L)
, new UserReviewResponse(별로에요, 3L));

given(userRepository.countUserReviewsByUserId(anyLong())).willReturn(response);
given(userRepository.findById(anyLong())).willReturn(Optional.of(existUser));
given(userRepository.countUserReviewsByUser(any(User.class))).willReturn(response);

// when
var result = userService.getUserReviews(userId);

// then
assertThat(result).hasSize(2);
assertThat(result.get(0).review()).isEqualTo(최고에요);
assertThat(result.get(0).count()).isEqualTo(2L);
assertThat(result.get(1).review()).isEqualTo(별로에요);
assertThat(result.get(1).count()).isEqualTo(3L);
assertThat(result.reviews()).hasSize(2);
assertThat(result.reviews().get(0).review()).isEqualTo(최고에요);
assertThat(result.reviews().get(0).count()).isEqualTo(2L);
assertThat(result.reviews().get(1).review()).isEqualTo(별로에요);

verify(userRepository, times(1)).countUserReviewsByUserId(anyLong());
verify(userRepository, times(1)).countUserReviewsByUser(any(User.class));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import jakarta.persistence.EntityManager;
import java.util.Optional;
import net.teumteum.core.config.AppConfig;
import net.teumteum.user.domain.response.UserReviewsResponse;
import net.teumteum.user.domain.response.UserReviewResponse;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
Expand Down Expand Up @@ -85,13 +85,14 @@ void Count_user_reviews_by_user_id() {
entityManager.clear();

// when
var result = userRepository.countUserReviewsByUserId(id);
var result = userRepository.countUserReviewsByUser(existUser);

// then
Assertions.assertThat(result)
.isNotEmpty()
.hasSize(3)
.extracting(UserReviewsResponse::review, UserReviewsResponse::count)
.extracting(UserReviewResponse::review,
UserReviewResponse::count)
.contains(
Assertions.tuple(최고에요, 3L),
Assertions.tuple(별로에요, 1L),
Expand Down
Loading