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
8 changes: 4 additions & 4 deletions .github/workflows/CI-CD_Pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ jobs:
if: github.ref == 'refs/heads/main' # ✅ main 브랜치일 때만 실행
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ github.repository_owner }}
IMAGE_PREFIX: ${{ github.repository }}

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -227,14 +227,14 @@ jobs:
EOF

# EC2에서 GHCR 로그인
echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u doohyojeong --password-stdin
echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

# 최신 이미지 pull & 컨테이너 실행
docker pull ghcr.io/doohyojeong/${{ env.DOCKER_IMAGE_NAME }}:latest
docker pull ghcr.io/prgrms-web-devcourse-final-project/${{ env.DOCKER_IMAGE_NAME }}:latest
docker stop app1 2>/dev/null
docker rm app1 2>/dev/null
docker run --env-file /home/ec2-user/prod.env -d --name app1 -p 8080:8080 \
ghcr.io/doohyojeong/${{ env.DOCKER_IMAGE_NAME }}:latest
ghcr.io/prgrms-web-devcourse-final-project/${{ env.DOCKER_IMAGE_NAME }}:latest

# dangling image 정리 + .env 삭제
docker rmi $(docker images -f "dangling=true" -q)
Expand Down
7 changes: 7 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.5.5'
id 'io.spring.dependency-management' version '1.1.7'

id("org.jetbrains.kotlin.jvm") version "1.9.25"
id("com.google.devtools.ksp") version "1.9.25-1.0.20"
}

