diff --git a/.github/workflows/ci-java.yml b/.github/workflows/ci-java.yml new file mode 100644 index 00000000..1a4faaa2 --- /dev/null +++ b/.github/workflows/ci-java.yml @@ -0,0 +1,47 @@ +name: CI (Java) + +on: + push: + branches: + - main + paths: + - "apps/user-service/**" + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - main + - develop + - release/** + paths: + - "apps/user-service/**" + +permissions: + contents: read + packages: write + security-events: write + checks: write + pull-requests: write + +jobs: + spotless-check: + name: Lint Check + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'gradle' + + - name: Grant execute permission for Gradle wrapper + run: chmod +x ./gradlew + working-directory: apps/user-service + + - name: Run Spotless Check + run: ./gradlew spotlessCheck + working-directory: apps/user-service \ No newline at end of file diff --git a/apps/pre-processing-service/app/config/logging/RepositoryLoggerMiddleware.py b/apps/pre-processing-service/app/config/logging/RepositoryLoggerMiddleware.py new file mode 100644 index 00000000..703834a6 --- /dev/null +++ b/apps/pre-processing-service/app/config/logging/RepositoryLoggerMiddleware.py @@ -0,0 +1,117 @@ +import time +from typing import Dict, Any, List +from fastapi import Request +from loguru import logger +from contextvars import ContextVar + +trace_id_context: ContextVar[str] = ContextVar('trace_id', default="NO_TRACE_ID") + +class RepositoryLoggingDependency: + """ + 레포지토리 로깅을 위한 의존성 클래스 + :param repository_type: 레포지토리 유형 (예: "VECTOR_DB", "RDB", "REDIS") + :param track_params: 추적할 매개변수 이름 목록 + """ + + def __init__(self, repository_type: str, track_params: List[str] = None): + self.repository_type = repository_type + self.track_params = track_params or [] + + async def __call__(self, request: Request): + """ + 의존성 주입 시 호출되는 메서드 + :param request: FastAPI Request 객체 + :return: 레포지토리 유형과 추출된 매개변수 딕셔너리 + """ + trace_id = trace_id_context.get("NO_TRACE_ID") + start_time = time.time() + + # 파라미터 추출 + params = await self._extract_params(request) + param_str = "" + if params: + param_strs = [f"{k}={v}" for k, v in params.items()] + param_str = " " + " ".join(param_strs) + + logger.info(f"[{self.repository_type}_START] trace_id={trace_id}{param_str}") + + # 응답 시 사용할 정보를 request.state에 저장 + request.state.repository_type = self.repository_type + request.state.start_time = start_time + request.state.param_str = param_str + + return {"repository_type": self.repository_type, "params": params} + + async def _extract_params(self, request: Request) -> Dict[str, Any]: + """ + 요청에서 추적 파라미터 추출 + :param request: FastAPI Request 객체 + :return: 추출된 매개변수 딕셔너리 + """ + params = {} + + try: + # Query Parameters 추출 + for key, value in request.query_params.items(): + if key in self.track_params: + params[key] = value + + # JSON Body 추출 + try: + json_body = await request.json() + if json_body: + for key, value in json_body.items(): + if key in self.track_params: + if isinstance(value, str) and len(value) > 50: + params[f"{key}_length"] = len(value) + elif isinstance(value, list): + params[f"{key}_count"] = len(value) + else: + params[key] = value + except: + pass + except: + pass + + return params + + +# 레포지토리별 의존성 인스턴스 생성 +vector_db_dependency = RepositoryLoggingDependency("VECTOR_DB", ["query", "embeddings", "top_k", "collection", "filters"]) +rdb_dependency = RepositoryLoggingDependency("RDB", ["table", "where_clause", "limit", "data"]) +redis_dependency = RepositoryLoggingDependency("REDIS", ["key", "value", "ttl", "pattern"]) +elasticsearch_dependency = RepositoryLoggingDependency("ELASTICSEARCH", ["index", "query", "size", "document"]) + + +# 응답 로깅을 위한 의존성 +async def log_repository_response(request: Request): + """ + 레포지토리 응답 시 성공 로그 기록 + :param request: FastAPI Request 객체 + """ + if hasattr(request.state, 'repository_type'): + trace_id = trace_id_context.get("NO_TRACE_ID") + duration = time.time() - request.state.start_time + logger.info( + f"[{request.state.repository_type}_SUCCESS] trace_id={trace_id} execution_time={duration:.4f}s{request.state.param_str}") + return None + + +""" +라우터 예시 +@router.post("/search") +async def vector_search( + query: str, + top_k: int = 10, + request: Request = None, + _: None = Depends(vector_db_dependency), # 직접 의존성 주입 + __: None = Depends(log_repository_response) +): + +또는 라우터 레벨에서: +vector_router = APIRouter( + prefix="/vector", + tags=["vector"], + dependencies=[Depends(vector_db_dependency)] +) +""" \ No newline at end of file diff --git a/apps/pre-processing-service/app/config/logging/ServiceLoggerMiddleware.py b/apps/pre-processing-service/app/config/logging/ServiceLoggerMiddleware.py new file mode 100644 index 00000000..5e4816a1 --- /dev/null +++ b/apps/pre-processing-service/app/config/logging/ServiceLoggerMiddleware.py @@ -0,0 +1,109 @@ +import time +from typing import Dict, Any, List +from fastapi import Request +from loguru import logger +from contextvars import ContextVar + +trace_id_context: ContextVar[str] = ContextVar('trace_id', default="NO_TRACE_ID") + + +class ServiceLoggingDependency: + """ + 서비스 로깅을 위한 의존성 클래스 + :param service_type: 서비스 유형 (예: "CHUNKING", "PARSING", "EMBEDDING") + :param track_params: 추적할 매개변수 이름 목록 + """ + + def __init__(self, service_type: str, track_params: List[str] = None): + self.service_type = service_type + self.track_params = track_params or [] + + async def __call__(self, request: Request): + """ + 의존성 주입 시 호출되는 메서드 + :param request: FastAPI Request 객체 + :return: 서비스 유형과 추출된 매개변수 딕셔너리 + """ + trace_id = trace_id_context.get("NO_TRACE_ID") + start_time = time.time() + + # 파라미터 추출 + params = await self._extract_params(request) + param_str = "" + if params: + param_strs = [f"{k}={v}" for k, v in params.items()] + param_str = " " + " ".join(param_strs) + + logger.info(f"[{self.service_type}_START] trace_id={trace_id}{param_str}") + + # 응답 시 사용할 정보를 request.state에 저장 + request.state.service_type = self.service_type + request.state.start_time = start_time + request.state.param_str = param_str + + return {"service_type": self.service_type, "params": params} + + async def _extract_params(self, request: Request) -> Dict[str, Any]: + """ + 요청에서 추적 파라미터 추출 + :param request: FastAPI Request 객체 + :return: 추출된 매개변수 딕셔너리 + """ + params = {} + + try: + # Query Parameters 추출 + for key, value in request.query_params.items(): + if key in self.track_params: + params[key] = value + + # JSON Body 추출 + try: + json_body = await request.json() + if json_body: + for key, value in json_body.items(): + if key in self.track_params: + if isinstance(value, str) and len(value) > 50: + params[f"{key}_length"] = len(value) + elif isinstance(value, list): + params[f"{key}_count"] = len(value) + else: + params[key] = value + except: + pass + except: + pass + + return params + + +# 서비스별 의존성 인스턴스 생성 +chunking_dependency = ServiceLoggingDependency("CHUNKING", ["text", "chunk_size", "overlap"]) +parsing_dependency = ServiceLoggingDependency("PARSING", ["file_path", "file_type", "document"]) +embedding_dependency = ServiceLoggingDependency("EMBEDDING", ["chunks", "model_name", "batch_size"]) + +# 응답 로깅을 위한 의존성 +async def log_service_response(request: Request): + """ + 서비스 응답 시 성공 로그 기록 + :param request: FastAPI Request 객체 + """ + if hasattr(request.state, 'service_type'): + trace_id = trace_id_context.get("NO_TRACE_ID") + duration = time.time() - request.state.start_time + logger.info( + f"[{request.state.service_type}_SUCCESS] trace_id={trace_id} execution_time={duration:.4f}s{request.state.param_str}") + return None + +""" +라우터 예시 +@router.post("/chunk") +async def chunk_text( + text: str, + chunk_size: int = 100, + overlap: int = 20, + request: Request = None, + _: None = Depends(chunking_dependency), # 직접 의존성 주입 + __: None = Depends(log_service_response) +): +""" \ No newline at end of file diff --git a/apps/pre-processing-service/test_main.http b/apps/pre-processing-service/test_main.http new file mode 100644 index 00000000..a2d81a92 --- /dev/null +++ b/apps/pre-processing-service/test_main.http @@ -0,0 +1,11 @@ +# Test your FastAPI endpoints + +GET http://127.0.0.1:8000/ +Accept: application/json + +### + +GET http://127.0.0.1:8000/hello/User +Accept: application/json + +### diff --git a/apps/user-service/build.gradle b/apps/user-service/build.gradle index f373d429..17c949f3 100644 --- a/apps/user-service/build.gradle +++ b/apps/user-service/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.5.4' id 'io.spring.dependency-management' version '1.1.7' + id 'com.diffplug.spotless' version '7.2.1' } group = 'com.gltkorea' @@ -43,3 +44,21 @@ dependencies { tasks.named('test') { useJUnitPlatform() } + +spotless { + java { + googleJavaFormat('1.17.0') + importOrder('java', 'javax', 'org', 'com', '', 'com.movement') + endWithNewline() + removeUnusedImports() + encoding('UTF-8') + + targetExclude("**/generated/**", "**/Q*.java") + } + format 'misc', { + target '**/*.gradle', '**/*.md', '**/.gitignore' + trimTrailingWhitespace() + indentWithTabs() + endWithNewline() + } +} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/UserServiceApplication.java b/apps/user-service/src/main/java/com/gltkorea/icebang/UserServiceApplication.java index 110b7d92..b5fcbef4 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/UserServiceApplication.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/UserServiceApplication.java @@ -6,8 +6,7 @@ @SpringBootApplication public class UserServiceApplication { - public static void main(String[] args) { - SpringApplication.run(UserServiceApplication.class, args); - } - + public static void main(String[] args) { + SpringApplication.run(UserServiceApplication.class, args); + } } diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/config/SecurityConfig.java b/apps/user-service/src/main/java/com/gltkorea/icebang/config/SecurityConfig.java new file mode 100644 index 00000000..4d7ac371 --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/config/SecurityConfig.java @@ -0,0 +1,6 @@ +package com.gltkorea.icebang.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SecurityConfig {} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/model/UserAccountPrincipal.java b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/model/UserAccountPrincipal.java new file mode 100644 index 00000000..abca6682 --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/model/UserAccountPrincipal.java @@ -0,0 +1,27 @@ +package com.gltkorea.icebang.domain.user.model; + +import java.util.Collection; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import lombok.Builder; + +@Builder +public class UserAccountPrincipal implements UserDetails { + @Override + public Collection getAuthorities() { + return List.of(); + } + + @Override + public String getPassword() { + return ""; + } + + @Override + public String getUsername() { + return ""; + } +} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/UserAuthService.java b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/UserAuthService.java new file mode 100644 index 00000000..dc8396ee --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/UserAuthService.java @@ -0,0 +1,5 @@ +package com.gltkorea.icebang.domain.user.service; + +import org.springframework.security.core.userdetails.UserDetailsService; + +public interface UserAuthService extends UserDetailsService {} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/UserAuthServiceImpl.java b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/UserAuthServiceImpl.java new file mode 100644 index 00000000..272e289e --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/domain/user/service/UserAuthServiceImpl.java @@ -0,0 +1,13 @@ +package com.gltkorea.icebang.domain.user.service; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import com.gltkorea.icebang.domain.user.model.UserAccountPrincipal; + +public class UserAuthServiceImpl implements UserAuthService { + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return UserAccountPrincipal.builder().build(); + } +} diff --git a/apps/user-service/src/main/resources/application-develop.yml b/apps/user-service/src/main/resources/application-develop.yml new file mode 100644 index 00000000..e69de29b diff --git a/apps/user-service/src/main/resources/application-production.yml b/apps/user-service/src/main/resources/application-production.yml new file mode 100644 index 00000000..e69de29b diff --git a/apps/user-service/src/main/resources/application-test.yml b/apps/user-service/src/main/resources/application-test.yml new file mode 100644 index 00000000..e69de29b diff --git a/apps/user-service/src/main/resources/application.yml b/apps/user-service/src/main/resources/application.yml new file mode 100644 index 00000000..e69de29b diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/TestUserServiceApplication.java b/apps/user-service/src/test/java/com/gltkorea/icebang/TestUserServiceApplication.java index 624e8df7..f53fa0a9 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/TestUserServiceApplication.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/TestUserServiceApplication.java @@ -4,8 +4,9 @@ public class TestUserServiceApplication { - public static void main(String[] args) { - SpringApplication.from(UserServiceApplication::main).with(TestcontainersConfiguration.class).run(args); - } - + public static void main(String[] args) { + SpringApplication.from(UserServiceApplication::main) + .with(TestcontainersConfiguration.class) + .run(args); + } } diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/TestcontainersConfiguration.java b/apps/user-service/src/test/java/com/gltkorea/icebang/TestcontainersConfiguration.java index a302d914..bbe8ed02 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/TestcontainersConfiguration.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/TestcontainersConfiguration.java @@ -3,6 +3,4 @@ import org.springframework.boot.test.context.TestConfiguration; @TestConfiguration(proxyBeanMethods = false) -class TestcontainersConfiguration { - -} +class TestcontainersConfiguration {} diff --git a/apps/user-service/src/test/java/com/gltkorea/icebang/UserServiceApplicationTests.java b/apps/user-service/src/test/java/com/gltkorea/icebang/UserServiceApplicationTests.java index c83e584d..26cfc86b 100644 --- a/apps/user-service/src/test/java/com/gltkorea/icebang/UserServiceApplicationTests.java +++ b/apps/user-service/src/test/java/com/gltkorea/icebang/UserServiceApplicationTests.java @@ -8,8 +8,6 @@ @SpringBootTest class UserServiceApplicationTests { - @Test - void contextLoads() { - } - + @Test + void contextLoads() {} }