add sync API with swagger documentation at /api
This commit is contained in:
@@ -18,6 +18,9 @@ gem "turbo-rails"
|
||||
gem "stimulus-rails"
|
||||
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
|
||||
gem "jbuilder"
|
||||
gem "grape"
|
||||
gem "grape-swagger"
|
||||
gem "rswag-ui"
|
||||
gem "caxlsx"
|
||||
gem "caxlsx_rails"
|
||||
|
||||
|
||||
@@ -120,6 +120,26 @@ GEM
|
||||
docile (1.4.1)
|
||||
dotenv (3.2.0)
|
||||
drb (2.2.3)
|
||||
dry-configurable (1.3.0)
|
||||
dry-core (~> 1.1)
|
||||
zeitwerk (~> 2.6)
|
||||
dry-core (1.2.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
logger
|
||||
zeitwerk (~> 2.6)
|
||||
dry-inflector (1.3.1)
|
||||
dry-logic (1.6.0)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-core (~> 1.1)
|
||||
zeitwerk (~> 2.6)
|
||||
dry-types (1.9.0)
|
||||
bigdecimal (>= 3.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-core (~> 1.0)
|
||||
dry-inflector (~> 1.0)
|
||||
dry-logic (~> 1.4)
|
||||
zeitwerk (~> 2.6)
|
||||
ed25519 (1.4.0)
|
||||
erb (6.0.1)
|
||||
erubi (1.13.1)
|
||||
@@ -131,6 +151,15 @@ GEM
|
||||
raabro (~> 1.4)
|
||||
globalid (1.3.0)
|
||||
activesupport (>= 6.1)
|
||||
grape (3.1.1)
|
||||
activesupport (>= 7.1)
|
||||
dry-configurable
|
||||
dry-types (>= 1.1)
|
||||
mustermann-grape (~> 1.1.0)
|
||||
rack (>= 2)
|
||||
zeitwerk
|
||||
grape-swagger (2.1.3)
|
||||
grape (>= 1.7, < 4.0)
|
||||
htmlentities (4.4.2)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
@@ -181,6 +210,10 @@ GEM
|
||||
minitest (6.0.1)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.0)
|
||||
mustermann (3.0.4)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
mustermann-grape (1.1.0)
|
||||
mustermann (>= 1.0.0)
|
||||
net-imap (0.6.2)
|
||||
date
|
||||
net-protocol
|
||||
@@ -273,6 +306,9 @@ GEM
|
||||
logger (~> 1)
|
||||
nokogiri (~> 1)
|
||||
rubyzip (>= 3.0.0, < 4.0.0)
|
||||
rswag-ui (2.17.0)
|
||||
actionpack (>= 5.2, < 8.2)
|
||||
railties (>= 5.2, < 8.2)
|
||||
rubocop (1.84.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
@@ -305,6 +341,7 @@ GEM
|
||||
ruby-vips (2.3.0)
|
||||
ffi (~> 1.12)
|
||||
logger
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (3.2.2)
|
||||
securerandom (0.4.1)
|
||||
selenium-webdriver (4.40.0)
|
||||
@@ -387,6 +424,8 @@ DEPENDENCIES
|
||||
caxlsx
|
||||
caxlsx_rails
|
||||
debug
|
||||
grape
|
||||
grape-swagger
|
||||
image_processing (~> 1.2)
|
||||
importmap-rails
|
||||
jbuilder
|
||||
@@ -395,6 +434,7 @@ DEPENDENCIES
|
||||
puma (>= 5.0)
|
||||
rails (~> 8.1.2)
|
||||
roo (~> 3.0)
|
||||
rswag-ui
|
||||
rubocop-rails-omakase
|
||||
selenium-webdriver
|
||||
simplecov
|
||||
@@ -445,6 +485,11 @@ CHECKSUMS
|
||||
docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
|
||||
dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d
|
||||
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
||||
dry-configurable (1.3.0) sha256=882d862858567fc1210d2549d4c090f34370fc1bb7c5c1933de3fe792e18afa8
|
||||
dry-core (1.2.0) sha256=0cc5a7da88df397f153947eeeae42e876e999c1e30900f3c536fb173854e96a1
|
||||
dry-inflector (1.3.1) sha256=7fb0c2bb04f67638f25c52e7ba39ab435d922a3a5c3cd196120f63accb682dcc
|
||||
dry-logic (1.6.0) sha256=da6fedbc0f90fc41f9b0cc7e6f05f5d529d1efaef6c8dcc8e0733f685745cea2
|
||||
dry-types (1.9.0) sha256=7b656fe0a78d2432500ae1f29fefd6762f5a032ca7000e4f36bc111453d45d4d
|
||||
ed25519 (1.4.0) sha256=16e97f5198689a154247169f3453ef4cfd3f7a47481fde0ae33206cdfdcac506
|
||||
erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5
|
||||
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
||||
@@ -452,6 +497,8 @@ CHECKSUMS
|
||||
ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f
|
||||
fugit (1.12.1) sha256=5898f478ede9b415f0804e42b8f3fd53f814bd85eebffceebdbc34e1107aaf68
|
||||
globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11
|
||||
grape (3.1.1) sha256=774f16782d917a90e69de0499dfaab571e5ad967569ac066a2b0b918af12de69
|
||||
grape-swagger (2.1.3) sha256=9ee955ada77c10ea2f2d2da5edcc4fc3cddcc40a6936f1b2558ee7b44e0f1153
|
||||
htmlentities (4.4.2) sha256=bbafbdf69f2eca9262be4efef7e43e6a1de54c95eb600f26984f71d2fe96c5c3
|
||||
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
|
||||
image_processing (1.14.0) sha256=754cc169c9c262980889bec6bfd325ed1dafad34f85242b5a07b60af004742fb
|
||||
@@ -472,6 +519,8 @@ CHECKSUMS
|
||||
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||
minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb
|
||||
msgpack (1.8.0) sha256=e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732
|
||||
mustermann (3.0.4) sha256=85fadcb6b3c6493a8b511b42426f904b7f27b282835502233dd154daab13aa22
|
||||
mustermann-grape (1.1.0) sha256=8d258a986004c8f01ce4c023c0b037c168a9ed889cf5778068ad54398fa458c5
|
||||
net-imap (0.6.2) sha256=08caacad486853c61676cca0c0c47df93db02abc4a8239a8b67eb0981428acc6
|
||||
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
|
||||
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
|
||||
@@ -508,6 +557,7 @@ CHECKSUMS
|
||||
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
||||
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
||||
roo (3.0.0) sha256=6fdd7a9158d657c69768b4168754ff2110cc21fdc01a1bec1010820cb05c91b1
|
||||
rswag-ui (2.17.0) sha256=5f707b9b5e8171ddf9f519f6e401e79e419bd1d07387508603e76124f2443212
|
||||
rubocop (1.84.0) sha256=88dec310153bb685a879f5a7cdb601f6287b8f0ee675d9dc63a17c7204c4190a
|
||||
rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd
|
||||
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
|
||||
@@ -515,6 +565,7 @@ CHECKSUMS
|
||||
rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d
|
||||
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
||||
ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374
|
||||
ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef
|
||||
rubyzip (3.2.2) sha256=c0ed99385f0625415c8f05bcae33fe649ed2952894a95ff8b08f26ca57ea5b3c
|
||||
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
||||
selenium-webdriver (4.40.0) sha256=16ef7aa9853c1d4b9d52eac45aafa916e3934c5c83cb4facb03f250adfd15e5b
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
"Sanasto Wiki" is a web-based dictionary application for simultaneous translators in the living Christianity. The application provides publicly accessible translations while restricting editing and commenting to invited contributors.
|
||||
"Sanasto Wiki" is a web-based glossary for simultaneous translators in the living Christianity. The application provides publicly accessible translations while restricting editing and commenting to invited contributors.
|
||||
|
||||
---
|
||||
|
||||
@@ -188,15 +188,25 @@ See 'public/Kristillisyyden sanasto ver 23.5.2013.xlsx'
|
||||
|
||||
---
|
||||
|
||||
## API (Optional Future)
|
||||
## API (Public Sync)
|
||||
|
||||
REST API for potential mobile app or integration:
|
||||
Public JSON endpoint for syncing entries:
|
||||
```
|
||||
GET /api/entries
|
||||
GET /api/entries/:id
|
||||
GET /api/entries/search?q=:query&lang=:code
|
||||
POST /api/entries (authenticated)
|
||||
PATCH /api/entries/:id (authenticated)
|
||||
GET /api/entries?since=2026-01-01T12:00:00Z
|
||||
```
|
||||
|
||||
Responses include all language columns, category and `updated_at`. The optional `since`
|
||||
parameter filters by `updated_at` (ISO8601).
|
||||
|
||||
Swagger docs:
|
||||
```
|
||||
GET /api/swagger
|
||||
```
|
||||
|
||||
Swagger UI:
|
||||
```
|
||||
GET /api
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
require "grape"
|
||||
require "grape-swagger"
|
||||
require "ostruct"
|
||||
|
||||
class Api::Base < Grape::API
|
||||
format :json
|
||||
content_type :json, "application/json"
|
||||
|
||||
helpers do
|
||||
def parse_since_param(raw_since)
|
||||
return nil if raw_since.blank?
|
||||
|
||||
Time.iso8601(raw_since)
|
||||
rescue ArgumentError
|
||||
error!({ error: "Invalid since parameter. Use ISO8601 timestamp." }, 400)
|
||||
end
|
||||
end
|
||||
|
||||
resource :entries do
|
||||
desc "Return public entries in all languages",
|
||||
attributes: OpenStruct.new(success: nil, produces: nil)
|
||||
params do
|
||||
optional :since,
|
||||
type: String,
|
||||
desc: "ISO8601 timestamp. Returns entries updated after this time."
|
||||
end
|
||||
get do
|
||||
since_time = parse_since_param(params[:since])
|
||||
|
||||
entries_scope = Entry.active_entries
|
||||
entries_scope = entries_scope.where("updated_at > ?", since_time) if since_time
|
||||
|
||||
entries_scope
|
||||
.order(:updated_at, :id)
|
||||
.select(
|
||||
:id,
|
||||
:category,
|
||||
:fi,
|
||||
:en,
|
||||
:sv,
|
||||
:no,
|
||||
:ru,
|
||||
:de,
|
||||
:updated_at
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
add_swagger_documentation(
|
||||
info: {
|
||||
title: "Sanasto Wiki API",
|
||||
description: "Public sync API for Sanasto Wiki glossary entries."
|
||||
},
|
||||
mount_path: "/swagger",
|
||||
hide_documentation_path: true,
|
||||
format: :json
|
||||
)
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
Rswag::Ui.configure do |config|
|
||||
config.swagger_endpoint "/api/swagger", "Sanasto Wiki API"
|
||||
end
|
||||
@@ -51,6 +51,9 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
mount Api::Base => "/api"
|
||||
mount Rswag::Ui::Engine => "/api"
|
||||
|
||||
resources :entries do
|
||||
resources :comments, only: [ :create ]
|
||||
collection do
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
require "test_helper"
|
||||
|
||||
class ApiEntriesTest < ActionDispatch::IntegrationTest
|
||||
test "returns active entries with language fields" do
|
||||
entry = entries(:one)
|
||||
|
||||
get "/api/entries"
|
||||
|
||||
assert_response :success
|
||||
payload = JSON.parse(response.body)
|
||||
entry_payload = payload.find { |item| item["id"] == entry.id }
|
||||
|
||||
assert_not_nil entry_payload
|
||||
assert_equal entry.fi, entry_payload["fi"]
|
||||
assert_equal entry.en, entry_payload["en"]
|
||||
assert_equal entry.sv, entry_payload["sv"]
|
||||
assert_equal entry.no, entry_payload["no"]
|
||||
assert_equal entry.ru, entry_payload["ru"]
|
||||
assert_equal entry.de, entry_payload["de"]
|
||||
end
|
||||
|
||||
test "filters by updated_at when since param is provided" do
|
||||
older_entry = Entry.create!(
|
||||
fi: "Older Entry",
|
||||
category: :word,
|
||||
status: :active
|
||||
)
|
||||
older_entry.update_column(:updated_at, 2.days.ago)
|
||||
|
||||
newer_entry = Entry.create!(
|
||||
fi: "Newer Entry",
|
||||
category: :word,
|
||||
status: :active
|
||||
)
|
||||
newer_entry.update_column(:updated_at, 1.hour.ago)
|
||||
|
||||
get "/api/entries", params: { since: 1.day.ago.iso8601 }
|
||||
|
||||
assert_response :success
|
||||
payload = JSON.parse(response.body)
|
||||
returned_ids = payload.map { |item| item["id"] }
|
||||
|
||||
assert_includes returned_ids, newer_entry.id
|
||||
assert_not_includes returned_ids, older_entry.id
|
||||
end
|
||||
|
||||
test "returns bad request for invalid since param" do
|
||||
get "/api/entries", params: { since: "not-a-time" }
|
||||
|
||||
assert_response :bad_request
|
||||
payload = JSON.parse(response.body)
|
||||
assert_equal "Invalid since parameter. Use ISO8601 timestamp.", payload["error"]
|
||||
end
|
||||
|
||||
test "exposes swagger docs" do
|
||||
get "/api/swagger"
|
||||
|
||||
assert_response :success
|
||||
payload = JSON.parse(response.body)
|
||||
assert_equal "Sanasto Wiki API", payload.dig("info", "title")
|
||||
assert_includes payload.keys, "paths"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user