Skip to content

Commit

Permalink
Merge pull request #611 from woowacourse-teams/develop
Browse files Browse the repository at this point in the history
Hang-Log Prod 1.1.0 Release
  • Loading branch information
jjongwa authored Sep 20, 2023
2 parents 349cd86 + 2c7671e commit 2ca7ffe
Show file tree
Hide file tree
Showing 183 changed files with 2,563 additions and 823 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/frontend-e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ jobs:
uses: cypress-io/github-action@v5
with:
browser: chrome
start: npm run dev
wait-on: 'http://localhost:3000'
start: npm run serve:dev
wait-on: "http://localhost:3000"
record: false
working-directory: ./frontend
env:
Expand Down
2 changes: 1 addition & 1 deletion backend/backend-submodule
4 changes: 4 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ dependencies {

implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'


implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'
}

test {
Expand Down
10 changes: 10 additions & 0 deletions backend/src/docs/asciidoc/docs.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,13 @@ include::{snippets}/shared-trip-controller-test/update-shared-status/path-parame
==== 응답
include::{snippets}/shared-trip-controller-test/update-shared-status/http-response.adoc[]
include::{snippets}/shared-trip-controller-test/update-shared-status/path-parameters.adoc[]

=== 공유된 여행 경비 조회 (GET /shared-trips/:shareCode/expense)

==== 요청
include::{snippets}/shared-trip-controller-test/get-shared-expenses/http-request.adoc[]
include::{snippets}/shared-trip-controller-test/get-shared-expenses/path-parameters.adoc[]

==== 응답
include::{snippets}/shared-trip-controller-test/get-shared-expenses/http-response.adoc[]
include::{snippets}/shared-trip-controller-test/get-shared-expenses/response-fields.adoc[]
2 changes: 0 additions & 2 deletions backend/src/main/java/hanglog/auth/Auth.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Auth {

boolean required() default true;
}
49 changes: 39 additions & 10 deletions backend/src/main/java/hanglog/auth/AuthArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package hanglog.auth;

import static hanglog.global.exception.ExceptionCode.NULL_REFRESH_TOKEN;
import static hanglog.global.exception.ExceptionCode.INVALID_REQUEST;
import static hanglog.global.exception.ExceptionCode.NOT_FOUND_REFRESH_TOKEN;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;

import hanglog.auth.domain.Accessor;
import hanglog.auth.domain.BearerAuthorizationExtractor;
import hanglog.auth.domain.JwtProvider;
import hanglog.auth.domain.MemberTokens;
import hanglog.global.exception.AuthException;
import hanglog.auth.domain.repository.RefreshTokenRepository;
import hanglog.global.exception.BadRequestException;
import hanglog.global.exception.RefreshTokenException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
Expand All @@ -27,28 +32,52 @@ public class AuthArgumentResolver implements HandlerMethodArgumentResolver {

private final BearerAuthorizationExtractor extractor;

private final RefreshTokenRepository refreshTokenRepository;

@Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.withContainingClass(Long.class)
.hasParameterAnnotation(Auth.class);
}

@Override
public Long resolveArgument(
public Accessor resolveArgument(
final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest,
final WebDataBinderFactory binderFactory
) throws Exception {
) {
final HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
final String refreshToken = Arrays.stream(request.getCookies())
.filter(cookie -> REFRESH_TOKEN.equals(cookie.getName()))
if (request == null) {
throw new BadRequestException(INVALID_REQUEST);
}

try {
final String refreshToken = extractRefreshToken(request.getCookies());
final String accessToken = extractor.extractAccessToken(webRequest.getHeader(AUTHORIZATION));
jwtProvider.validateTokens(new MemberTokens(refreshToken, accessToken));

final Long memberId = Long.valueOf(jwtProvider.getSubject(accessToken));
return Accessor.member(memberId);
} catch (final RefreshTokenException e) {
return Accessor.guest();
}
}

private String extractRefreshToken(final Cookie... cookies) {
if (cookies == null) {
throw new RefreshTokenException(NOT_FOUND_REFRESH_TOKEN);
}
return Arrays.stream(cookies)
.filter(this::isValidRefreshToken)
.findFirst()
.orElseThrow(() -> new AuthException(NULL_REFRESH_TOKEN))
.orElseThrow(() -> new RefreshTokenException(NOT_FOUND_REFRESH_TOKEN))
.getValue();
}

final String accessToken = extractor.extractAccessToken(webRequest.getHeader(AUTHORIZATION));
jwtProvider.validateTokens(new MemberTokens(refreshToken, accessToken));
return Long.valueOf(jwtProvider.getSubject(accessToken));
private boolean isValidRefreshToken(final Cookie cookie) {
// TODO: refreshToken 만료 기한 검사 필요
return REFRESH_TOKEN.equals(cookie.getName()) &&
refreshTokenRepository.existsByToken(cookie.getValue());
}
}
12 changes: 12 additions & 0 deletions backend/src/main/java/hanglog/auth/MemberOnly.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package hanglog.auth;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(METHOD)
@Retention(RUNTIME)
public @interface MemberOnly {
}
26 changes: 26 additions & 0 deletions backend/src/main/java/hanglog/auth/MemberOnlyChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package hanglog.auth;

import static hanglog.global.exception.ExceptionCode.INVALID_AUTHORITY;

import hanglog.auth.domain.Accessor;
import hanglog.global.exception.AuthException;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MemberOnlyChecker {

@Before("@annotation(hanglog.auth.MemberOnly)")
public void check(final JoinPoint joinPoint) {
Arrays.stream(joinPoint.getArgs())
.filter(Accessor.class::isInstance)
.map(Accessor.class::cast)
.filter(Accessor::isMember)
.findFirst()
.orElseThrow(() -> new AuthException(INVALID_AUTHORITY));
}
}
29 changes: 29 additions & 0 deletions backend/src/main/java/hanglog/auth/domain/Accessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package hanglog.auth.domain;

import static hanglog.auth.domain.Authority.MEMBER;

import lombok.Getter;

@Getter
public class Accessor {

private final Long memberId;
private final Authority authority;

private Accessor(final Long memberId, final Authority authority) {
this.memberId = memberId;
this.authority = authority;
}

public static Accessor guest() {
return new Accessor(0L, Authority.GUEST);
}

public static Accessor member(final Long memberId) {
return new Accessor(memberId, MEMBER);
}

public boolean isMember() {
return MEMBER.equals(authority);
}
}
5 changes: 5 additions & 0 deletions backend/src/main/java/hanglog/auth/domain/Authority.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package hanglog.auth.domain;

public enum Authority {
GUEST, MEMBER
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class RefreshToken {
@Id
private String token;

@Column(nullable = false, unique = true)
@Column(nullable = false)
private Long memberId;

public RefreshToken(final String token, final Long memberId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {

Optional<RefreshToken> findByToken(final String token);

boolean existsByToken(final String token);

void deleteByMemberId(final Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import static org.springframework.http.HttpStatus.CREATED;

import hanglog.auth.Auth;
import hanglog.auth.MemberOnly;
import hanglog.auth.domain.Accessor;
import hanglog.auth.domain.MemberTokens;
import hanglog.auth.dto.AccessTokenResponse;
import hanglog.auth.dto.LoginRequest;
Expand Down Expand Up @@ -56,14 +58,16 @@ public ResponseEntity<AccessTokenResponse> extendLogin(
}

@DeleteMapping("/logout")
public ResponseEntity<Void> logout(@Auth final Long memberId) {
authService.removeMemberRefreshToken(memberId);
@MemberOnly
public ResponseEntity<Void> logout(@Auth final Accessor accessor) {
authService.removeMemberRefreshToken(accessor.getMemberId());
return ResponseEntity.noContent().build();
}

@DeleteMapping("/account")
public ResponseEntity<Void> deleteAccount(@Auth final Long memberId) {
authService.deleteAccount(memberId);
@MemberOnly
public ResponseEntity<Void> deleteAccount(@Auth final Accessor accessor) {
authService.deleteAccount(accessor.getMemberId());
return ResponseEntity.noContent().build();
}
}
27 changes: 23 additions & 4 deletions backend/src/main/java/hanglog/auth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package hanglog.auth.service;

import static hanglog.global.exception.ExceptionCode.FAIL_TO_VALIDATE_TOKEN;
import static hanglog.global.exception.ExceptionCode.INVALID_REFRESH_TOKEN;

import hanglog.auth.domain.BearerAuthorizationExtractor;
import hanglog.auth.domain.JwtProvider;
import hanglog.auth.domain.MemberTokens;
Expand All @@ -19,11 +16,16 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static hanglog.global.exception.ExceptionCode.*;

@Service
@Transactional
@RequiredArgsConstructor
public class AuthService {

private static final int MAX_TRY_COUNT = 5;
private static final int FOUR_DIGIT_RANGE = 10000;

private final MemberRepository memberRepository;
private final OauthProviders oauthProviders;
private final RefreshTokenRepository refreshTokenRepository;
Expand All @@ -47,7 +49,24 @@ public MemberTokens login(final String providerName, final String code) {

private Member findOrCreateMember(final String socialLoginId, final String nickname, final String imageUrl) {
return memberRepository.findBySocialLoginId(socialLoginId)
.orElseGet(() -> memberRepository.save(new Member(socialLoginId, nickname, imageUrl)));
.orElseGet(() -> createMember(socialLoginId, nickname, imageUrl));
}

private Member createMember(final String socialLoginId, final String nickname, final String imageUrl) {
int tryCount = 0;
while (tryCount < MAX_TRY_COUNT) {
final String nicknameWithRandomNumber = nickname + generateRandomFourDigitCode();
if (!memberRepository.existsByNickname(nicknameWithRandomNumber)) {
return memberRepository.save(new Member(socialLoginId, nicknameWithRandomNumber, imageUrl));
}
tryCount += 1;
}
throw new AuthException(FAIL_TO_GENERATE_RANDOM_NICKNAME);
}

public String generateRandomFourDigitCode() {
final int randomNumber = (int) (Math.random() * FOUR_DIGIT_RANGE);
return String.format("%04d", randomNumber);
}

public String renewalAccessToken(final String refreshTokenRequest, final String authorizationHeader) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package hanglog.trip.domain;
package hanglog.city.domain;

import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package hanglog.trip.domain.repository;
package hanglog.city.domain.repository;

import hanglog.trip.domain.City;
import hanglog.city.domain.City;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CityRepository extends JpaRepository<City, Long> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import static lombok.AccessLevel.PRIVATE;

import hanglog.trip.domain.City;
import hanglog.city.domain.City;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package hanglog.trip.dto.response;
package hanglog.city.dto.response;

import static lombok.AccessLevel.PRIVATE;

import hanglog.trip.domain.City;
import hanglog.city.domain.City;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package hanglog.trip.presentation;
package hanglog.city.presentation;

import hanglog.city.dto.response.CityResponse;
import hanglog.trip.service.CityService;
import hanglog.city.service.CityService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package hanglog.trip.service;
package hanglog.city.service;

import hanglog.city.dto.response.CityResponse;
import hanglog.trip.domain.City;
import hanglog.trip.domain.repository.CityRepository;
import hanglog.city.domain.City;
import hanglog.city.domain.repository.CityRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package hanglog.expense.presentation;

import hanglog.auth.Auth;
import hanglog.auth.MemberOnly;
import hanglog.auth.domain.Accessor;
import hanglog.expense.dto.response.TripExpenseResponse;
import hanglog.expense.service.ExpenseService;
import hanglog.trip.service.TripService;
Expand All @@ -20,8 +22,12 @@ public class ExpenseController {
private final TripService tripService;

@GetMapping
public ResponseEntity<TripExpenseResponse> getExpenses(@Auth final Long memberId, @PathVariable final Long tripId) {
tripService.validateTripByMember(memberId, tripId);
@MemberOnly
public ResponseEntity<TripExpenseResponse> getExpenses(
@Auth final Accessor accessor,
@PathVariable final Long tripId
) {
tripService.validateTripByMember(accessor.getMemberId(), tripId);
final TripExpenseResponse tripExpenseResponse = expenseService.getAllExpenses(tripId);
return ResponseEntity.ok().body(tripExpenseResponse);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public TripExpenseResponse getAllExpenses(final Long tripId) {

final List<CategoryExpense> categoryExpenses = categoryAmounts.entrySet().stream()
.map(entry -> new CategoryExpense(entry.getKey(), entry.getValue(), totalAmount))
.sorted((o1,o2) -> o2.getAmount().compareTo(o1.getAmount()))
.toList();

final List<DayLogExpense> dayLogExpenses = dayLogAmounts.entrySet().stream()
Expand Down
Loading

0 comments on commit 2ca7ffe

Please sign in to comment.