Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package app.batch.bottlenote;

import app.bottlenote.support.report.service.DailyDataReportService;
import app.external.version.config.AppInfoConfig;
import app.external.webhook.config.WebhookConfig;
import java.util.TimeZone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

@EntityScan(basePackages = "app")
@SpringBootApplication(scanBasePackages = "app")
@ComponentScan(basePackages = "app")
@SpringBootApplication(scanBasePackages = "app.batch.bottlenote")
@Import({DailyDataReportService.class, WebhookConfig.class, AppInfoConfig.class})
public class BatchApplication {
public static void main(String[] args) {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package app.batch.bottlenote;

import static org.assertj.core.api.Assertions.assertThat;

import app.batch.bottlenote.job.ranking.BestReviewSelectionJobConfig.BestReviewQuartzJob;
import app.batch.bottlenote.job.ranking.PopularAlcoholSelectionJobConfig.PopularAlcoholQuartzJob;
import app.batch.bottlenote.job.report.DailyDataReportJobConfig.DailyDataReportQuartzJob;
import app.bottlenote.review.service.ReviewService;
import app.bottlenote.support.report.service.DailyDataReportService;
import app.bottlenote.support.report.service.ReviewReportService;
import app.bottlenote.support.report.service.UserReportService;
import app.bottlenote.user.service.AdminUserService;
import app.bottlenote.user.service.OauthService;
import app.external.version.config.AppInfoConfig;
import app.external.webhook.config.DiscordWebhookProperties;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.Job;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.web.client.RestTemplate;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

@Tag("batch")
@Testcontainers
@SpringBootTest(classes = BatchApplication.class)
@DisplayName("[batch] BatchApplication context")
class BatchApplicationContextTest {

@Container
static final MySQLContainer<?> MYSQL =
new MySQLContainer<>(DockerImageName.parse("mysql:8.0.32"))
.withDatabaseName("bottlenote_batch")
.withUsername("root")
.withPassword("root");

@Autowired private ApplicationContext context;

@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", MYSQL::getJdbcUrl);
registry.add("spring.datasource.username", MYSQL::getUsername);
registry.add("spring.datasource.password", MYSQL::getPassword);
registry.add("spring.datasource.driver-class-name", MYSQL::getDriverClassName);
registry.add("spring.jpa.hibernate.ddl-auto", () -> "none");
registry.add("spring.quartz.jdbc.initialize-schema", () -> "always");
registry.add("spring.batch.jdbc.initialize-schema", () -> "always");
registry.add("webhook.discord.url", () -> "https://discord.test.webhook.url");
}

@Test
@DisplayName("배치에 필요한 Job과 Quartz binding을 로드한다")
void contextLoadsBatchJobsAndQuartzBindings() {
assertThat(context.getBean("bestReviewSelectedJob", Job.class)).isNotNull();
assertThat(context.getBean("popularAlcoholJob", Job.class)).isNotNull();
assertThat(context.getBean("dailyDataReportJob", Job.class)).isNotNull();

assertThat(context.getBean(BestReviewQuartzJob.class)).isNotNull();
assertThat(context.getBean(PopularAlcoholQuartzJob.class)).isNotNull();
assertThat(context.getBean(DailyDataReportQuartzJob.class)).isNotNull();
}

@Test
@DisplayName("일일 리포트에 필요한 mono/external bean wiring을 유지한다")
void contextKeepsDailyReportDependencies() {
assertThat(context.getBean(DailyDataReportService.class)).isNotNull();
assertThat(context.getBean("webhookRestTemplate", RestTemplate.class)).isNotNull();
assertThat(context.getBean(DiscordWebhookProperties.class).getUrl())
.isEqualTo("https://discord.test.webhook.url");
assertThat(context.getBean(AppInfoConfig.class).getEnvironment()).isEqualTo("test");
}

@Test
@DisplayName("batch와 무관한 product/admin 성격의 mono bean은 로드하지 않는다")
void contextDoesNotLoadUnnecessaryMonoBeans() {
assertThat(context.getBeansOfType(OauthService.class)).isEmpty();
assertThat(context.getBeansOfType(AdminUserService.class)).isEmpty();
assertThat(context.getBeansOfType(ReviewService.class)).isEmpty();
assertThat(context.getBeansOfType(UserReportService.class)).isEmpty();
assertThat(context.getBeansOfType(ReviewReportService.class)).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private Optional<String> resolveToken(HttpServletRequest request) {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
List<String> excludePath = List.of("login", "guest-login", "regions", "alcohols/categories");
List<String> excludePath = List.of("login", "regions", "alcohols/categories");

return excludePath.stream().anyMatch(path::contains);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,6 @@ public String createAccessToken(String userEmail, UserType role, Long userId) {
.compact();
}

public String createGuestToken(Long userId, int expireTime) {
Claims claims = Jwts.claims();
claims.put("userId", userId);
claims.put(KEY_ROLES, UserType.ROLE_GUEST.name());
Date now = new Date();
return Jwts.builder()
.setClaims(claims)
.setSubject("guest")
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + expireTime))
.signWith(secretKey, SignatureAlgorithm.HS512)
.compact();
}

/**
* 필수적인 파라미터를 받아 리프레시 토큰을 생성하는 메소드
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@
public class OauthConfigProperties {
private String refreshTokenHeaderPrefix;
private int cookieExpireTime;
private String guestCode;

public void printConfigs() {
log.info("refreshTokenHeaderPrefix: {}", refreshTokenHeaderPrefix);
log.info("cookieExpireTime: {}", cookieExpireTime);
log.info("guestCode: {}", guestCode);
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@ Optional<User> findByEmailAndSocialType(
@Query("select u from users u order by u.id limit 1")
Optional<User> getFirstUser();

@Query(
"""
SELECT u
FROM users u
WHERE u.role = 'ROLE_GUEST'
ORDER BY u.id
LIMIT 1
""")
Optional<User> loadGuestUser();

@Query("select count (u)+1 from users u")
String getNextNicknameSequence();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
import app.bottlenote.global.security.jwt.JwtAuthenticationManager;
import app.bottlenote.global.security.jwt.JwtTokenProvider;
import app.bottlenote.global.security.jwt.TokenValidator;
import app.bottlenote.user.constant.GenderType;
import app.bottlenote.user.constant.SocialType;
import app.bottlenote.user.constant.UserType;
import app.bottlenote.user.domain.User;
import app.bottlenote.user.dto.request.OauthRequest;
import app.bottlenote.user.dto.response.BasicAccountResponse;
import app.bottlenote.user.dto.response.TokenItem;
import app.bottlenote.user.exception.UserException;
import app.bottlenote.user.exception.UserExceptionCode;
Expand Down Expand Up @@ -156,23 +154,6 @@ public void restoreUser(String email, String password) {
user.restore();
}

@Transactional
public String guestLogin() {
final int expireTime = 1000 * 60 * 60 * 24;
OauthRequest oauthRequest =
new OauthRequest(
"guest" + UUID.randomUUID() + "@bottlenote.com",
"socialUniqueId" + UUID.randomUUID(),
SocialType.APPLE,
GenderType.MALE,
30);
User guest =
oauthRepository
.loadGuestUser()
.orElseGet(() -> oauthSignUp(oauthRequest, UserType.ROLE_GUEST));
return tokenProvider.createGuestToken(guest.getId(), expireTime);
}

private User oauthSignUp(OauthRequest oauthRequest, UserType userType) {

String socialUniqueId = oauthRequest.socialUniqueId();
Expand Down Expand Up @@ -228,64 +209,6 @@ public TokenItem refresh(String refreshToken) {
return reissuedToken;
}

@Transactional
public BasicAccountResponse basicSignup(
String email, String password, Integer age, GenderType gender) {
oauthRepository
.findByEmail(email)
.ifPresent(
user -> {
throw new UserException(UserExceptionCode.USER_ALREADY_EXISTS);
});

String encodePassword = passwordEncoder.encode(password);
User user =
oauthRepository.save(
User.builder()
.email(email)
.password(encodePassword)
.role(UserType.ROLE_USER)
.socialType(List.of(SocialType.BASIC))
.nickName(generateNickname())
.age(age)
.gender(gender)
.build());

TokenItem token = tokenProvider.generateToken(user.getEmail(), user.getRole(), user.getId());
user.updateRefreshToken(token.refreshToken());

log.info(
"기본 회원가입 완료: email={}, userId={}, nickname={}",
user.getEmail(),
user.getId(),
user.getNickName());

return BasicAccountResponse.builder()
.message(user.getNickName() + "님 환영합니다!")
.email(user.getEmail())
.nickname(user.getNickName())
.accessToken(token.accessToken())
.refreshToken(token.refreshToken())
.build();
}

@Transactional
public TokenItem basicLogin(String email, String password) {
User user =
oauthRepository
.findByEmail(email)
.orElseThrow(() -> new UserException(UserExceptionCode.USER_NOT_FOUND));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new UserException(UserExceptionCode.INVALID_PASSWORD);
}

checkActiveUser(user);

TokenItem token = tokenProvider.generateToken(user.getEmail(), user.getRole(), user.getId());
user.updateRefreshToken(token.refreshToken());
return token;
}

protected String generateNickname() {
List<String> a =
Arrays.asList("부드러운", "향기로운", "숙성된", "풍부한", "깊은", "황금빛", "오크향의", "스모키한", "달콤한", "강렬한");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ public class TestConfigProperties {
@Bean
public OauthConfigProperties oauthConfigProperties() {
return OauthConfigProperties.builder()
.guestCode("guest-code-for-test")
.cookieExpireTime(100000)
.refreshTokenHeaderPrefix("refresh-token")
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ GET /api/v1/follow/{userId}/following-list
[discrete]
==== 요청 파라미터 ====

include::{snippets}/follow/search/query-parameters.adoc[]
include::{snippets}/follow/follower-list/query-parameters.adoc[]

[discrete]
==== 응답 파라미터 ====

include::{snippets}/follow/search/response-fields.adoc[]
include::{snippets}/follow/search/response-body.adoc[]

include::{snippets}/follow/follower-list/response-fields.adoc[]
include::{snippets}/follow/follower-list/response-body.adoc[]

Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ GET /api/v1/follow/{userId}/follower-list
[discrete]
==== 요청 파라미터 ====

include::{snippets}/follow/search/query-parameters.adoc[]
include::{snippets}/follow/following-list/query-parameters.adoc[]

[discrete]
==== 응답 파라미터 ====

include::{snippets}/follow/search/response-fields.adoc[]
include::{snippets}/follow/search/response-body.adoc[]

include::{snippets}/follow/following-list/response-fields.adoc[]
include::{snippets}/follow/following-list/response-body.adoc[]

Loading
Loading