Skip to content
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
6 changes: 6 additions & 0 deletions .github/workflows/cd-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ jobs:
echo "AWS_S3_PUBLIC_BASE_URL=${{ secrets.AWS_S3_PUBLIC_BASE_URL }}" >> .env
echo "FRONTEND_URL=${{ secrets.FRONTEND_URL }}" >> .env
echo "TOSS_PAYMENT_SECRET_KEY=${{ secrets.TOSS_PAYMENT_SECRET_KEY }}" >> .env
echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> .env


echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
Expand Down Expand Up @@ -233,6 +235,8 @@ jobs:
echo "CRON_PAYSLIP=${{ secrets.CRON_PAYSLIP }}" >> .env
echo "CRON_NOTIFICATION=${{ secrets.CRON_NOTIFICATION }}" >> .env
echo "CRON_BILLING=${{ secrets.CRON_BILLING }}" >> .env
echo "CRON_CLOCK_IN=${{ secrets.CRON_CLOCK_IN }}" >> .env
echo "FRONTEND_URL=${{ secrets.FRONTEND_URL }}" >> .env
echo "PAYROLL_BATCH_SIZE=${{ secrets.PAYROLL_BATCH_SIZE }}" >> .env
echo "PAYSLIP_BATCH_SIZE=${{ secrets.PAYSLIP_BATCH_SIZE }}" >> .env
echo "NOTIFICATION_BATCH_SIZE=${{ secrets.NOTIFICATION_BATCH_SIZE }}" >> .env
Expand All @@ -242,6 +246,8 @@ jobs:
echo "AWS_KMS_KEY_ID=${{ secrets.AWS_KMS_KEY_ID }}" >> .env
echo "NOTIFICATION_MAX_RETRY=${{ secrets.NOTIFICATION_MAX_RETRY }}" >> .env
echo "TOSS_PAYMENT_SECRET_KEY=${{ secrets.TOSS_PAYMENT_SECRET_KEY }}" >> .env
echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> .env

echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
docker compose down -v
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import com.mangoboss.app.dto.auth.response.JwtResponse;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import com.mangoboss.app.api.facade.auth.AuthFacade;
import com.mangoboss.app.dto.auth.requeset.LoginRequest;
Expand All @@ -29,4 +26,8 @@ public JwtResponse socialLogin(@RequestBody LoginRequest loginRequest) {
return authFacade.socialLogin(loginRequest);
}

