Class: OKF::Server::App

Inherits:
Object
  • Object
show all
Defined in:
lib/okf/server/app.rb

Overview

HTTP access to a bundle's knowledge graph — a Rack app, so it runs under any Rack server (WEBrick, via okf server) and can be mounted in a Rails app:

mount OKF::Server::App.new(folder) => "/knowledge"

The page (OKF::Render::Graph) boots from a minimal graph (id + title + edges

  • type/tag indexes) and pulls each concept's markdown body and description from here on demand, so the initial payload stays small and bodies are read live from disk (edits show without a restart). Part of the shell — it does I/O.

    GET / the interactive graph page (text/html) GET /node?id=… the concept's raw markdown body (text/markdown) GET /node/meta?id=… its description, as an escaped HTML fragment GET /catalog rich per-concept metadata for the catalog/files/stats views: { concepts: [ title, type, description, tags, timestamp, status, top_dir, dir, links_* ] } (JSON) GET /tags the tag index { tag => [id, …] } (JSON) GET /types the type index { type => [id, …] } (JSON) GET /index the §6 progressive-disclosure map for the Index panel: { directories: [ …okf-index rows… ] } (JSON, from the boot snapshot — authored maps are structure) GET /log the §7 history for the Log panel: { logs: [ dir, content ] } (JSON; content read live from disk, like a body — the log is the file that changes most) GET /search?q=… ranked concepts in this bundle, for the ⌘K palette: { query, total, truncated, results: [ …rows… ] } (JSON)

Constant Summary collapse

SEARCH_LIMIT =

How many rows /search answers with. The palette shows a handful and the rest is scroll nobody reaches, but the count is reported alongside so a capped answer never reads as a complete one.

50
SEARCH_ENGINE =

The engine /search runs on, named rather than inferred. fuzzy: true would route here on its own today — the index is the only registered engine that offers it — but that is correctness by coincidence, and an addon declaring :fuzzy would silently take the route.

It is also the right engine here for a reason the CLI's default does not share: okf search is one-shot and cannot amortize an index build, while this is a long-lived server answering keystroke after keystroke. And the page's own MiniSearch is what minifts is a port of, so a palette hit and an in-page search rank alike instead of nearly alike.

:index

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(folder, title: nil, link: nil, layout: "cose", siblings: nil, self_slug: nil, hub_path: nil, search_endpoint: nil, manage_root: nil, manage_token: nil, map: false) ⇒ App

+siblings+/+self_slug+/+hub_path+ are set only when this app is hosted under a hub (OKF::Server::Hub): the other bundles the in-page switcher offers, this bundle's own mount slug, the hub root, and the hub's cross-bundle search route. They stay nil for a standalone server and for okf render, so a static file never advertises a switcher or a search it cannot answer.

search_endpoint belongs to that group for the same reason, even though this app now answers /search itself: the page resolves it relative to the URL the reader is on, so only whoever mounted the app knows what to call it. okf server mounts at the root and passes "search"; a host doing mount App.new(folder) => "/knowledge" needs its own spelling, and a default would have pointed its palette at the host's root instead. The route answers either way — advertising it is the caller's call.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/okf/server/app.rb', line 84

def initialize(folder, title: nil, link: nil, layout: "cose", siblings: nil, self_slug: nil, hub_path: nil,
               search_endpoint: nil, manage_root: nil, manage_token: nil, map: false)
  @folder = folder
  @title = title
  @link = link
  @layout = layout
  @map = map
  @siblings = siblings
  @self_slug = self_slug
  @hub_path = hub_path
  @search_endpoint = search_endpoint
  @manage_root = manage_root
  @manage_token = manage_token
end

Class Method Details

.not_foundObject

The 404 both this app and the Hub answer with, so the two cannot drift.



126
127
128
# File 'lib/okf/server/app.rb', line 126

def self.not_found
  [ 404, { "content-type" => "text/plain; charset=utf-8" }, [ "not found\n" ] ]
end

.search_payload(corpus, query) ⇒ Object

The /search payload, defined once because two hosts answer it: this one bundle, or every bundle the hub hosts. The only difference is the corpus handed in — and a nil slug drops the slug key from a row, so a standalone server never answers as if it were a set.

It takes a prepared corpus rather than the pairs, because that is what makes the index survive the request that built it.



60
61
62
63
64
65
66
67
68
69
# File 'lib/okf/server/app.rb', line 60

def self.search_payload(corpus, query)
  terms = query.to_s.split(/\s+/).reject(&:empty?)
  rows = terms.empty? ? [] : OKF::Bundle::Search.with(corpus, terms, fuzzy: true, engine: SEARCH_ENGINE)
  {
    "query" => query.to_s.strip,
    "total" => rows.length,
    "truncated" => rows.length > SEARCH_LIMIT,
    "results" => rows.first(SEARCH_LIMIT)
  }
end

Instance Method Details

#call(env) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/okf/server/app.rb', line 107

def call(env)
  request = Rack::Request.new(env)
  return not_found unless request.get?

  case request.path_info
  when "", "/" then respond("text/html; charset=utf-8", page)
  when "/node" then node_body(request.params["id"])
  when "/node/meta" then node_meta(request.params["id"])
  when "/catalog" then respond_json(catalog)
  when "/tags" then respond_json(graph.tag_index)
  when "/types" then respond_json(graph.type_index)
  when "/index" then respond_json(directory_index)
  when "/log" then respond_json(logs)
  when "/search" then respond_json(self.class.search_payload(search_corpus, request.params["q"]))
  else not_found
  end
end

#warm_searchObject

Build the search index now rather than on the first reader's keystroke. okf server calls this after the bundle is loaded, so the cost lands in boot — where it is expected and attributable — instead of in a request.



102
103
104
105
# File 'lib/okf/server/app.rb', line 102

def warm_search
  search_corpus
  self
end