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
7 changes: 4 additions & 3 deletions core/src/main/java/com/opencontext/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
);

// Add API Key authentication filter for Admin APIs
http.addFilterBefore(apiKeyAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

log.info("Security configuration completed successfully");
// TEMPORARY: Disabled for testing - RE-ENABLE BEFORE PRODUCTION
// http.addFilterBefore(apiKeyAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

log.info("Security configuration completed successfully (API Key authentication DISABLED)");
Comment on lines +80 to +83

Copilot AI Dec 15, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security authentication has been disabled with a temporary comment. This is a critical security vulnerability that leaves the API completely unprotected. If this is genuinely needed for development, it should be controlled via a configuration property, not commented out in code. The comment states 'RE-ENABLE BEFORE PRODUCTION' but there's no guarantee this will happen - consider using a feature flag or environment-based configuration instead.

Copilot uses AI. Check for mistakes.
return http.build();
}

Expand Down
75 changes: 48 additions & 27 deletions core/src/main/java/com/opencontext/controller/SourceController.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,12 @@ public ResponseEntity<CommonResponse<String>> deleteSourceDocument(@PathVariable
}
}

@Value("${app.rag.service.url:http://open-context-rag:8001}")
private String ragServiceUrl;

/**
* Executes the document ingestion pipeline asynchronously.
* ๋ฌธ์„œ ์ˆ˜์ง‘ ํŒŒ์ดํ”„๋ผ์ธ์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
* RAG ์„œ๋น„์Šค (FastAPI)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
*/
@Async
@Transactional
Expand All @@ -253,41 +257,58 @@ public void processIngestionPipeline(UUID documentId) {
try {
// 1. Update status to PARSING
fileStorageService.updateDocumentStatus(documentId, IngestionStatus.PARSING);

// 2. Parse document using Unstructured API
var parsedElements = documentParsingService.parseDocument(documentId);
log.info("Document parsing completed: id={}, elements={}", documentId, parsedElements.size());

// 3. Update status to CHUNKING
fileStorageService.updateDocumentStatus(documentId, IngestionStatus.CHUNKING);

// 4. Split into chunks
var structuredChunks = chunkingService.createChunks(documentId, parsedElements);
log.info("Document chunking completed: id={}, chunks={}", documentId, structuredChunks.size());
// 2. Get document info
SourceDocument document = sourceDocumentRepository.findById(documentId)
.orElseThrow(() -> new BusinessException(ErrorCode.SOURCE_DOCUMENT_NOT_FOUND,
"Document not found: " + documentId));

// 3. Generate presigned MinIO URL (valid for 1 hour)
String presignedUrl = fileStorageService.generatePresignedUrl(document.getFileStoragePath());
log.debug("Generated presigned URL for document: id={}, filename={}", documentId, document.getOriginalFilename());

// 4. Call RAG service
com.opencontext.dto.ProcessDocumentRequest request = com.opencontext.dto.ProcessDocumentRequest.builder()
.documentId(documentId.toString())
.fileUrl(presignedUrl)
.filename(document.getOriginalFilename())
.fileType(document.getFileType())
.build();

// 5. Update status to EMBEDDING
fileStorageService.updateDocumentStatus(documentId, IngestionStatus.EMBEDDING);

// 6. Generate embeddings
var embeddedChunks = embeddingService.generateEmbeddings(documentId, structuredChunks);
log.info("Embedding generation completed: id={}, embedded_chunks={}", documentId, embeddedChunks.size());
String ragProcessUrl = ragServiceUrl + "/api/v1/process";
log.info("Calling RAG service: url={}, documentId={}", ragProcessUrl, documentId);

// 7. Update status to INDEXING
fileStorageService.updateDocumentStatus(documentId, IngestionStatus.INDEXING);

// 8. Store in Elasticsearch and PostgreSQL
indexingService.indexChunks(documentId, embeddedChunks);
log.info("Document indexing completed: id={}", documentId);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<com.opencontext.dto.ProcessDocumentRequest> requestEntity = new HttpEntity<>(request, headers);

ResponseEntity<com.opencontext.dto.ProcessDocumentResponse> response = restTemplate.postForEntity(
ragProcessUrl,
requestEntity,
com.opencontext.dto.ProcessDocumentResponse.class
);
Comment on lines +285 to +289

Copilot AI Dec 15, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation for the RestTemplate call to the RAG service. If the RAG service is down or returns an error status code, the restTemplate.postForEntity() could throw an exception that should be properly handled. Consider adding explicit error handling for HTTP errors, timeouts, and connection failures.

Copilot uses AI. Check for mistakes.

// 5. Check response
if (response.getBody() != null && response.getBody().isSuccess()) {
com.opencontext.dto.ProcessDocumentResponse ragResponse = response.getBody();

// Update status to COMPLETED
fileStorageService.updateDocumentStatusToCompleted(documentId);

log.info("RAG processing completed: documentId={}, chunks={}, status={}",
documentId, ragResponse.getChunksProcessed(), ragResponse.getStatus());
} else {
String errorMessage = response.getBody() != null ? response.getBody().getErrorMessage() : "Unknown error";
throw new BusinessException(ErrorCode.INGESTION_PIPELINE_FAILED,
"RAG service processing failed: " + errorMessage);
}

// 9. Update status to COMPLETED
fileStorageService.updateDocumentStatusToCompleted(documentId);

log.info("Ingestion pipeline completed successfully: documentId={}", documentId);

} catch (Exception e) {
log.error("Ingestion pipeline failed: documentId={}", documentId, e);
fileStorageService.updateDocumentStatusToError(documentId, e.getMessage());
throw new BusinessException(ErrorCode.INGESTION_PIPELINE_FAILED,
throw new BusinessException(ErrorCode.INGESTION_PIPELINE_FAILED,
"Ingestion pipeline failed: " + e.getMessage());
}
}
Expand Down
37 changes: 37 additions & 0 deletions core/src/main/java/com/opencontext/dto/ProcessDocumentRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.opencontext.dto;

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

/**
* Request DTO for RAG service document processing endpoint.
* Sent from core/ to rag/ FastAPI service.
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProcessDocumentRequest {

/**
* Document UUID
*/
private String documentId;

/**
* Presigned MinIO URL for downloading the file
*/
private String fileUrl;

/**
* Original filename
*/
private String filename;

/**
* File type (PDF, MARKDOWN, TEXT)
*/
private String fileType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.opencontext.dto;

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

/**
* Response DTO from RAG service document processing endpoint.
* Received by core/ from rag/ FastAPI service.
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProcessDocumentResponse {

/**
* Whether processing completed successfully
*/
private boolean success;

/**
* Document UUID
*/
private String documentId;

/**
* Number of chunks created and indexed
*/
private int chunksProcessed;

/**
* Processing status (COMPLETED or ERROR)
*/
private String status;

/**
* Error message if processing failed
*/
private String errorMessage;
}
30 changes: 30 additions & 0 deletions core/src/main/java/com/opencontext/service/FileStorageService.java
Original file line number Diff line number Diff line change
Expand Up @@ -678,4 +678,34 @@ private SourceDocumentDto convertToDto(SourceDocument document) {
.updatedAt(document.getUpdatedAt())
.build();
}

