diff --git a/src/main/java/kr/re/etri/security/jwt/controllers/ProjectController.java b/src/main/java/kr/re/etri/security/jwt/controllers/ProjectController.java new file mode 100644 index 0000000..8a5141a --- /dev/null +++ b/src/main/java/kr/re/etri/security/jwt/controllers/ProjectController.java @@ -0,0 +1,83 @@ +package kr.re.etri.security.jwt.controllers; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import kr.re.etri.security.jwt.entity.ProjectEntity; +import kr.re.etri.security.jwt.payload.request.BaseSearchRequest; +import kr.re.etri.security.jwt.payload.request.ProjectRequest; +import kr.re.etri.security.jwt.service.ProjectService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.data.domain.Page; + +import java.util.List; + +@Tag(name = "프로젝트 API", description = "Project CRUD 기능 제공") +@RestController +@RequestMapping("/api/projects") +@RequiredArgsConstructor +public class ProjectController { + + private final ProjectService projectService; + + @Operation(summary = "전체 프로젝트 목록 조회") + @GetMapping + public ResponseEntity> getAllProjects() { + return ResponseEntity.ok(projectService.findAll()); + } + @Operation(summary = "ID로 프로젝트 조회") + @GetMapping("/{id}") + public ResponseEntity getProjectById( + @Parameter(description = "조회할 프로젝트 ID", required = true, in = ParameterIn.PATH) + @PathVariable("id") Long id) { + + return projectService.findById(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @Operation(summary = "검색 및 페이지네이션 프로젝트 목록 조회") + @GetMapping("/search") + public ResponseEntity> searchProjects(BaseSearchRequest request) { + Page page = projectService.search(request); + return ResponseEntity.ok(page); + } + + @Operation(summary = "프로젝트 생성") + @PostMapping + public ResponseEntity createProject(@RequestBody ProjectEntity project) { + try { + ProjectEntity saved = projectService.create(project); + return ResponseEntity.ok(saved); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + @Operation(summary = "프로젝트 수정") + @PutMapping("/{id}") + public ResponseEntity updateProject( + @Parameter(description = "수정할 프로젝트 ID", required = true, in = ParameterIn.PATH) + @PathVariable("id") Long id, + @RequestBody ProjectRequest dto) { + + return projectService.update(id, dto) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + + @Operation(summary = "프로젝트 삭제") + @DeleteMapping("/{id}") + public ResponseEntity deleteProject( + @Parameter(description = "삭제할 프로젝트 ID", required = true, in = ParameterIn.PATH) + @PathVariable("id") Long id) { + if (projectService.delete(id)) { + return ResponseEntity.noContent().build(); + } + return ResponseEntity.notFound().build(); + } +} diff --git a/src/main/java/kr/re/etri/security/jwt/entity/ProjectEntity.java b/src/main/java/kr/re/etri/security/jwt/entity/ProjectEntity.java index e6d0e5b..3f61388 100644 --- a/src/main/java/kr/re/etri/security/jwt/entity/ProjectEntity.java +++ b/src/main/java/kr/re/etri/security/jwt/entity/ProjectEntity.java @@ -1,15 +1,13 @@ package kr.re.etri.security.jwt.entity; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import org.hibernate.annotations.Comment; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; +@Schema(description = "프로젝트") @Comment("프로젝트") @Entity @Table(name = "tb_project") @@ -22,33 +20,58 @@ public class ProjectEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Schema(description = "ID", example = "null") @Comment("ID") private Long id; + @Column(unique = true) + @Schema(description = "프로젝트 코드", example = "PRJ001") @Comment("프로젝트 코드") private String prjCd; + + @Schema(description = "프로젝트 이름", example = "AI 연구 프로젝트") @Comment("프로젝트 이름") private String prjNm; + + @Schema(description = "프로젝트 설명", example = "ETRI와 함께하는 AI 개발") @Comment("프로젝트 설명") private String prjDesc; + + @Schema(description = "프로젝트 시작일", example = "2025-08-01") @Comment("프로젝트 시작일") private String prjStartDt; + + @Schema(description = "프로젝트 종료일", example = "2025-12-31") @Comment("프로젝트 종료일") private String prjEndDt; + + @Schema(description = "삭제 여부", example = "N") @Comment("삭제 여부") private String delYn; + @CreatedDate + @Schema(description = "등록 일자", example = "2025-08-01T12:34:56") @Comment("등록 일자") private String regDate; + + @Schema(description = "등록 유저 ID", example = "admin") @Comment("등록 유저 ID") private String regUserId; + + @Schema(description = "등록 유저 이름", example = "관리자") @Comment("등록 유저 이름") private String regUserNm; + @LastModifiedDate + @Schema(description = "수정 일자", example = "2025-08-01T13:45:00") @Comment("수정 일자") private String modDate; + + @Schema(description = "수정 유저 ID", example = "editor") @Comment("수정 유저 ID") private String modUserId; + + @Schema(description = "수정 유저 이름", example = "에디터") @Comment("수정 유저 이름") private String modUserNm; } diff --git a/src/main/java/kr/re/etri/security/jwt/payload/request/BaseSearchRequest.java b/src/main/java/kr/re/etri/security/jwt/payload/request/BaseSearchRequest.java new file mode 100644 index 0000000..bfc7f59 --- /dev/null +++ b/src/main/java/kr/re/etri/security/jwt/payload/request/BaseSearchRequest.java @@ -0,0 +1,28 @@ +package kr.re.etri.security.jwt.payload.request; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +@Getter +@Setter +public class BaseSearchRequest { + + private int page = 0; // 페이지 번호 (0부터 시작) + private int size = 10; // 한 페이지당 출력 건수 + + private String keyword; // 공통 키워드 검색 + + private String searchType; // 검색 유형 (예: "전체", "제목", "작성자" 등) + + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) + private LocalDate startDate; // 등록일자 검색 시작 + + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) + private LocalDate endDate; // 등록일자 검색 종료 + + private String sortField = "id"; // 정렬 기준 필드명 + private String sortDirection = "DESC"; // 정렬 방향: ASC / DESC +} diff --git a/src/main/java/kr/re/etri/security/jwt/payload/request/ProjectRequest.java b/src/main/java/kr/re/etri/security/jwt/payload/request/ProjectRequest.java new file mode 100644 index 0000000..55ea289 --- /dev/null +++ b/src/main/java/kr/re/etri/security/jwt/payload/request/ProjectRequest.java @@ -0,0 +1,19 @@ +package kr.re.etri.security.jwt.payload.request; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ProjectRequest { + private String prjCd; + private String prjNm; + private String prjDesc; + private String prjStartDt; + private String prjEndDt; + private String delYn; + private String regUserId; + private String regUserNm; + private String modUserId; + private String modUserNm; +} diff --git a/src/main/java/kr/re/etri/security/jwt/repository/ProjectRepository.java b/src/main/java/kr/re/etri/security/jwt/repository/ProjectRepository.java new file mode 100644 index 0000000..0145c9f --- /dev/null +++ b/src/main/java/kr/re/etri/security/jwt/repository/ProjectRepository.java @@ -0,0 +1,13 @@ +package kr.re.etri.security.jwt.repository; + +import kr.re.etri.security.jwt.entity.ProjectEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProjectRepository extends JpaRepository { + boolean existsByPrjCd(String prjCd); + + Page findByPrjNmContainingIgnoreCase(String keyword, Pageable pageable); + +} diff --git a/src/main/java/kr/re/etri/security/jwt/security/WebSecurityConfig.java b/src/main/java/kr/re/etri/security/jwt/security/WebSecurityConfig.java index 5f41c99..3aa8e06 100644 --- a/src/main/java/kr/re/etri/security/jwt/security/WebSecurityConfig.java +++ b/src/main/java/kr/re/etri/security/jwt/security/WebSecurityConfig.java @@ -83,22 +83,38 @@ public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter { // http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); // } +// @Bean +// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { +// http.csrf(AbstractHttpConfigurer::disable) +// .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) +// .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) +// .authorizeHttpRequests(auth -> +// auth.requestMatchers("/api/auth/**").permitAll() +// .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll() +// .requestMatchers("/api/test/**").permitAll() +// .anyRequest().authenticated() +// ); +// +// http.authenticationProvider(authenticationProvider()); +// +// http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); +// +// return http.build(); +// } + + // 임시 설정 @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) - .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> - auth.requestMatchers("/api/auth/**").permitAll() - .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll() - .requestMatchers("/api/test/**").permitAll() - .anyRequest().authenticated() - ); - - http.authenticationProvider(authenticationProvider()); + .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> + auth.anyRequest().permitAll() // 모든 요청 허용 + ); + http.authenticationProvider(authenticationProvider()); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); - + return http.build(); } } diff --git a/src/main/java/kr/re/etri/security/jwt/service/ProjectService.java b/src/main/java/kr/re/etri/security/jwt/service/ProjectService.java new file mode 100644 index 0000000..76e8187 --- /dev/null +++ b/src/main/java/kr/re/etri/security/jwt/service/ProjectService.java @@ -0,0 +1,70 @@ +package kr.re.etri.security.jwt.service; + +import kr.re.etri.security.jwt.entity.ProjectEntity; +import kr.re.etri.security.jwt.payload.request.BaseSearchRequest; +import kr.re.etri.security.jwt.payload.request.ProjectRequest; +import kr.re.etri.security.jwt.repository.ProjectRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ProjectService { + + private final ProjectRepository projectRepository; + + public List findAll() { + return projectRepository.findAll(); + } + + public Optional findById(Long id) { + return projectRepository.findById(id); + } + + public Page search(BaseSearchRequest request) { + Pageable pageable = PageRequest.of( + request.getPage(), + request.getSize(), + Sort.by(Sort.Direction.fromString(request.getSortDirection()), request.getSortField()) + ); + + if (request.getKeyword() != null && !request.getKeyword().isEmpty()) { + return projectRepository.findByPrjNmContainingIgnoreCase(request.getKeyword(), pageable); + } else { + return projectRepository.findAll(pageable); + } + } + + @Transactional + public ProjectEntity create(ProjectEntity project) { + if (projectRepository.existsByPrjCd(project.getPrjCd())) { + throw new IllegalArgumentException("중복된 프로젝트 코드입니다."); + } + return projectRepository.save(project); + } + + @Transactional + public Optional update(Long id, ProjectRequest dto) { + return projectRepository.findById(id) + .map(project -> { + BeanUtils.copyProperties(dto, project); + return projectRepository.save(project); + }); + } + + @Transactional + public boolean delete(Long id) { + if (!projectRepository.existsById(id)) { + return false; + } + projectRepository.deleteById(id); + return true; + } +}