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
60 changes: 37 additions & 23 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,60 @@
// Generated by https://start.springboot.io
// 优质的 spring/boot/data/security/cloud 框架中文文档尽在 => https://springdoc.cn
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.5'
id 'io.spring.dependency-management' version '1.1.7'
id 'java'
id 'org.springframework.boot' version '3.4.5'
id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
// runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

//JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

//Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

//Smtp
implementation 'org.springframework.boot:spring-boot-starter-mail'

// swagger
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0"
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
3 changes: 3 additions & 0 deletions src/main/java/com/JoinUs/dp/domain/club/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
임시로 만든 club 도메인
전부 삭제하고 다른 club 도메인으로 교체해도 무방함
대신 interview.SetData 클래스 파일을 삭제하거나(SetData 파일은 없어도 상관없음) 약간의 코드변경을 해야함
25 changes: 25 additions & 0 deletions src/main/java/com/JoinUs/dp/domain/club/entity/Club.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.JoinUs.dp.domain.club.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter @Setter
public class Club {
protected Club() {}

@Id
@GeneratedValue
private Long id;

@Column
private String name;

public Club(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.JoinUs.dp.domain.club.repository;

import com.JoinUs.dp.domain.club.entity.Club;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ClubRepository extends JpaRepository<Club, Long> {
}
152 changes: 152 additions & 0 deletions src/main/java/com/JoinUs/dp/domain/interview/SetData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package com.JoinUs.dp.domain.interview;

import com.JoinUs.dp.domain.club.entity.Club;
import com.JoinUs.dp.domain.club.repository.ClubRepository;
import com.JoinUs.dp.domain.interview.entity.Applicant;
import com.JoinUs.dp.domain.interview.entity.Interview;
import com.JoinUs.dp.domain.interview.entity.enums.InterviewConfirmedEnum;
import com.JoinUs.dp.domain.interview.entity.enums.InterviewResultEnum;
import com.JoinUs.dp.domain.interview.repository.ApplicantRepository;
import com.JoinUs.dp.domain.interview.repository.InterviewRepository;
import com.JoinUs.dp.domain.interview.service.InterviewService;
import com.JoinUs.dp.domain.user.dto.request.RegisterDto;
import com.JoinUs.dp.domain.user.entity.User;
import com.JoinUs.dp.domain.user.repository.UserRepository;
import com.JoinUs.dp.domain.user.service.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;


// 테스트 데이터 생성 클래스 파일 (삭제해도 무방함)
@Component("interviewDataSeeder")
@RequiredArgsConstructor
public class SetData implements CommandLineRunner {
private final InterviewService interviewService;
private final AuthService authService;
private final InterviewRepository interviewRepository;
private final ClubRepository clubRepository;
private final UserRepository userRepository;
private final ApplicantRepository applicantRepository;

private void UserDataSeeding(){
authService.register(new RegisterDto(
"user1@gmail.com",
"user1",
"user1"
));
authService.register(new RegisterDto(
"user2@gmail.com",
"user2",
"user2"
));
authService.register(new RegisterDto(
"user3@gmail.com",
"user3",
"user3"
));
authService.register(new RegisterDto(
"user4@gmail.com",
"user4",
"user4"
));
authService.register(new RegisterDto(
"user5@gmail.com",
"user5",
"user5"
));
authService.register(new RegisterDto(
"user6@gmail.com",
"user6",
"user6"
));
authService.register(new RegisterDto(
"user7@gmail.com",
"user7",
"user7"
));

}

private void ClubDataSeeding(){
List<Club> clubs = List.of(
new Club("밴드부"),
new Club("OTT")
);
clubRepository.saveAll(clubs);
}

// private void InterviewDataSeeding(){
// Club club = clubRepository.findById(1L).orElse(null);
// DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm");
//
// List<Interview> interviews = List.of(
// new Interview(
// club,
// LocalDateTime.parse("2025-10-11T13:00", fmt),
// LocalDateTime.parse("2025-10-11T13:30", fmt),
// "산학협동관 I208호",
// 3L
// ),
// new Interview(
// club,
// LocalDateTime.parse("2025-10-11T13:00", fmt),
// LocalDateTime.parse("2025-10-11T13:30", fmt),
// "산학협동관 I210호",
// 3L
// ),
// new Interview(
// club,
// LocalDateTime.parse("2025-10-11T13:30", fmt),
// LocalDateTime.parse("2025-10-11T14:00", fmt),
// "산학협동관 I208호",
// 3L
// )
// );
// interviewRepository.saveAll(interviews);
// }

//
// private void ApplyInterview(Long interviewId, List<String> userNameList) {
// for (String userName : userNameList) {
// User user = userRepository.findByUsername(userName);
// String userEmail = user.getEmail();
// interviewService.apply(interviewId, userEmail);
// }
// }
//
// private void SetInterviewResult(List<String> userNameList, InterviewResultEnum status) {
// for (String userName : userNameList) {
// Applicant applicant = applicantRepository.findByUser_Username(userName);
// applicant.setInterviewResult(status);
// applicantRepository.save(applicant);
// }
// }
//
// private void SetUserConfirmStatus(List<String> userNameList, InterviewConfirmedEnum status) {
// for (String userName : userNameList) {
// Applicant applicant = applicantRepository.findByUser_Username(userName);
// applicant.setConfirmStatus(status);
// applicantRepository.save(applicant);
// }
//}

public void run(String[] args){
UserDataSeeding();
ClubDataSeeding();
// InterviewDataSeeding();

// ApplyInterview(1L, List.of("user1", "user2", "user3"));
// ApplyInterview(2L, List.of("user4", "user5"));
// ApplyInterview(3L, List.of("user6", "user7"));
//
// SetInterviewResult(List.of("user1", "user2", "user3", "user4"), InterviewResultEnum.PASSED);
// SetInterviewResult(List.of("user5", "user6", "user7"), InterviewResultEnum.FAILED);
// SetUserConfirmStatus(List.of("user1", "user2"), InterviewConfirmedEnum.CONFIRMED);
// SetUserConfirmStatus(List.of("user3"), InterviewConfirmedEnum.DECLINED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.JoinUs.dp.domain.interview.controller;


import com.JoinUs.dp.domain.interview.dto.request.CreateInterviewRequestDto;
import com.JoinUs.dp.domain.interview.dto.response.CheckDetailedInterviewResponseDto;
import com.JoinUs.dp.domain.interview.dto.response.CheckInterviewResponseDto;
import com.JoinUs.dp.domain.interview.dto.response.InterviewResponseDto;
import com.JoinUs.dp.domain.interview.dto.response.UnconfirmedUserResponseDto;
import com.JoinUs.dp.domain.interview.service.InterviewService;
import com.JoinUs.dp.global.common.ApiPath;
import com.JoinUs.dp.global.dto.Response;
import com.JoinUs.dp.global.utility.JwtProvider;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

@RestController
@RequiredArgsConstructor
public class InterviewController {
private final InterviewService interviewService;
private final JwtProvider jwtProvider;

// 1) 면접 회차 생성
@Operation(summary = "면접 회차 생성")
@PostMapping(ApiPath.CLUB_INTERVIEWS)
public ResponseEntity<Response<InterviewResponseDto>> create(
@PathVariable Long clubId,
@RequestBody CreateInterviewRequestDto req
) {
return ResponseEntity.ok(
new Response<>(
HttpStatus.OK,
interviewService.create(clubId, req),
"면접 회차 생성 완료"
)
);
}

// 2) 면접 회차 조회
@Operation(summary = "면접 회차 조회")
@GetMapping(ApiPath.CLUB_INTERVIEWS)
public ResponseEntity<Response<CheckInterviewResponseDto>> check(@PathVariable Long clubId) {
return ResponseEntity.ok(
new Response<>(
HttpStatus.OK,
interviewService.check(clubId),
"면접 회차 조회"
)
);
}

// // 3) 면접 신청
// @PostMapping(ApiPath.INTERVIEW_APPLY)
// public void apply(
// @PathVariable Long interviewId,
// @RequestBody ApplyInterviewRequestDto req
// ) {
// interviewService.apply(interviewId, req);
// }

private String resolveBearer(String authorization) {
if (authorization == null || !authorization.startsWith("Bearer ")) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "토큰이 없습니다");
}
return authorization.substring(7); // "Bearer " 제거
}

// 3) 면접 신청
@Operation(summary = "면접 신청")
@SecurityRequirement(name = "bearerAuth")
@PostMapping("/interviews/{interviewId}/apply")
public void apply(@PathVariable Long interviewId, HttpServletRequest request) {
String authorization = request.getHeader(HttpHeaders.AUTHORIZATION); // Swagger가 전역으로 붙여줌
if (authorization == null || authorization.isBlank()) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Authorization 헤더가 없습니다");
}
String token = resolveBearer(authorization);

// 유효성 확인
if (!jwtProvider.validateToken(token)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰");
}
// 토큰 -> 이메일
String email = jwtProvider.extractEmail(token);

// interviewService.applyWithToken(interviewId, token);
interviewService.apply(interviewId, email);
}


// 4) 신청자 면접 현황 조회
@Operation(summary = "신청자 면접 현황 조회")
@GetMapping(ApiPath.CLUB_INTERVIEW_APPS)
public ResponseEntity<Response<CheckDetailedInterviewResponseDto>> checkDetailed(@PathVariable Long clubId) {
return ResponseEntity.ok(
new Response<>(
HttpStatus.OK,
interviewService.checkDetailed(clubId),
"신청자 면접 현황 조회"
)
);
}

// 5) 미확정/가입거절 인원 조회
@Operation(summary = "미확정/가입거절 인원 조회")
@GetMapping(ApiPath.CLUB_VACANCIES)
public ResponseEntity<Response<UnconfirmedUserResponseDto>> checkUnconfirmedUser(@PathVariable Long clubId) {
return ResponseEntity.ok(
new Response<>(
HttpStatus.OK,
interviewService.checkUnconfirmedUser(clubId),
"미확정/가입거절 인원 조회"
)
);
}
}
Loading