Skip to content

Commit

Permalink
Added SPARQL endpoint and full-text search.
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-bate committed Sep 4, 2024
1 parent d6aa552 commit 275ef3d
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 3 deletions.
7 changes: 6 additions & 1 deletion firm_server/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ class Context:


@click.group(context_settings=dict(auto_envvar_prefix="FIRM"))
@click.option("--config", type=click.File("r"), envvar="FIRM_CONFIG")
@click.option(
"--config",
type=click.File("r"),
envvar="FIRM_CONFIG",
)
@click.option(
"--storage",
"storage_key",
type=click.Choice(STORE_DRIVERS),
default="filesystem",
show_default=True,
envvar="FIRM_STORAGE",
)
@click.pass_context
def cli(ctx: click.Context, config: click.File, storage_key: str):
Expand Down
70 changes: 68 additions & 2 deletions firm_server/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
from firm.services.activitypub import ActivityPubService, ActivityPubTenant
from firm.services.nodeinfo import nodeinfo_index, nodeinfo_version
from firm.services.webfinger import webfinger
from firm_ld.search import IndexedResource, SearchEngine
from firm_ld.sparql import create_sparql_endpoint
from firm_ld.store import RdfResourceStore
from starlette.exceptions import HTTPException
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.responses import PlainTextResponse as StarlettePlainTextResponse
from starlette.responses import Response
from starlette.routing import Match, Route, Scope
from starlette.routing import Match, Mount, Route, Scope

from firm_server.adapters import (
AuthenticationBackendAdapter,
Expand Down Expand Up @@ -206,6 +209,48 @@ def matches(self, scope: Scope) -> tuple[Match, Scope]:
)


def _rdf_search(store: RdfResourceStore) -> HttpResponse:
# TODO support named graphs for search
search_engine = SearchEngine(store.graph)
search_engine.add_index(
IndexedResource(
"https://www.w3.org/ns/activitystreams#Note",
[
"https://www.w3.org/ns/activitystreams#content",
"https://www.w3.org/ns/activitystreams#summary",
],
[
"https://www.w3.org/ns/activitystreams#content",
"https://www.w3.org/ns/activitystreams#summary",
],
)
)
search_engine.add_index(
IndexedResource(
"https://www.w3.org/ns/activitystreams#Person",
[
"https://www.w3.org/ns/activitystreams#summary",
],
[
"https://www.w3.org/ns/activitystreams#summary",
"https://www.w3.org/ns/activitystreams#name",
"https://www.w3.org/ns/activitystreams#preferredUsername",
],
)
)
log.info("Indexing RDF store")
search_engine.update_index()

def _search(request: HttpRequest) -> HttpResponse:
# TODO Need to expose query params on HttpRequest
return JSONResponse(
search_engine.search(request.query_params["q"]),
headers={"Access-Control-Allow-Origin", "*"},
)

return _search


def get_routes(store: ResourceStore, config: ServerConfig):
activitypub_service = ActivityPubService(
[
Expand Down Expand Up @@ -240,7 +285,7 @@ def get_routes(store: ResourceStore, config: ServerConfig):
)
],
)
return [
routes = [
Route("/.well-known/webfinger", endpoint=_adapt_endpoint(webfinger, store)),
Route("/.well-known/nodeinfo", endpoint=_adapt_endpoint(nodeinfo_index, store)),
Route("/nodeinfo/{version}", endpoint=_adapt_endpoint(nodeinfo_version, store)),
Expand All @@ -252,3 +297,24 @@ def get_routes(store: ResourceStore, config: ServerConfig):
),
activitypub_route,
]
if isinstance(store, RdfResourceStore):
log.info("Registering SPARQL endpoint")
example_query = """\
PREFIX as: <https://www.w3.org/ns/activitystreams#>
PREFIX firm: <https://firm.stevebate.dev#>
SELECT ?object WHERE {
?object is as:Note
}
""".rstrip()
# TODO tenant-specific config for sparql endpoints
# The namespace will always be firm.stevebate.dev
sparql_app = create_sparql_endpoint(
"https://firm.stevebate.dev/sparql/",
example_query=example_query,
favicon="https://firm.stevebate.dev/static/favicon/favicon.ico",
)
routes.insert(4, Mount("/sparql", app=sparql_app, name="sparql"))
# Add a search engine
routes.insert(4, Route("/search", endpoint=_rdf_search(store)))
return routes
Loading

0 comments on commit 275ef3d

Please sign in to comment.