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
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import org.ject.support.common.exception.GlobalErrorCode;
import org.ject.support.common.exception.GlobalException;
import org.ject.support.common.security.CustomUserDetails;
import org.ject.support.domain.auth.exception.AuthErrorCode;
import org.ject.support.domain.auth.exception.AuthException;
import org.ject.support.domain.member.Role;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
Expand Down Expand Up @@ -51,6 +49,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
if (jwtTokenProvider.validateToken(accessToken)) {
Authentication auth = jwtTokenProvider.getAuthenticationByToken(accessToken);
SecurityContextHolder.getContext().setAuthentication(auth);

chain.doFilter(request, response);
return;
} else {
clearAuthCookie(response, "accessToken");
SecurityContextHolder.clearContext();
Expand All @@ -64,15 +65,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
jwtTokenProvider.resolveVerificationToken(request);

if (verificationToken != null) {
if (!jwtTokenProvider.validateToken(verificationToken)) {
// verification token은 실패 시 에러가 맞음
throw new AuthException(AuthErrorCode.INVALID_TOKEN);
}
if (jwtTokenProvider.validateToken(verificationToken)) {
String email = jwtTokenProvider.extractEmailFromVerificationToken(verificationToken);

String email = jwtTokenProvider.extractEmailFromVerificationToken(verificationToken);

Authentication auth = createVerificationAuthentication(email);
SecurityContextHolder.getContext().setAuthentication(auth);
Authentication auth = createVerificationAuthentication(email);
SecurityContextHolder.getContext().setAuthentication(auth);
} else {
clearAuthCookie(response, "verificationToken");
SecurityContextHolder.clearContext();
}
}

chain.doFilter(request, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.ject.support.common.exception.GlobalException;
Expand Down Expand Up @@ -205,7 +206,19 @@ public String createVerificationToken(String email) {
.signWith(secretKey)
.compact();
}


/**
* 인증번호 검증 쿠키 삭제
*/
public void deleteVerificationCookie(HttpServletResponse response) {
Cookie cookie = new Cookie("verificationToken", null);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setMaxAge(0);
response.addCookie(cookie);
}

/**
* 인증번호 검증 토큰에서 이메일 추출
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public interface AdminTempApplyApiSpec {
)
Page<TempSavedApplyResponse> getTempApplies(
@RequestParam(required = false) JobFamily jobFamily,
@RequestParam(required = false) final Long semesterId,
@PageableDefault(size = 15) final Pageable pageable
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public void deleteTempApply(@PathVariable final Long tempApplyId) {

@GetMapping()
public Page<TempSavedApplyResponse> getTempApplies(@RequestParam(required = false) JobFamily jobFamily,
@RequestParam(required = false) final Long semesterId,
@PageableDefault(size = 15) Pageable pageable) {
return adminTempApplyService.getTempApplies(jobFamily, pageable);
return adminTempApplyService.getTempApplies(jobFamily, semesterId, pageable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public interface SubmittedApplyApiSpec {
summary = "제출된 지원서 목록 조회",
description = "제출된 지원서들의 목록을 조회합니다.")
Page<SubmittedApplyResponse> findSubmittedApplies(@RequestParam(required = false) final JobFamily jobFamily,
@RequestParam(required = false) final Long semesterId,
@PageableDefault(size = 15) final Pageable pageable);

@Operation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ public SubmittedApplyCountResponse getSubmittedApplyCount() {
@Override
@GetMapping
public Page<SubmittedApplyResponse> findSubmittedApplies(@RequestParam(required = false) final JobFamily jobFamily,
@RequestParam(required = false) final Long semesterId,
@PageableDefault(size = 15) final Pageable pageable) {
return submittedApplyService.findSubmittedApplies(jobFamily, pageable);
return submittedApplyService.findSubmittedApplies(jobFamily, semesterId, pageable);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import org.ject.support.domain.admin.exception.AdminException;
import org.ject.support.domain.member.MemberStatus;
import org.ject.support.domain.member.entity.Member;
import org.ject.support.external.discord.DiscordComponent;
import org.ject.support.external.infrastructure.DiscordRateLimiter;
import org.ject.support.external.notification.event.AdminLoginNotificationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
Expand All @@ -24,10 +25,10 @@
@Transactional(readOnly = true)
public class AdminAuthService {

private final ApplicationEventPublisher applicationEventPublisher;
private final RedisTemplate<String, String> redisTemplate;
private final AdminMemberComponent adminMemberComponent;
private final DiscordRateLimiter discordRateLimiter;
private final DiscordComponent discordComponent;
private final JwtTokenProvider jwtTokenProvider;

private static final String ADMIN_LOGIN_AUTH_CODE_KEY_PREFIX = "admin-login:";
Expand All @@ -46,9 +47,9 @@ public String sendAdminAuthCode(String email) {

if (discordRateLimiter.tryConsume(1)) {
redisTemplate.opsForValue().set(key, authCode, Duration.ofSeconds(ADMIN_LOGIN_AUTH_CODE_EXPIRATION));
discordComponent.sendAdminLoginMessage(member.getEmail(), authCode)
.doOnError(e -> log.error("Discord 전송 실패: {}", member.getEmail(), e))
.subscribe();

applicationEventPublisher
.publishEvent(new AdminLoginNotificationEvent(email, authCode));
} else {
throw new AdminException(AdminErrorCode.TOO_MANY_REQUESTS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ public void deleteTempApply(Long applyId) {
applyRepository.delete(apply);
}

public Page<TempSavedApplyResponse> getTempApplies(JobFamily jobFamily, Pageable pageable) {
public Page<TempSavedApplyResponse> getTempApplies(JobFamily jobFamily, Long semesterId, Pageable pageable) {
Apply.Status tempSavedStatus = Apply.Status.TEMP_SAVED;
Page<Apply> applyPage = applyRepository.findAppliesByStatus(jobFamily, tempSavedStatus, pageable);
Page<Apply> applyPage = applyRepository.findAppliesByStatus(jobFamily, tempSavedStatus, semesterId, pageable);

List<TempSavedApplyResponse> content = applyPage.getContent().stream()
.map(this::toTempSavedApplyResponse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ public class SubmittedApplyService {

@Transactional(readOnly = true)
public Page<SubmittedApplyResponse> findSubmittedApplies(final JobFamily jobFamily,
final Long semesterId,
final Pageable pageable) {
Page<Apply> applyPage = applyRepository.findAppliesByStatus(jobFamily, Status.SUBMITTED, pageable);
Page<Apply> applyPage = applyRepository.findAppliesByStatus(jobFamily, Status.SUBMITTED, semesterId, pageable);

List<SubmittedApplyResponse> content = applyPage.getContent().stream()
.map(this::toSubmittedApplyResponse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
public interface ApplyQueryRepository {
Page<Apply> findAppliesByStatus(final JobFamily jobFamily,
final Apply.Status status,
final Long semesterId,
final Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public class ApplyQueryRepositoryImpl implements ApplyQueryRepository {

@Override
public Page<Apply> findAppliesByStatus(final JobFamily jobFamily,
final Apply.Status status, final Pageable pageable) {
final Apply.Status status,
final Long semesterId,
final Pageable pageable) {

List<Apply> content = queryFactory
.selectFrom(apply)
Expand All @@ -37,7 +39,8 @@ public Page<Apply> findAppliesByStatus(final JobFamily jobFamily,
.where(
apply.member.isDeleted.eq(false),
eqJobFamily(jobFamily),
eqApplyStatus(status)
eqApplyStatus(status),
eqSemesterId(semesterId)
)
.orderBy(apply.createdAt.desc())
.offset(pageable.getOffset())
Expand All @@ -51,7 +54,8 @@ public Page<Apply> findAppliesByStatus(final JobFamily jobFamily,
.where(
apply.member.isDeleted.eq(false),
eqJobFamily(jobFamily),
eqApplyStatus(status)
eqApplyStatus(status),
eqSemesterId(semesterId)
).fetchOne();

return PageResponse.from(content, pageable, total);
Expand All @@ -67,4 +71,10 @@ private BooleanExpression eqApplyStatus(final Apply.Status status) {
.map(apply.status::eq)
.orElse(null);
}

private BooleanExpression eqSemesterId(final Long semesterId) {
return Optional.ofNullable(semesterId)
.map(apply.recruit.semester.id::eq)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@
import org.ject.support.domain.recruit.exception.RecruitErrorCode;
import org.ject.support.domain.recruit.exception.RecruitException;
import org.ject.support.domain.recruit.repository.RecruitRepository;
import org.ject.support.external.n8n.event.ApplicationSubmittedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.ject.support.domain.apply.domain.Apply.Status.JOINED;
import static org.ject.support.domain.apply.domain.Apply.Status.SUBMITTED;
Expand All @@ -53,6 +54,8 @@ public class ApplyService implements ApplyUsecase {
private final Map2JsonSerializer map2JsonSerializer;
private final String2MapSerializer string2MapSerializer;

private final ApplicationEventPublisher applicationEventPublisher;

@Override
@PeriodAccessible(permitAllJob = true)
@Transactional(readOnly = true)
Expand Down Expand Up @@ -167,6 +170,10 @@ public void submitApplication(Long memberId,

// 5. Apply 엔티티에 제출 위임 (검증 및 상태 변경 포함)
apply.submit(applicationForm);

// 6. n8n에 지원 완료 이벤트 발행
applicationEventPublisher
.publishEvent(new ApplicationSubmittedEvent(apply.getId()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import org.ject.support.domain.member.Role;
import org.ject.support.domain.member.entity.Member;
import org.ject.support.domain.member.repository.MemberRepository;
import org.ject.support.external.discord.DiscordComponent;
import org.ject.support.external.notification.event.SupporterTokenIssuedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -28,10 +29,10 @@ public class AuthSupporterTokenService {

private final long TOKEN_EXPIRATION_MILLIS = 24 * 60 * 60 * 1000; // 1일

private final ApplicationEventPublisher applicationEventPublisher;
private final JwtTokenProvider jwtTokenProvider;
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final DiscordComponent discordComponent;

public void issueReadOnlyToken(String email, String pin) {
Member member = memberRepository.findByEmailAndRole(email, Role.ADMIN)
Expand All @@ -50,10 +51,7 @@ public void issueReadOnlyToken(String email, String pin) {

String token = jwtTokenProvider.createToken(claims, TOKEN_EXPIRATION_MILLIS);

discordComponent.sendSupporterTokenIssueMessage(email, token)
.doOnError(e -> {
log.error("지원자 토큰 전송 실패: {}", email, e);
})
.subscribe();
applicationEventPublisher
.publishEvent(new SupporterTokenIssuedEvent(email, token));
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.ject.support.domain.jectalk.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.ject.support.domain.jectalk.dto.JectalkResponse;
import org.ject.support.domain.project.entity.Project;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.RequestParam;

@Tag(name = "Jectalk", description = "젝톡 API")
public interface JectalkApiSpec {
Expand All @@ -14,5 +17,8 @@ public interface JectalkApiSpec {
summary = "젝톡 목록 조회",
description = "젝톡 목록을 조회합니다."
)
Page<JectalkResponse> findJectalks(@PageableDefault(size = 12) Pageable pageable);
Page<JectalkResponse> findJectalks(
@PageableDefault(size = 12) Pageable pageable,
@Parameter(description = "기수 (SEMESTER_1, SEMESTER_2, SEMESTER_3)", example = "SEMESTER_1")
@RequestParam(required = false) Project.Category category);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import lombok.RequiredArgsConstructor;
import org.ject.support.domain.jectalk.dto.JectalkResponse;
import org.ject.support.domain.jectalk.service.JectalkService;
import org.ject.support.domain.project.entity.Project;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -19,7 +21,9 @@ public class JectalkController implements JectalkApiSpec {

@Override
@GetMapping
public Page<JectalkResponse> findJectalks(@PageableDefault(size = 12) Pageable pageable) {
return jectalkService.findJectalks(pageable);
public Page<JectalkResponse> findJectalks(
@PageableDefault(size = 12) Pageable pageable,
@RequestParam(required = false) Project.Category category) {
return jectalkService.findJectalks(pageable, category);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

import com.querydsl.core.annotations.QueryProjection;
import lombok.Builder;
import org.ject.support.domain.jectalk.enums.ContentType;

@Builder
public record JectalkResponse(Long id, String name, String youtubeUrl, String imageUrl, String summary) {
public record JectalkResponse(
Long id,
String title,
String description,
String contentUrl,
ContentType contentType,
String thumbnailUrl,
String summary) {

@QueryProjection
public JectalkResponse {
Expand Down
Loading
Loading