This commit is contained in:
@@ -5,23 +5,26 @@ class Api::RssController < ApiController
|
||||
end
|
||||
|
||||
api :GET, "/api/rss/blog", "Blog RSS feed"
|
||||
param :lang, String, required: false, desc: "Feed language (en, hu). Unknown values fall back to en"
|
||||
returns code: 200, desc: "RSS XML feed of blog posts"
|
||||
def blog
|
||||
xml = Rss::BlogFeed.new.xml
|
||||
xml = Rss::BlogFeed.new(lang: params[:lang]).xml
|
||||
render xml: xml, content_type: "application/rss+xml"
|
||||
end
|
||||
|
||||
api :GET, "/api/rss/releases", "Software releases RSS feed"
|
||||
param :lang, String, required: false, desc: "Feed language (en, hu). Unknown values fall back to en"
|
||||
returns code: 200, desc: "RSS XML feed of software releases"
|
||||
def releases
|
||||
xml = Rss::ReleasesFeed.new.xml
|
||||
xml = Rss::ReleasesFeed.new(lang: params[:lang]).xml
|
||||
render xml: xml, content_type: "application/rss+xml"
|
||||
end
|
||||
|
||||
api :GET, "/api/rss/howtos", "HowTos RSS feed"
|
||||
param :lang, String, required: false, desc: "Feed language (en, hu). Unknown values fall back to en"
|
||||
returns code: 200, desc: "RSS XML feed of tech howtos"
|
||||
def howtos
|
||||
xml = Rss::HowtosFeed.new.xml
|
||||
xml = Rss::HowtosFeed.new(lang: params[:lang]).xml
|
||||
render xml: xml, content_type: "application/rss+xml"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,8 +7,10 @@ class Api::WikiController < ApiController
|
||||
param :tag, String, required: false, desc: "Filter by tag (blog, howto, engine)"
|
||||
param :limit, :number, required: false, desc: "Limit number of results"
|
||||
param :body, String, required: false, desc: "Include body content (1 = yes)"
|
||||
param :lang, String, required: false, desc: "Content language (en, hu). Unknown values fall back to en"
|
||||
returns code: 200, desc: "Wiki pages response" do
|
||||
property :tag, String, desc: "Applied tag filter"
|
||||
property :lang, String, desc: "Requested content language"
|
||||
property :count, Integer, desc: "Number of pages returned"
|
||||
property :pages, Array, desc: "Array of wiki pages" do
|
||||
property :id, String, desc: "Page ID"
|
||||
@@ -17,7 +19,7 @@ class Api::WikiController < ApiController
|
||||
property :description, String, desc: "Short description"
|
||||
property :createdAt, String, desc: "Created date (ISO 8601)"
|
||||
property :updatedAt, String, desc: "Updated date (ISO 8601)"
|
||||
property :locale, String, desc: "Locale code"
|
||||
property :locale, String, desc: "Language the page body is actually written in (differs from lang when the page has no translation)"
|
||||
property :route, String, desc: "URL slug"
|
||||
property :tags, Array, of: String, desc: "Tags"
|
||||
property :repo, String, desc: "Git repository URL (from page metadata, engines)"
|
||||
@@ -31,7 +33,8 @@ class Api::WikiController < ApiController
|
||||
render json: Wiki::Pages.new.fetch(
|
||||
tag: params[:tag],
|
||||
limit: params[:limit],
|
||||
body: params[:body]
|
||||
body: params[:body],
|
||||
lang: params[:lang]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,12 +2,12 @@ module Rss
|
||||
class BlogFeed < Feed
|
||||
private
|
||||
|
||||
def title = "Teletype Games Blog"
|
||||
def title = translate("rss.blog.title")
|
||||
def link = "#{site_url}/blog"
|
||||
def description = "Latest blog posts from Teletype Games"
|
||||
def description = translate("rss.blog.description")
|
||||
|
||||
def items
|
||||
Wiki::Pages.new.all(tag: "blog", limit: 30).map do |page|
|
||||
Wiki::Pages.new.all(tag: "blog", limit: 30, lang: lang).map do |page|
|
||||
{
|
||||
title: page["title"],
|
||||
link: "#{site_url}/blog/#{page['route']}",
|
||||
|
||||
@@ -2,12 +2,16 @@ require "rss"
|
||||
|
||||
module Rss
|
||||
class Feed
|
||||
def initialize(lang: nil)
|
||||
@lang = Locales.resolve(lang)
|
||||
end
|
||||
|
||||
def xml
|
||||
RSS::Maker.make("2.0") do |maker|
|
||||
maker.channel.title = title
|
||||
maker.channel.link = link
|
||||
maker.channel.description = description
|
||||
maker.channel.language = "hu"
|
||||
maker.channel.language = lang
|
||||
|
||||
items.each do |item|
|
||||
maker.items.new_item do |rss_item|
|
||||
@@ -22,9 +26,13 @@ module Rss
|
||||
|
||||
private
|
||||
|
||||
attr_reader :lang
|
||||
|
||||
def site_url = Rails.configuration.x.site_url
|
||||
|
||||
def wiki_url = Rails.configuration.x.wiki_url
|
||||
def wiki_url = "#{Rails.configuration.x.wiki_url}#{Wiki::Pages.path_prefix(lang)}"
|
||||
|
||||
def translate(key, **options) = I18n.t(key, locale: lang, **options)
|
||||
|
||||
def parse_time(value)
|
||||
Time.parse(value.to_s)
|
||||
|
||||
@@ -2,12 +2,12 @@ module Rss
|
||||
class HowtosFeed < Feed
|
||||
private
|
||||
|
||||
def title = "Teletype Games HowTos"
|
||||
def title = translate("rss.howtos.title")
|
||||
def link = "#{site_url}/howtos"
|
||||
def description = "Latest tech howtos from Teletype Games"
|
||||
def description = translate("rss.howtos.description")
|
||||
|
||||
def items
|
||||
Wiki::Pages.new.all(tag: "howto", limit: 30).map do |page|
|
||||
Wiki::Pages.new.all(tag: "howto", limit: 30, lang: lang).map do |page|
|
||||
{
|
||||
title: page["title"],
|
||||
link: "#{wiki_url}/#{page['path']}",
|
||||
|
||||
@@ -2,9 +2,9 @@ module Rss
|
||||
class ReleasesFeed < Feed
|
||||
private
|
||||
|
||||
def title = "Teletype Games Releases"
|
||||
def title = translate("rss.releases.title")
|
||||
def link = "#{site_url}/catalog"
|
||||
def description = "Latest game releases from Teletype Games"
|
||||
def description = translate("rss.releases.description")
|
||||
|
||||
def items
|
||||
WarpEngine::Release.includes(:software).order(created_at: :desc).limit(50).filter_map do |release|
|
||||
@@ -14,7 +14,7 @@ module Rss
|
||||
{
|
||||
title: "#{software.title} v#{release.version}",
|
||||
link: "#{site_url}/catalog/#{software.name}",
|
||||
description: "#{software.title} #{release.version} released – #{software.desc}",
|
||||
description: translate("rss.releases.item_description", title: software.title, version: release.version, desc: software.desc),
|
||||
published_at: release.created_at.to_time
|
||||
}
|
||||
end
|
||||
|
||||
@@ -3,12 +3,18 @@ require "json"
|
||||
|
||||
module Wiki
|
||||
class Pages
|
||||
def fetch(tag:, limit: nil, body: nil)
|
||||
def self.path_prefix(language)
|
||||
language == Locales::DEFAULT ? "" : "/#{language}"
|
||||
end
|
||||
|
||||
def fetch(tag:, limit: nil, body: nil, lang: nil)
|
||||
language = Locales.resolve(lang)
|
||||
|
||||
query = { tag: tag }
|
||||
query[:limit] = limit if limit.present?
|
||||
query[:body] = body if body.present?
|
||||
|
||||
uri = URI.parse("#{Rails.configuration.x.wiki_url}/custom/pages.json")
|
||||
uri = URI.parse("#{Rails.configuration.x.wiki_url}#{self.class.path_prefix(language)}/custom/pages.json")
|
||||
uri.query = URI.encode_www_form(query)
|
||||
|
||||
response = Net::HTTP.start(
|
||||
@@ -17,21 +23,21 @@ module Wiki
|
||||
open_timeout: 5, read_timeout: 10
|
||||
) { |http| http.get(uri.request_uri) }
|
||||
|
||||
return empty(tag, "grav responded #{response.code}") unless response.is_a?(Net::HTTPSuccess)
|
||||
return empty(tag, language, "grav responded #{response.code}") unless response.is_a?(Net::HTTPSuccess)
|
||||
|
||||
JSON.parse(response.body)
|
||||
rescue StandardError => e
|
||||
empty(tag, e.message)
|
||||
empty(tag, language, e.message)
|
||||
end
|
||||
|
||||
def all(tag:, limit: nil)
|
||||
fetch(tag: tag, limit: limit).fetch("pages", [])
|
||||
def all(tag:, limit: nil, lang: nil)
|
||||
fetch(tag: tag, limit: limit, lang: lang).fetch("pages", [])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def empty(tag, error)
|
||||
{ "tag" => tag, "count" => 0, "pages" => [], "error" => error }
|
||||
def empty(tag, language, error)
|
||||
{ "tag" => tag, "lang" => language, "count" => 0, "pages" => [], "error" => error }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,3 +5,14 @@ en:
|
||||
date:
|
||||
formats:
|
||||
long: "%Y-%m-%d"
|
||||
rss:
|
||||
blog:
|
||||
title: "Teletype Games Blog"
|
||||
description: "Latest blog posts from Teletype Games"
|
||||
howtos:
|
||||
title: "Teletype Games HowTos"
|
||||
description: "Latest tech howtos from Teletype Games"
|
||||
releases:
|
||||
title: "Teletype Games Releases"
|
||||
description: "Latest game releases from Teletype Games"
|
||||
item_description: "%{title} %{version} released – %{desc}"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
hu:
|
||||
time:
|
||||
formats:
|
||||
long: "%Y-%m-%d %H:%M"
|
||||
date:
|
||||
formats:
|
||||
long: "%Y-%m-%d"
|
||||
rss:
|
||||
blog:
|
||||
title: "Teletype Games blog"
|
||||
description: "A Teletype Games legfrissebb blogbejegyzései"
|
||||
howtos:
|
||||
title: "Teletype Games útmutatók"
|
||||
description: "A Teletype Games legfrissebb technikai útmutatói"
|
||||
releases:
|
||||
title: "Teletype Games kiadások"
|
||||
description: "A Teletype Games legfrissebb játékkiadásai"
|
||||
item_description: "Megjelent a %{title} %{version} – %{desc}"
|
||||
@@ -0,0 +1,9 @@
|
||||
module Locales
|
||||
SUPPORTED = %w[en hu].freeze
|
||||
DEFAULT = "en"
|
||||
|
||||
def self.resolve(value)
|
||||
normalized = value.to_s.strip.downcase
|
||||
SUPPORTED.include?(normalized) ? normalized : DEFAULT
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Locales do
|
||||
describe ".resolve" do
|
||||
it "accepts the supported languages" do
|
||||
expect(Locales.resolve("en")).to eq("en")
|
||||
expect(Locales.resolve("hu")).to eq("hu")
|
||||
end
|
||||
|
||||
it "normalises case and whitespace" do
|
||||
expect(Locales.resolve(" HU ")).to eq("hu")
|
||||
end
|
||||
|
||||
it "falls back to the default for anything else" do
|
||||
expect(Locales.resolve("de")).to eq(Locales::DEFAULT)
|
||||
expect(Locales.resolve(nil)).to eq(Locales::DEFAULT)
|
||||
expect(Locales.resolve("")).to eq(Locales::DEFAULT)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::Wiki", type: :request do
|
||||
let(:service) { instance_double(Wiki::Pages) }
|
||||
|
||||
before do
|
||||
host! "teletypegames.org"
|
||||
allow(Wiki::Pages).to receive(:new).and_return(service)
|
||||
end
|
||||
|
||||
it "passes the requested language through to the wiki service" do
|
||||
expect(service).to receive(:fetch)
|
||||
.with(tag: "howto", limit: nil, body: nil, lang: "hu")
|
||||
.and_return({ "tag" => "howto", "lang" => "hu", "count" => 0, "pages" => [] })
|
||||
|
||||
get "/api/wiki/pages", params: { tag: "howto", lang: "hu" }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.parsed_body["lang"]).to eq("hu")
|
||||
end
|
||||
|
||||
it "leaves the fallback to the service when no language is asked for" do
|
||||
expect(service).to receive(:fetch)
|
||||
.with(tag: "howto", limit: nil, body: nil, lang: nil)
|
||||
.and_return({ "tag" => "howto", "lang" => "en", "count" => 0, "pages" => [] })
|
||||
|
||||
get "/api/wiki/pages", params: { tag: "howto" }
|
||||
|
||||
expect(response.parsed_body["lang"]).to eq("en")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Wiki::Pages do
|
||||
let(:requested_paths) { [] }
|
||||
|
||||
before do
|
||||
http = instance_double(Net::HTTP)
|
||||
response = Net::HTTPOK.new("1.1", "200", "OK")
|
||||
allow(response).to receive(:body).and_return({ tag: "howto", lang: "en", count: 0, pages: [] }.to_json)
|
||||
allow(http).to receive(:get) do |path|
|
||||
requested_paths << path
|
||||
response
|
||||
end
|
||||
allow(Net::HTTP).to receive(:start) { |*_args, **_opts, &block| block.call(http) }
|
||||
end
|
||||
|
||||
describe "#fetch" do
|
||||
it "requests the default language without a path prefix" do
|
||||
described_class.new.fetch(tag: "howto")
|
||||
|
||||
expect(requested_paths.first).to start_with("/custom/pages.json")
|
||||
end
|
||||
|
||||
it "prefixes the path with the requested language" do
|
||||
described_class.new.fetch(tag: "howto", lang: "hu")
|
||||
|
||||
expect(requested_paths.first).to start_with("/hu/custom/pages.json")
|
||||
end
|
||||
|
||||
it "falls back to the default language for an unsupported one" do
|
||||
described_class.new.fetch(tag: "howto", lang: "de")
|
||||
|
||||
expect(requested_paths.first).to start_with("/custom/pages.json")
|
||||
end
|
||||
|
||||
it "reports the resolved language when grav is unreachable" do
|
||||
allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
|
||||
|
||||
result = described_class.new.fetch(tag: "howto", lang: "hu")
|
||||
|
||||
expect(result["lang"]).to eq("hu")
|
||||
expect(result["pages"]).to eq([])
|
||||
expect(result["error"]).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user