diff --git a/document-parser/domain/value_objects.py b/document-parser/domain/value_objects.py index cf9b258..af973b5 100644 --- a/document-parser/domain/value_objects.py +++ b/document-parser/domain/value_objects.py @@ -40,11 +40,11 @@ class DocumentLifecycleState(StrEnum): class StoreKind(StrEnum): - """Backing technology of a Store. Today only OpenSearch is implemented; - the enum is here so future backends (Pinecone, Qdrant, pgvector) can be - added without touching the persistence schema.""" + """Backing technology of a Store. New backends plug in here without + touching the persistence schema.""" OPENSEARCH = "opensearch" + NEO4J = "neo4j" class DocumentStoreLinkState(StrEnum): diff --git a/document-parser/services/store_service.py b/document-parser/services/store_service.py index bee40a5..13e2000 100644 --- a/document-parser/services/store_service.py +++ b/document-parser/services/store_service.py @@ -87,13 +87,17 @@ def _validate_slug(slug: str) -> None: def _validate_config_for_kind(kind: StoreKind, config: dict) -> None: - """Per-kind config schema. Today only OpenSearch is wired; new kinds plug - in here without touching the rest of the pipeline.""" + """Per-kind config schema. New kinds plug in here without touching the + rest of the pipeline. Authentication for remote stores comes from the + deployment env (see `infra/settings.py`).""" if kind is StoreKind.OPENSEARCH: index_name = config.get("index_name") or config.get("indexName") if not isinstance(index_name, str) or not index_name.strip(): raise StoreValidationError("OpenSearch config requires a non-empty 'index_name'") - # Future: add LlamaIndex / LangChain / pgvector branches here. + elif kind is StoreKind.NEO4J: + index_name = config.get("index_name") or config.get("indexName") + if not isinstance(index_name, str) or not index_name.strip(): + raise StoreValidationError("Neo4j config requires a non-empty 'index_name'") class StoreService: diff --git a/document-parser/tests/test_store_service.py b/document-parser/tests/test_store_service.py index 31cab80..19b3996 100644 --- a/document-parser/tests/test_store_service.py +++ b/document-parser/tests/test_store_service.py @@ -110,6 +110,27 @@ class TestCreate: config={}, ) + async def test_create_neo4j_ok(self, service): + store = await service.create_store( + name="kg", + slug="kg", + kind=StoreKind.NEO4J, + embedder="bge-m3", + config={"index_name": "chunks-vec", "database": "neo4j"}, + ) + assert store.kind is StoreKind.NEO4J + assert store.config["index_name"] == "chunks-vec" + + async def test_create_neo4j_rejects_missing_index_name(self, service): + with pytest.raises(StoreValidationError): + await service.create_store( + name="kg", + slug="kg", + kind=StoreKind.NEO4J, + embedder="bge-m3", + config={}, + ) + async def test_create_default_clears_others(self, service): # The seeded 'default' store starts as is_default=True. new_default = await service.create_store( diff --git a/frontend/src/features/store/ui/Neo4jConfigForm.vue b/frontend/src/features/store/ui/Neo4jConfigForm.vue new file mode 100644 index 0000000..c710250 --- /dev/null +++ b/frontend/src/features/store/ui/Neo4jConfigForm.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/frontend/src/features/store/ui/StoreConfigForm.vue b/frontend/src/features/store/ui/StoreConfigForm.vue index 2db8a72..ed742de 100644 --- a/frontend/src/features/store/ui/StoreConfigForm.vue +++ b/frontend/src/features/store/ui/StoreConfigForm.vue @@ -10,6 +10,7 @@