/**
* Generates a presigned GET URL for downloading a file from MinIO.
* The URL is valid for 1 hour.
*
* @param objectKey the MinIO object key
* @return presigned GET URL
*/
public String generatePresignedUrl(String objectKey) {
try {
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
.method(io.minio.http.Method.GET)
.bucket(minioConfig.getBucketName())
.object(objectKey)
.expiry(3600) // 1 hour in seconds
.build();

String presignedUrl = minioClient.getPresignedObjectUrl(args);

log.debug("Generated presigned URL: objectKey={}, expirySeconds=3600", objectKey);

return presignedUrl;

} catch (Exception e) {
log.error("Failed to generate presigned URL: objectKey={}, error={}",
objectKey, e.getMessage(), e);
throw new BusinessException(ErrorCode.STORAGE_ERROR,
"Failed to generate presigned URL: " + e.getMessage());
}
}
}
14 changes: 9 additions & 5 deletions core/src/main/java/com/opencontext/service/IndexingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,21 @@ private Map<String, Object> createElasticsearchDocumentPRD(StructuredChunk chunk
metadata.put("sequenceInDocument", 0); // default value
metadata.put("language", "ko"); // Korean default value

// Reflect actual file type by querying from SourceDocument
// ์‹ค์ œ ํŒŒ์ผ ํƒ€์ž…๊ณผ ์›๋ณธ ํŒŒ์ผ๋ช…์„ SourceDocument์—์„œ ์กฐํšŒํ•˜์—ฌ ๋ฐ˜์˜
String resolvedFileType = "UNKNOWN";
String originalFilename = "";
try {
UUID srcId = UUID.fromString(chunk.getDocumentId());
resolvedFileType = sourceDocumentRepository.findById(srcId)
.map(SourceDocument::getFileType)
.orElse("UNKNOWN");
SourceDocument sourceDoc = sourceDocumentRepository.findById(srcId).orElse(null);
if (sourceDoc != null) {
resolvedFileType = sourceDoc.getFileType();
originalFilename = sourceDoc.getOriginalFilename();
}
} catch (Exception e) {
log.warn("Failed to resolve fileType for documentId={}, defaulting to UNKNOWN", chunk.getDocumentId());
log.warn("Failed to resolve fileType and originalFilename for documentId={}, using defaults", chunk.getDocumentId());
}
metadata.put("fileType", resolvedFileType);
metadata.put("originalFilename", originalFilename);

// Handle breadcrumbs (default: empty array)
metadata.put("breadcrumbs", Arrays.asList()); // empty array default value
Expand Down
8 changes: 6 additions & 2 deletions core/src/main/java/com/opencontext/service/SearchService.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,15 @@ private Map<String, Object> executeElasticsearchQuery(String query, List<Float>
*/
private Map<String, Object> buildHybridSearchQuery(String query, List<Float> queryEmbedding, int topK) {

// BM25 keyword search query
// BM25 ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ
Map<String, Object> bm25Query = Map.of(
"multi_match", Map.of(
"query", query,
"fields", Arrays.asList("content^2", "metadata.title^1.5"),
"fields", Arrays.asList(
"metadata.originalFilename^3.0", // ํŒŒ์ผ๋ช… (3๋ฐฐ ๊ฐ€์ค‘์น˜ - ๊ฐ€์žฅ ๋†’์Œ)
"content^2.0", // ๋ณธ๋ฌธ ๋‚ด์šฉ (2๋ฐฐ ๊ฐ€์ค‘์น˜)
"metadata.title^1.5" // ์ฒญํฌ ์ œ๋ชฉ (1.5๋ฐฐ ๊ฐ€์ค‘์น˜)
),
"type", "best_fields",
"fuzziness", "AUTO",
"boost", bm25Weight
Expand Down
66 changes: 32 additions & 34 deletions core/src/main/resources/application-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ spring:
application:
name: open-context-core

# Database Configuration (Docker)
profiles:
active: dev

# Database Configuration (Development)

Copilot AI Dec 15, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment incorrectly describes the configuration as 'Docker' when it's actually for local development based on the values (localhost URLs). The profile is also set to 'dev' on line 6. Update the comment to accurately reflect this is a development configuration.

Copilot uses AI. Check for mistakes.
datasource:
url: jdbc:postgresql://postgres:5432/opencontext
username: user
password: password
url: jdbc:postgresql://localhost:5432/opencontext
username: postgres
password: 3482
Comment on lines +11 to +12

Copilot AI Dec 15, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded database credentials are exposed in the configuration file. The username 'postgres' and password '3482' should be externalized to environment variables or a secure secrets management system. This is a security risk, especially if this file is committed to version control.

Suggested change
username: postgres
password: 3482
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:}

Copilot uses AI. Check for mistakes.
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 10
Expand All @@ -15,67 +18,62 @@ spring:
# JPA Configuration
jpa:
hibernate:
ddl-auto: validate
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true

# Flyway Configuration (Docker)
# Flyway Configuration
flyway:
baseline-on-migrate: true
locations: classpath:db/migration
validate-on-migrate: true

# Servlet Configuration
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB

# Application-specific Configuration (Docker)
# Application-specific Configuration
app:
# Elasticsearch Configuration (Docker)
# RAG Service Configuration
rag:
service:
url: http://localhost:8001

# Elasticsearch Configuration
elasticsearch:
url: http://elasticsearch:9200
url: http://localhost:9200
index: document_chunks_index
# Ollama Configuration (Docker)

# Ollama Configuration
ollama:
api:
url: http://ollama:11434
url: http://localhost:11434
embedding:
model: dengcao/Qwen3-Embedding-0.6B:F16
model: bge-m3:latest

# Embedding Configuration
embedding:
batch-size: 2 # ๋” ์ž‘์€ ๋ฐฐ์น˜๋กœ ์•ˆ์ •์„ฑ ์šฐ์„ 
batch-size: 10

# Search Configuration
search:
snippet-max-length: 50
bm25-weight: 0.3
vector-weight: 0.7

# Content Configuration
content:
default-max-tokens: 25000
tokenizer: tiktoken-cl100k_base

# Unstructured API Configuration
unstructured:
api:
url: http://unstructured-api:8000

# MinIO Configuration (Docker)
# MinIO Configuration (Development)
minio:
endpoint: http://minio:9000
endpoint: http://localhost:9000
access-key: minioadmin
secret-key: minioadmin123!
secret-key: minioadmin
bucket-name: opencontext-documents


# Unstructured.io Configuration (Development)
unstructured:
base-url: http://localhost:8000

# Application Configuration
opencontext:
Expand All @@ -95,8 +93,8 @@ management:
health:
show-details: when-authorized
health:
elasticsearch:
enabled: false
elasticsearch:
enabled: false # RAG ์„œ๋น„์Šค๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ–ˆ์œผ๋ฏ€๋กœ ๋น„ํ™œ์„ฑํ™”

# Logging Configuration
logging:
Expand Down
Loading