Skip to content
Merged

Dev #59

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cb7102d
refactor: S3 파일 μ—…λ‘œλ“œ μœ νš¨μ„± 검사, λ³΄μ•ˆ 취약점 보완 및 μ•ˆμ •μ„± ν–₯상
bumstone Sep 4, 2025
52ae1e8
feat: Application κΈ°λ³Έ ꡬ쑰 및 User μ—”ν‹°ν‹° λ‚΄ UserSkill 리슀트 μΆ”κ°€
bumstone Sep 4, 2025
51a240d
feat: λͺ¨μ§‘ 곡고 κΈ°λŠ₯ κ΅¬ν˜„
bumstone Sep 5, 2025
5ba45de
feat: λͺ¨μ§‘ 곡고 μΆ”μ²œ κΈ°λŠ₯ κ΅¬ν˜„
bumstone Sep 6, 2025
fa05caa
feat: λͺ¨μ§‘ 곡고에 λŒ€ν•œ 지원 μ‹ μ²­ κΈ°λŠ₯ κ΅¬ν˜„
bumstone Sep 6, 2025
4646040
Merge pull request #53 from Gitdeun/feat/#46-recruitment_application
bumstone Sep 6, 2025
5a9b5cb
refactor: λΆˆν•„μš”ν•œ λ‘œκΉ… 및 μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ©”μ„œλ“œ, import λ¬Έ 정리
bumstone Sep 7, 2025
8b77f1b
fix: λͺ¨μ§‘ 인원 증감 둜직 κ°œμ„  및 μ•Œλ¦Ό ν˜•νƒœ μΆ”κ°€
bumstone Sep 7, 2025
ab1dc52
refactor: image파일과 Dto의 @RequestBody둜 뢄리 및 μ—”ν‹°ν‹° ν•„λ“œ μˆ˜μ •
bumstone Sep 7, 2025
ea9f085
fix: S3 버전에 맞게 μž¬μ„€μ •
bumstone Sep 7, 2025
ba74e36
fix: updateDto의 μœ νš¨μ„± 검사 μ™„ν™”
bumstone Sep 7, 2025
f8855c0
fix: λͺ¨μ§‘ 인원 queryDSL μˆ˜μ •
bumstone Sep 8, 2025
9601ed1
feat: emailSending SMTP μ„€μ • 및 λ°œμ‹ μž 정보 μˆ˜μ •
bumstone Sep 8, 2025
e1a9b2b
feat: fastAPI 연동 mindmap, FastApiClient μˆ˜μ •
bumstone Sep 9, 2025
a351fac
refactor: μ‚¬μš©ν•˜μ§€ μ•Šμ€ μ°Έμ‘° Dto, client 제거
bumstone Sep 9, 2025
967416b
feat: λͺ¨μ§‘곡고 μž‘μ„± 및 μˆ˜μ • μ‹œ 이메일 μž‘μ„± μΆ”κ°€
bumstone Sep 9, 2025
5d5007c
feat: λ§ˆμΈλ“œλ§΅ CRUD μˆ˜μ •
bumstone Sep 10, 2025
13322ca
Merge branch 'dev' of https://github.com/Gitdeun/gitdeun-BE into dev
bumstone Sep 10, 2025
a792be9
fix: @RequestMapping μ—”λ“œν¬μΈνŠΈ 적용
bumstone Sep 10, 2025
c4f8f03
feat: Mindmap Soft Deleted 적용 및 ν•€ κ³ μ • SSE 적용
bumstone Sep 13, 2025
56f3989
feat: Mindmap Prompt History 도메인 κ΅¬ν˜„
bumstone Sep 13, 2025
4886314
feat: Mindmap λ§ˆμΈλ“œλ§΅ 제λͺ© μˆ˜μ • 및 sse κ΄€λ ¨ κ°œμ„ 
bumstone Sep 13, 2025
1974e97
refactor: μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” Service λ©”μ„œλ“œ 제거
bumstone Sep 13, 2025
2f90d69
style: 기타 ν•„λ“œ, 주석, μ—”λ“œν¬μΈνŠΈ μˆ˜μ •
bumstone Sep 15, 2025
66d3223
refactor: N + 1 문제 μ΅œμ ν™”
bumstone Sep 15, 2025
7c77475
feat: λͺ¨μ§‘곡고 λͺ©λ‘ 쑰회 검색 κΈ°λŠ₯ κ΅¬ν˜„(Full Text Search + κΈ°λ³Έ like 검색)
bumstone Sep 15, 2025
85f6962
refactor: ν”„λ‘¬ν”„νŠΈ μš”μ•½ 뢄석 및 λ§ˆμΈλ“œλ§΅ 제λͺ© 톡합 및 fastapi resultDto 호좜 적용
bumstone Sep 15, 2025
c4f345c
Merge pull request #57 from Gitdeun/Improve/#56-PromptHistory_FullTex…
bumstone Sep 15, 2025
43b897f
fix: λ‚΄ 곡고 λͺ©λ‘μ‘°νšŒ μ‹œ 속성λͺ… 였λ₯˜
bumstone Sep 15, 2025
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
16 changes: 13 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,17 @@ dependencies {
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:${springCloudAwsVersion}")
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3'

// 파일의 맀직 λ°”μ΄νŠΈ κ²€μ‚¬μš© Apache Tika 라이브러리
implementation 'org.apache.tika:tika-core:3.2.2'

// queryDsl
implementation "com.querydsl:querydsl-core:${queryDslVersion}"
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
annotationProcessor (
"com.querydsl:querydsl-apt:${queryDslVersion}:jakarta",
'jakarta.annotation:jakarta.annotation-api',
// JPA 메타λͺ¨λΈ μƒμ„±μš©
"jakarta.persistence:jakarta.persistence-api:3.1.0"
'jakarta.persistence:jakarta.persistence-api'
)

// db
Expand Down Expand Up @@ -117,12 +121,18 @@ dependencies {
/** Q 클래슀 생성 경둜 μ§€μ • **/
def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile

// gradle clean μ‹œμ— QClass 디렉토리 μ‚­μ œ
clean {
delete file(querydslDir)
}

// πŸ”§ μ†ŒμŠ€μ…‹μ— generated 디렉토리 μΆ”κ°€
sourceSets {
main { java.srcDirs += querydslDir }
main.java.srcDirs += [ querydslDir ]
}

tasks.withType(JavaCompile).configureEach {
options.annotationProcessorGeneratedSourcesDirectory = querydslDir
options.generatedSourceOutputDirectory.set(querydslDir)
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.teamEWSN.gitdeun.Application.controller;

import com.teamEWSN.gitdeun.Application.dto.*;
import com.teamEWSN.gitdeun.Application.service.ApplicationService;
import com.teamEWSN.gitdeun.common.jwt.CustomUserDetails;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
@Tag(name = "Application", description = "λͺ¨μ§‘ 곡고 지원 κ΄€λ ¨ API")
public class ApplicationController {

private final ApplicationService applicationService;

/**
* λͺ¨μ§‘ 곡고에 μ§€μ›ν•˜κΈ°
*/
@PostMapping("/recruitments/{recruitmentId}/applications")
public ResponseEntity<ApplicationResponseDto> createApplication(
@PathVariable Long recruitmentId,
@Valid @RequestBody ApplicationCreateRequestDto requestDto,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
log.info("Creating application for recruitment: {} by user: {}", recruitmentId, userDetails.getId());
ApplicationResponseDto response = applicationService.createApplication(
recruitmentId, requestDto, userDetails.getId()
);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

/**
* λ‚΄ 지원 λͺ©λ‘ 쑰회
*/
@GetMapping("/users/me/applications")
public ResponseEntity<Page<ApplicationListResponseDto>> getMyApplications(
@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
log.info("Getting applications for user: {}", userDetails.getId());
Page<ApplicationListResponseDto> applications = applicationService.getMyApplications(
userDetails.getId(), pageable
);
return ResponseEntity.ok(applications);
}

/**
* νŠΉμ • 곡고의 μ§€μ›μž λͺ©λ‘ 쑰회 (λͺ¨μ§‘μžλ§Œ κ°€λŠ₯)
*/
@GetMapping("/recruitments/{recruitmentId}/applications")
public ResponseEntity<Page<ApplicationListResponseDto>> getRecruitmentApplications(
@PathVariable Long recruitmentId,
@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Page<ApplicationListResponseDto> applications = applicationService.getRecruitmentApplications(
recruitmentId, userDetails.getId(), pageable
);
return ResponseEntity.ok(applications);
}

/**
* 지원 상세 쑰회
*/
@GetMapping("/applications/{applicationId}")
public ResponseEntity<ApplicationResponseDto> getApplication(
@PathVariable Long applicationId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
ApplicationResponseDto application = applicationService.getApplication(
applicationId, userDetails.getId()
);
return ResponseEntity.ok(application);
}

/**
* 지원 철회 (μ§€μ›μžλ§Œ κ°€λŠ₯)
*/
@PatchMapping("/applications/{applicationId}/withdraw")
public ResponseEntity<Void> withdrawApplication(
@PathVariable Long applicationId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
applicationService.withdrawApplication(applicationId, userDetails.getId());
return ResponseEntity.noContent().build();
}

/**
* 지원 수락 (λͺ¨μ§‘μžλ§Œ κ°€λŠ₯)
*/
@PatchMapping("/applications/{applicationId}/accept")
public ResponseEntity<ApplicationResponseDto> acceptApplication(
@PathVariable Long applicationId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
ApplicationResponseDto response = applicationService.acceptApplication(
applicationId, userDetails.getId()
);
return ResponseEntity.ok(response);
}

/**
* 지원 거절 (λͺ¨μ§‘μžλ§Œ κ°€λŠ₯)
*/
@PatchMapping("/applications/{applicationId}/reject")
public ResponseEntity<ApplicationResponseDto> rejectApplication(
@PathVariable Long applicationId,
@Valid @RequestBody ApplicationStatusUpdateDto updateDto,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
ApplicationResponseDto response = applicationService.rejectApplication(
applicationId, userDetails.getId(), updateDto
);
return ResponseEntity.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.teamEWSN.gitdeun.Application.dto;

import com.teamEWSN.gitdeun.Recruitment.entity.RecruitmentField;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ApplicationCreateRequestDto {

@NotNull(message = "지원 λΆ„μ•Όλ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”.")
private RecruitmentField appliedField;

@Size(max = 1000, message = "지원 λ©”μ‹œμ§€λŠ” 1000자 μ΄λ‚΄λ‘œ μž‘μ„±ν•΄μ£Όμ„Έμš”.")
private String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.teamEWSN.gitdeun.Application.dto;

import com.teamEWSN.gitdeun.Application.entity.ApplicationStatus;
import com.teamEWSN.gitdeun.Recruitment.entity.RecruitmentField;
import lombok.*;

import java.time.LocalDateTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ApplicationListResponseDto {

private Long applicationId;

// μ§€μ›μž κ°„λž΅ 정보
private Long applicantId;
private String applicantName;
private String applicantNickname;
private String applicantProfileImage;

// 곡고 κ°„λž΅ 정보
private String recruitmentTitle;

// 지원 정보
private RecruitmentField appliedField;
private ApplicationStatus status;
private LocalDateTime createdAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.teamEWSN.gitdeun.Application.dto;

import com.teamEWSN.gitdeun.Application.entity.ApplicationStatus;
import com.teamEWSN.gitdeun.Recruitment.entity.RecruitmentField;
import lombok.*;

import java.time.LocalDateTime;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ApplicationResponseDto {

private Long applicationId;

// μ§€μ›μž 정보
private Long applicantId;
private String applicantName;
private String applicantEmail;
private String applicantNickname;
private String applicantProfileImage;

// 곡고 정보
private Long recruitmentId;
private String recruitmentTitle;
private String recruiterName;

// 지원 정보
private RecruitmentField appliedField;
private String message;
private ApplicationStatus status;
private String rejectReason;
private boolean active;

// λ‚ μ§œ 정보
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.teamEWSN.gitdeun.Application.dto;

import jakarta.validation.constraints.Size;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ApplicationStatusUpdateDto {

@Size(max = 500, message = "거절 μ‚¬μœ λŠ” 500자 μ΄λ‚΄λ‘œ μž‘μ„±ν•΄μ£Όμ„Έμš”.")
private String rejectReason;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
@Table(name = "application",
uniqueConstraints = {
// 동일 μ‚¬μš©μžΒ·κ³΅κ³ μ— λŒ€ν•΄ "ν™œμ„±" μ‹ μ²­ 쀑볡 λ°©μ§€μš© (WITHDRAWN μ œμ™Έ)
@UniqueConstraint(name = "uk_active_application",
columnNames = {"recruitment_id", "applicant_id", "active"})
@UniqueConstraint(name = "uk_application_recruitment_applicant",
columnNames = {"recruitment_id", "applicant_id"})
},
indexes = {
@Index(name = "idx_application_recruitment", columnList = "recruitment_id"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.teamEWSN.gitdeun.Application.mapper;

import com.teamEWSN.gitdeun.Application.dto.ApplicationListResponseDto;
import com.teamEWSN.gitdeun.Application.dto.ApplicationResponseDto;
import com.teamEWSN.gitdeun.Application.entity.Application;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ApplicationMapper {

@Mapping(source = "id", target = "applicationId")
@Mapping(source = "applicant.id", target = "applicantId")
@Mapping(source = "applicant.name", target = "applicantName")
@Mapping(source = "applicant.email", target = "applicantEmail")
@Mapping(source = "applicant.nickname", target = "applicantNickname")
@Mapping(source = "applicant.profileImage", target = "applicantProfileImage")
@Mapping(source = "recruitment.id", target = "recruitmentId")
@Mapping(source = "recruitment.title", target = "recruitmentTitle")
@Mapping(source = "recruitment.recruiter.name", target = "recruiterName")
ApplicationResponseDto toResponseDto(Application application);

@Mapping(source = "id", target = "applicationId")
@Mapping(source = "applicant.id", target = "applicantId")
@Mapping(source = "applicant.name", target = "applicantName")
@Mapping(source = "applicant.nickname", target = "applicantNickname")
@Mapping(source = "applicant.profileImage", target = "applicantProfileImage")
@Mapping(source = "recruitment.title", target = "recruitmentTitle")
ApplicationListResponseDto toListResponseDto(Application application);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.teamEWSN.gitdeun.Application.repository;

import com.teamEWSN.gitdeun.Application.entity.Application;
import com.teamEWSN.gitdeun.Recruitment.entity.Recruitment;
import com.teamEWSN.gitdeun.user.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface ApplicationRepository extends JpaRepository<Application, Long> {

// νŠΉμ • μ‚¬μš©μžμ˜ ν™œμ„± 지원 λ‚΄μ—­λ§Œ 쑰회
Page<Application> findByApplicantAndActiveTrueOrderByCreatedAtDesc(User applicant, Pageable pageable);

// νŠΉμ • 곡고의 ν™œμ„± μ§€μ›μžλ§Œ 쑰회
Page<Application> findByRecruitmentAndActiveTrueOrderByCreatedAtDesc(Recruitment recruitment, Pageable pageable);

// μ‚¬μš©μžκ°€ νŠΉμ • 곡고에 이미 μ§€μ›ν–ˆλŠ”μ§€ 확인 (ν™œμ„± μ§€μ›λ§Œ)
boolean existsByRecruitmentAndApplicantAndActiveTrue(Recruitment recruitment, User applicant);

// νŠΉμ • 지원 쑰회 (μ§€μ›μž 본인 ν™•μΈμš©)
Optional<Application> findByIdAndApplicant(Long id, User applicant);

// νŠΉμ • 지원 쑰회 (곡고 μž‘μ„±μž ν™•μΈμš©)
@Query("SELECT a FROM Application a WHERE a.id = :id AND a.recruitment.recruiter = :recruiter")
Optional<Application> findByIdAndRecruiter(@Param("id") Long id, @Param("recruiter") User recruiter);

}
Loading