grav interation

This commit is contained in:
2026-06-07 23:24:29 +02:00
parent 43aca861a2
commit 8875307668
6 changed files with 89 additions and 69 deletions
@@ -0,0 +1,10 @@
class Api::WikiController < ApiController
# GET /api/wiki/pages?tag=blog|howto[&limit=30][&body=1]
def index
render json: WikiService.new.pages(
tag: params[:tag],
limit: params[:limit],
body: params[:body]
)
end
end
+46
View File
@@ -0,0 +1,46 @@
require "net/http"
require "json"
# Fetches wiki pages (blog, howto, …) from the Grav backend, filtered by tag.
#
# Grav exposes: GET {WIKI_GRAV_URL}/api/pages.json?tag=<tag>[&limit=<n>][&body=1]
# and returns:
# {
# "tag": "howto", "count": 24,
# "pages": [
# { "id", "path", "title", "description",
# "createdAt", "updatedAt", "locale", "route", "tags",
# # with body=1 also:
# "render" (rendered HTML),
# "content" (raw markdown) }
# ]
# }
#
# This replaces the frontend's direct WikiJS GraphQL calls with a simple GET,
# and points at Grav (which is taking over from wiki.teletypegames.org).
class WikiService
GRAV_URL = ENV.fetch("WIKI_GRAV_URL", "http://localhost:8080").freeze
def pages(tag:, limit: nil, body: nil)
query = { tag: tag }
query[:limit] = limit if limit.present?
query[:body] = body if body.present?
uri = URI.parse("#{GRAV_URL}/api/pages.json")
uri.query = URI.encode_www_form(query)
response = Net::HTTP.start(
uri.host, uri.port,
use_ssl: uri.scheme == "https",
open_timeout: 5, read_timeout: 10
) { |http| http.get(uri.request_uri) }
unless response.is_a?(Net::HTTPSuccess)
return { "tag" => tag, "count" => 0, "pages" => [], "error" => "grav responded #{response.code}" }
end
JSON.parse(response.body)
rescue StandardError => e
{ "tag" => tag, "count" => 0, "pages" => [], "error" => e.message }
end
end
+1
View File
@@ -8,6 +8,7 @@ Rails.application.routes.draw do
get "events", to: "events#index"
get "members", to: "members#index"
get "image/:id", to: "images#show"
get "wiki/pages", to: "wiki#index"
end
get "update", to: "update#update"
+31 -60
View File
@@ -1,45 +1,28 @@
import type { WikiPage, WikiPageWithContent, WikiPageContent } from '../lib/interfaces/wiki.interface'
const BASE = import.meta.env.VITE_WIKI_BASE || 'https://wiki.teletypegames.org'
const TOKEN = import.meta.env.WEBAPP_WIKIJS_TOKEN
// Public content base — used only for building external links to wiki pages.
// Points at Grav (which is taking over from wiki.teletypegames.org).
const WIKI_BASE = import.meta.env.VITE_WIKI_BASE || 'https://wiki.teletypegames.org'
function buildHeaders(): Record<string, string> {
const h: Record<string, string> = { 'Content-Type': 'application/json', 'Accept': 'application/json' }
if (TOKEN) h['Authorization'] = `Bearer ${TOKEN}`
return h
}
// Pages come from our own Rails API (same-origin), which proxies the Grav
// content backend: GET /api/wiki/pages?tag=<tag>[&limit=<n>][&body=1]
async function fetchPages(
tag: string,
opts: { body?: boolean; limit?: number } = {},
): Promise<any[]> {
const params = new URLSearchParams({ tag })
if (opts.body) params.set('body', '1')
if (opts.limit) params.set('limit', String(opts.limit))
async function gql(query: string): Promise<any> {
const res = await fetch(`${BASE}/graphql`, {
method: 'POST',
headers: buildHeaders(),
body: JSON.stringify({ query }),
})
const res = await fetch(`/api/wiki/pages?${params.toString()}`)
if (!res.ok) return []
const json = await res.json()
if (json.errors) throw new Error(json.errors.map((e: any) => e.message).join(', '))
return json.data
return json?.pages ?? []
}
const listBlogPages = async (): Promise<WikiPageWithContent[]> => {
const data = await gql(`{
pages {
list(orderBy: CREATED, orderByDirection: DESC, tags: ["blog"]) {
id path title description updatedAt createdAt locale
}
}
}`)
const pages: any[] = data?.pages?.list ?? []
const pagesWithContent = await Promise.all(pages.map(async (p: any) => {
try {
const contentData = await gql(`{ pages { single(id: ${p.id}) { content } } }`)
return { ...p, content: contentData?.pages?.single?.content ?? '' }
} catch {
return { ...p, content: '' }
}
}))
return pagesWithContent.map((p: any): WikiPageWithContent => ({
const pages = await fetchPages('blog', { body: true })
return pages.map((p: any): WikiPageWithContent => ({
id: p.id,
path: p.path,
title: p.title || p.path,
@@ -52,14 +35,7 @@ const listBlogPages = async (): Promise<WikiPageWithContent[]> => {
}
const getBlogPage = async (slug: string): Promise<WikiPageContent | null> => {
const data = await gql(`{
pages {
list(orderBy: CREATED, orderByDirection: DESC, tags: ["blog"]) {
id path
}
}
}`)
const pages: any[] = data?.pages?.list ?? []
const pages = await fetchPages('blog', { body: true })
const matched = pages.find((p: any) => {
const pageSlug = p.path.startsWith('blog/') ? p.path.replace('blog/', '') : p.path
@@ -68,25 +44,20 @@ const getBlogPage = async (slug: string): Promise<WikiPageContent | null> => {
if (!matched) return null
const singleData = await gql(`{
pages {
single(id: ${matched.id}) {
id path title description render updatedAt createdAt locale
}
}
}`)
return singleData?.pages?.single ?? null
return {
id: matched.id,
path: matched.path,
title: matched.title || matched.path,
description: matched.description ?? '',
render: matched.render ?? '',
updatedAt: matched.updatedAt,
createdAt: matched.createdAt,
locale: matched.locale,
}
}
const listHowtoPages = async (): Promise<WikiPage[]> => {
const data = await gql(`{
pages {
list(orderBy: UPDATED, orderByDirection: DESC, tags: ["howto"]) {
id path title description updatedAt createdAt locale
}
}
}`)
const pages: any[] = data?.pages?.list ?? []
const pages = await fetchPages('howto', { limit: 30 })
return pages.map((p: any): WikiPage => ({
id: p.id,
path: p.path,
@@ -95,8 +66,8 @@ const listHowtoPages = async (): Promise<WikiPage[]> => {
updatedAt: p.updatedAt,
createdAt: p.createdAt,
locale: p.locale,
})).slice(0, 30)
}))
}
export { BASE as WIKI_BASE }
export { WIKI_BASE }
export default { listBlogPages, getBlogPage, listHowtoPages }
-8
View File
@@ -13,14 +13,6 @@ export default defineConfig({
changeOrigin: true,
rewrite: (path) => path.replace(/^\/proxy\/wiki/, '')
},
'/_assets': {
target: 'https://wiki.teletypegames.org',
changeOrigin: true,
},
'/_error': {
target: 'https://wiki.teletypegames.org',
changeOrigin: true,
},
'/proxy/git': {
target: 'https://git.teletypegames.org',
changeOrigin: true,