group = 'com.ai.lawyer'
Expand Down Expand Up @@ -57,6 +60,10 @@ dependencies {
// Annotation Processing (어노테이션 처리)
annotationProcessor 'org.projectlombok:lombok'

// Querydsl
implementation("io.github.openfeign.querydsl:querydsl-jpa:7.0")
ksp("io.github.openfeign.querydsl:querydsl-ksp-codegen:7.0")

// Testing (테스트)
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.ai.lawyer.domain.law.controller;

import com.ai.lawyer.domain.law.dto.LawSearchRequestDto;
import com.ai.lawyer.domain.law.dto.LawsDto;
import com.ai.lawyer.domain.law.entity.Law;
import com.ai.lawyer.domain.law.service.LawService;
import com.ai.lawyer.global.dto.PageResponseDto;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/law")
public class LawController {

private final LawService lawService;

// 법령 리스트 출력
@GetMapping(value = "/list")
public ResponseEntity<?> list(
@RequestParam String query,
@RequestParam int page
) throws Exception {
String lawList = lawService.getLawList(query, page);
return ResponseEntity.ok().body(lawList);
}


@GetMapping(value = "/list/save")
public ResponseEntity<?> getStatisticsCard(
@RequestParam String query,
@RequestParam int page
) throws Exception {
long startTime = System.currentTimeMillis();

lawService.saveLaw(query, page);

long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
System.out.println("saveLaw 실행 시간: " + elapsedTime + "ms");

return ResponseEntity.ok().body("Success");
}

@GetMapping("/{id}")
public ResponseEntity<Law> getFullLaw(@PathVariable Long id) {
Law law = lawService.getLawWithAllChildren(id);

return ResponseEntity.ok(law);
}


@PostMapping("/search")
public ResponseEntity<PageResponseDto> searchLaws(@RequestBody LawSearchRequestDto searchRequest) {
Page<LawsDto> laws = lawService.searchLaws(searchRequest);
PageResponseDto response = PageResponseDto.builder()
.content(laws.getContent())
.totalElements(laws.getTotalElements())
.totalPages(laws.getTotalPages())
.pageNumber(laws.getNumber())
.pageSize(laws.getSize())
.build();
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ai.lawyer.domain.law.dto;

import lombok.Builder;
import lombok.Data;

import java.time.LocalDate;

@Data
@Builder
public class LawSearchRequestDto {
private String lawName; // 법령명
private String lawField; // 법령분야
private String ministry; // 소관부처
private LocalDate promulgationDateStart; // 공포일자 시작
private LocalDate promulgationDateEnd; // 공포일자 종료
private LocalDate enforcementDateStart; // 시행일자 시작
private LocalDate enforcementDateEnd; // 시행일자 종료
private int pageNumber; // 페이지 번호
private int pageSize; // 페이지 크기
}
26 changes: 26 additions & 0 deletions backend/src/main/java/com/ai/lawyer/domain/law/dto/LawsDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.ai.lawyer.domain.law.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

import java.time.LocalDate;

@Data
@Builder
@AllArgsConstructor
public class LawsDto {
private Long id;

private String lawName; // 법령명

private String lawField; // 법령분야

private String ministry; // 소관부처

private String promulgationNumber; // 공포번호

private LocalDate promulgationDate; // 공포일자

private LocalDate enforcementDate; // 시행일자
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ai.lawyer.domain.law.repository;

import com.ai.lawyer.domain.law.entity.Hang;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface HangRepository extends JpaRepository<Hang, Long> {

// Hang + Ho만 페치
@EntityGraph(attributePaths = "hoList")
List<Hang> findByJoId(Long joId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ai.lawyer.domain.law.repository;

import com.ai.lawyer.domain.law.entity.Ho;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface HoRepository extends JpaRepository<Ho, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ai.lawyer.domain.law.repository;

import com.ai.lawyer.domain.law.entity.Jang;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface JangRepository extends JpaRepository<Jang, Long> {
// Jang + Jo만 페치
@EntityGraph(attributePaths = "joList")
List<Jang> findByLawId(Long lawId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ai.lawyer.domain.law.repository;

import com.ai.lawyer.domain.law.entity.Jo;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface JoRepository extends JpaRepository<Jo, Long> {

// Jo + Hang만 페치
@EntityGraph(attributePaths = "hangList")
List<Jo> findByJangId(Long jangId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ai.lawyer.domain.law.repository;

import com.ai.lawyer.domain.law.entity.Law;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface LawRepository extends JpaRepository<Law, Long>, LawRepositoryCustom {

// Law + Jang만 페치
@EntityGraph(attributePaths = "jangList")
Optional<Law> findWithJangById(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ai.lawyer.domain.law.repository;

import com.ai.lawyer.domain.law.dto.LawSearchRequestDto;
import com.ai.lawyer.domain.law.dto.LawsDto;
import org.springframework.data.domain.Page;

public interface LawRepositoryCustom {
Page<LawsDto> searchLaws(LawSearchRequestDto searchRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.ai.lawyer.domain.law.repository;

import com.ai.lawyer.domain.law.dto.LawSearchRequestDto;
import com.ai.lawyer.domain.law.dto.LawsDto;
import com.ai.lawyer.domain.law.entity.QLaw;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import java.util.List;

@Repository
@RequiredArgsConstructor
public class LawRepositoryCustomImpl implements LawRepositoryCustom {

private final JPAQueryFactory queryFactory;

private QLaw law = QLaw.law;

@Override
public Page<LawsDto> searchLaws(LawSearchRequestDto searchRequest) {
BooleanBuilder builder = new BooleanBuilder();

// 법령명 조건 (부분 검색)
if (StringUtils.hasText(searchRequest.getLawName())) {
builder.and(law.getLawName().containsIgnoreCase(searchRequest.getLawName()));
}

// 소관부처 조건 (완전 일치)
if (StringUtils.hasText(searchRequest.getMinistry())) {
builder.and(law.getMinistry().eq(searchRequest.getMinistry()));
}

// 법령분야 조건 (완전 일치)
if (StringUtils.hasText(searchRequest.getLawField())) {
builder.and(law.getLawField().eq(searchRequest.getLawField()));
}

// 시행일자 범위 조건
if (searchRequest.getEnforcementDateStart() != null &&
searchRequest.getEnforcementDateEnd() != null) {
builder.and(law.getEnforcementDate().between(
searchRequest.getEnforcementDateStart(),
searchRequest.getEnforcementDateEnd()));
} else if (searchRequest.getEnforcementDateStart() != null) {
builder.and(law.getEnforcementDate().goe(searchRequest.getEnforcementDateStart()));
} else if (searchRequest.getEnforcementDateEnd() != null) {
builder.and(law.getEnforcementDate().loe(searchRequest.getEnforcementDateEnd()));
}

// 공포일자 범위 조건
if (searchRequest.getPromulgationDateStart() != null &&
searchRequest.getPromulgationDateEnd() != null) {
builder.and(law.getPromulgationDate().between(
searchRequest.getPromulgationDateStart(),
searchRequest.getPromulgationDateEnd()));
} else if (searchRequest.getPromulgationDateStart() != null) {
builder.and(law.getPromulgationDate().goe(searchRequest.getPromulgationDateStart()));
} else if (searchRequest.getPromulgationDateEnd() != null) {
builder.and(law.getPromulgationDate().loe(searchRequest.getPromulgationDateEnd()));
}

Pageable pageable = PageRequest.of(searchRequest.getPageNumber(), searchRequest.getPageSize());

// DTO 프로젝션 조회
JPAQuery<LawsDto> query = queryFactory
.select(Projections.constructor(
LawsDto.class,
law.getId(),
law.getLawName(),
law.getLawField(),
law.getMinistry(),
law.getPromulgationNumber(),
law.getPromulgationDate(),
law.getEnforcementDate()
))
.from(law)
.where(builder)
.offset(pageable.getOffset())
.limit(pageable.getPageSize());

List<LawsDto> content = query.fetch();

// 전체 개수 조회
Long total = queryFactory
.select(law.count())
.from(law)
.where(builder)
.fetchOne();

return new PageImpl<>(content, pageable, total != null ? total : 0);
}
}
Loading