@PostMapping("/logout")
public void logout(@RequestHeader("X-Refresh-Token") String refreshToken) {
authFacade.logout(refreshToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.mangoboss.app.api.controller.faq;

import com.mangoboss.app.api.facade.faq.BossFaqFacade;
import com.mangoboss.app.dto.ListWrapperResponse;
import com.mangoboss.app.dto.faq.FaqResponse;
import com.mangoboss.storage.faq.FaqCategory;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/boss/faq")
@RequiredArgsConstructor
@PreAuthorize("hasRole('BOSS')")
public class BossFaqController {

private final BossFaqFacade bossFaqFacade;

@GetMapping
public ListWrapperResponse<FaqResponse> getFaqs(@RequestParam(value = "category", defaultValue = "ALL") FaqCategory category) {
return ListWrapperResponse.of(bossFaqFacade.getFaqs(category));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.mangoboss.app.api.controller.faq;

import com.mangoboss.app.api.facade.faq.StaffFaqFacade;
import com.mangoboss.app.dto.ListWrapperResponse;
import com.mangoboss.app.dto.faq.FaqResponse;
import com.mangoboss.storage.faq.FaqCategory;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/staff/faq")
@RequiredArgsConstructor
@PreAuthorize("hasRole('STAFF')")
public class StaffFaqController {

private final StaffFaqFacade staffFaqFacade;

@GetMapping
public ListWrapperResponse<FaqResponse> getFaqs(@RequestParam(value = "category", defaultValue = "ALL") FaqCategory category) {
return ListWrapperResponse.of(staffFaqFacade.getFaqs(category));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class AuthFacade {
private final AuthService authService;

public JwtResponse reissueAccessToken(final RefreshTokenRequest refreshTokenRequest) {
authService.validateNotBlacklisted(refreshTokenRequest.refreshToken());
final String refreshToken = refreshTokenRequest.refreshToken();
final Long userId = authService.validateAndExtractIdFromRefreshToken(refreshToken);
final UserEntity user = userService.getUserById(userId);
Expand All @@ -30,4 +31,8 @@ public JwtResponse socialLogin(final LoginRequest loginRequest) {
final UserEntity user = userService.getOrCreateUser(kakaoUserInfo);
return authService.generateToken(user);
}

public void logout(final String refreshToken) {
authService.logout(refreshToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.mangoboss.app.api.facade.faq;

import com.mangoboss.app.domain.service.faq.FaqService;
import com.mangoboss.app.dto.faq.FaqResponse;
import com.mangoboss.storage.faq.FaqCategory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
public class BossFaqFacade {

private final FaqService faqService;

public List<FaqResponse> getFaqs(final FaqCategory category) {
return faqService.getFaqs(category).stream()
.map(FaqResponse::fromEntity)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.mangoboss.app.api.facade.faq;

import com.mangoboss.app.domain.service.faq.FaqService;
import com.mangoboss.app.dto.faq.FaqResponse;
import com.mangoboss.storage.faq.FaqCategory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
public class StaffFaqFacade {

private final FaqService faqService;

public List<FaqResponse> getFaqs(final FaqCategory category) {
return faqService.getFaqs(category).stream()
.map(FaqResponse::fromEntity)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public void createOrReplaceSubscription(Long bossId, SubscriptionCreateRequest r

public SubscriptionResponse getSubscription(Long bossId) {
final var subscription = subscriptionService.getSubscription(bossId);
if (subscription == null) {
return SubscriptionResponse.builder()
.planType(null)
.startedAt(null)
.expiredAt(null)
.nextPaymentDate(null)
.build();
}
return SubscriptionResponse.fromEntity(subscription);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public enum CustomErrorInfo {
INVALID_TOKEN(401, "유효하지 않은 토큰입니다.", 401003),
EXPIRED_TOKEN(401, "토큰이 만료되었습니다.", 401004),
UNSUPPORTED_TOKEN(401, "지원되지 않는 토큰입니다.", 401005),
INVALID_REFRESH_TOKEN(401, "블랙리스트에 등록된 리프레시 토큰입니다.", 401006),

// 403 FORBIDDEN
NOT_STORE_BOSS(403, "이 매장의 사장이 아닙니다.", 403001),
Expand Down Expand Up @@ -97,6 +98,7 @@ public enum CustomErrorInfo {
SUBSCRIPTION_NOT_FOUND(404, "구독 정보를 찾을 수 없습니다.", 404022),
PAYROLL_SETTING_NOT_FOUND(404, "해당 매장의 급여 설정을 찾을 수 없습니다.", 404023),
BILLING_CARD_NOT_REGISTERED(404, "결제수단이 등록되어 있지 않습니다.", 404024),
FAQ_NOT_FOUND(404, "FAQ를 찾을 수 없습니다.", 404025),

// 405 Method Not Allowed
METHOD_NOT_ALLOWED(405, "HTTP 메서드가 잘못되었습니다.", 405001),
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/com/mangoboss/app/common/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,22 @@ public Claims parseClaims(final String accessToken) {
return e.getClaims();
}
}

public long getRefreshTokenRemainingExpiration(final String refreshToken) {
Claims claims = parseClaims(refreshToken, false);
return claims.getExpiration().getTime() - System.currentTimeMillis();
}

private Claims parseClaims(final String token, final boolean isAccessToken) {
try {
SecretKey secret = isAccessToken ? accessSecret : refreshSecret;
return Jwts.parserBuilder()
.setSigningKey(secret)
.build()
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
return e.getClaims(); // 만료된 토큰일 경우에도 claims 추출 가능
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mangoboss.app.domain.repository;


import com.mangoboss.storage.faq.FaqEntity;
import com.mangoboss.storage.faq.FaqCategory;

import java.util.List;

public interface FaqRepository {
List<FaqEntity> findAll();

List<FaqEntity> findByCategory(FaqCategory category);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import com.mangoboss.storage.subscription.SubscriptionEntity;

import java.util.Optional;

public interface SubscriptionRepository {
void save(SubscriptionEntity subscriptionEntity);
SubscriptionEntity findByBossId(Long bossId);
Optional<SubscriptionEntity> findByBossId(Long bossId);
void delete(SubscriptionEntity subscriptionEntity);
boolean existsByBossId(Long bossId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
import com.mangoboss.storage.user.UserEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Service
@RequiredArgsConstructor
@Slf4j
public class AuthService {
private final JwtUtil jwtUtil;
private final KakaoSocialLogin kakaoSocialLogin;
private final StringRedisTemplate redisTemplate;

public KakaoUserInfo socialLogin(final LoginRequest loginRequest) {
final String kakaoAccessToken = kakaoSocialLogin.requestKakaoAccessToken(loginRequest);
Expand All @@ -42,4 +46,21 @@ public JwtResponse generateToken(final UserEntity user){
.refreshToken(refreshToken)
.build();
}

public void logout(final String refreshToken) {
final long remainingMillis = jwtUtil.getRefreshTokenRemainingExpiration(refreshToken);
final String redisKey = generateBlacklistKey(refreshToken);
redisTemplate.opsForValue().set(redisKey, "LOGOUT", Duration.ofMillis(remainingMillis));
}

public void validateNotBlacklisted(final String refreshToken) {
final String redisKey = generateBlacklistKey(refreshToken);
if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {
throw new CustomException(CustomErrorInfo.INVALID_REFRESH_TOKEN);
}
}

private String generateBlacklistKey(final String refreshToken) {
return "auth:logout:refresh:" + refreshToken;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,15 @@ public void validateBillingExists(Long bossId) {
}

public BillingCardInfoResponse getBillingCardInfo(Long bossId) {
BillingEntity billing = billingRepository.findByBossId(bossId)
.orElseThrow(() -> new CustomException(CustomErrorInfo.BILLING_NOT_FOUND));

if (billing.getBillingKey() == null || billing.getCardData() == null) {
throw new CustomException(CustomErrorInfo.BILLING_CARD_NOT_REGISTERED);
BillingEntity billing = billingRepository.findByBossId(bossId).orElse(null);

if (billing == null || billing.getBillingKey() == null || billing.getCardData() == null) {
return BillingCardInfoResponse.builder()
.cardCompany(null)
.cardNumber(null)
.cardType(null)
.ownerType(null)
.build();
}

Map<String, Object> cardMap = JsonConverter.fromJson(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.mangoboss.app.domain.service.faq;

import com.mangoboss.app.domain.repository.FaqRepository;
import com.mangoboss.storage.faq.FaqEntity;
import com.mangoboss.storage.faq.FaqCategory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class FaqService {

private final FaqRepository faqRepository;

public List<FaqEntity> getFaqs(final FaqCategory category) {
if (category == FaqCategory.ALL) {
return faqRepository.findAll();
}
return faqRepository.findByCategory(category);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.mangoboss.app.domain.service.subscription;

import com.mangoboss.app.common.exception.CustomErrorInfo;
import com.mangoboss.app.common.exception.CustomException;
import com.mangoboss.app.domain.repository.SubscriptionOrderRepository;
import com.mangoboss.app.dto.subscription.response.SubscriptionOrderResponse;
import com.mangoboss.storage.subscription.PlanType;
Expand All @@ -22,21 +24,20 @@ public class SubscriptionService {

@Transactional
public void createOrReplaceSubscription(Long bossId, PlanType planType) {
if (subscriptionRepository.existsByBossId(bossId)) {
SubscriptionEntity existing = subscriptionRepository.findByBossId(bossId);
subscriptionRepository.delete(existing);
}
subscriptionRepository.findByBossId(bossId).ifPresent(subscriptionRepository::delete);

SubscriptionEntity subscription = SubscriptionEntity.create(bossId, planType);
subscriptionRepository.save(subscription);
}

public SubscriptionEntity getSubscription(Long bossId) {
return subscriptionRepository.findByBossId(bossId);
return subscriptionRepository.findByBossId(bossId).orElse(null);
}

@Transactional
public void deleteSubscription(Long bossId) {
SubscriptionEntity subscription = subscriptionRepository.findByBossId(bossId);
SubscriptionEntity subscription = subscriptionRepository.findByBossId(bossId)
.orElseThrow(() -> new CustomException(CustomErrorInfo.SUBSCRIPTION_NOT_FOUND));
subscriptionRepository.delete(subscription);
}

Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/com/mangoboss/app/dto/faq/FaqResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mangoboss.app.dto.faq;

import com.mangoboss.storage.faq.FaqCategory;
import com.mangoboss.storage.faq.FaqEntity;
import lombok.Builder;

@Builder
public record FaqResponse(
Long id,
FaqCategory category,
String question,
String answer
) {
public static FaqResponse fromEntity(final FaqEntity entity) {
return FaqResponse.builder()
.id(entity.getId())
.category(entity.getCategory())
.question(entity.getQuestion())
.answer(entity.getAnswer())
.build();
}
}
Loading