Skip to content

Commit

Permalink
Merge branch 'api'
Browse files Browse the repository at this point in the history
  • Loading branch information
wildjcrt committed Sep 8, 2024
2 parents 0e8aeee + 4ea50b8 commit cfe94f0
Show file tree
Hide file tree
Showing 13 changed files with 329 additions and 3 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ gem "csv"
# sql LIKE search
gem "ransack"

# API
gem "grape"

# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"

Expand Down
27 changes: 27 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ GEM
irb (~> 1.10)
reline (>= 0.3.8)
drb (2.2.1)
dry-core (1.0.1)
concurrent-ruby (~> 1.0)
zeitwerk (~> 2.6)
dry-inflector (1.1.0)
dry-logic (1.5.0)
concurrent-ruby (~> 1.0)
dry-core (~> 1.0, < 2)
zeitwerk (~> 2.6)
dry-types (1.7.2)
bigdecimal (~> 3.0)
concurrent-ruby (~> 1.0)
dry-core (~> 1.0)
dry-inflector (~> 1.0)
dry-logic (~> 1.4)
zeitwerk (~> 2.6)
erubi (1.13.0)
globalid (1.2.1)
activesupport (>= 6.1)
Expand All @@ -116,6 +131,12 @@ GEM
google-protobuf (4.28.0-x86_64-linux)
bigdecimal
rake (>= 13)
grape (2.1.3)
activesupport (>= 6)
dry-types (>= 1.1)
mustermann-grape (~> 1.1.0)
rack (>= 2)
zeitwerk
i18n (1.14.5)
concurrent-ruby (~> 1.0)
importmap-rails (2.0.1)
Expand Down Expand Up @@ -145,6 +166,10 @@ GEM
mini_mime (1.1.5)
minitest (5.25.1)
msgpack (1.7.2)
mustermann (3.0.3)
ruby2_keywords (~> 0.0.1)
mustermann-grape (1.1.0)
mustermann (>= 1.0.0)
net-imap (0.4.16)
date
net-protocol
Expand Down Expand Up @@ -247,6 +272,7 @@ GEM
rubocop-performance
rubocop-rails
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
sass-embedded (1.78.0-aarch64-linux-gnu)
google-protobuf (~> 4.27)
Expand Down Expand Up @@ -308,6 +334,7 @@ DEPENDENCIES
csv
dartsass-rails
debug
grape
importmap-rails
jbuilder
puma (>= 5.0)
Expand Down
8 changes: 8 additions & 0 deletions app/api/application_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class ApplicationAPI < Grape::API
content_type :text, "text/plain"
content_type :json, "application/json"
default_format :json

mount V1::Base
mount V2::Base
end
9 changes: 9 additions & 0 deletions app/api/v1/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module V1
class Base < ApplicationAPI
version :v1, using: :path

mount FeyAPI
mount PoinsotAPI
mount SafoluAPI
end
end
32 changes: 32 additions & 0 deletions app/api/v1/fey_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module V1
class FeyAPI < Base
resources :p do
params do
requires :name, type: String, desc: "方敏英字典詞彙,對應 Term#name"
end
get ":name" do
dictionary = Dictionary.find_by(name: "方敏英字典")
term = dictionary.terms.includes(:stem, descriptions: %i[examples synonyms]).find_by(name: params[:name])

if term.present?
result = { t: term.lower_name }
result[:stem] = term.stem.name if term.stem.present?

result[:h] = []
result[:h][0] = { d: [] }
result[:h][0][:name] = term.name if term.lower_name != term.name
term.descriptions.each do |description|
description_hash = { f: description.content }
description_hash[:e] = description.examples.map(&:content) if description.examples.present?
description_hash[:s] = description.synonyms.alts.map(&:content) if description.synonyms.alts.present?
result[:h][0][:d] << description_hash
end

result
else
{ term: :not_found }
end
end
end
end
end
33 changes: 33 additions & 0 deletions app/api/v1/poinsot_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module V1
class PoinsotAPI < Base
resources :m do
params do
requires :name, type: String, desc: "博利亞潘世光阿法字典詞彙,對應 Term#name"
end
get ":name" do
dictionary = Dictionary.find_by(name: "博利亞潘世光阿法字典")
term = dictionary.terms.includes(:stem, descriptions: %i[examples synonyms]).find_by(name: params[:name])
console
if term.present?
result = { t: term.lower_name }
result[:stem] = term.stem.name if term.stem.present?

result[:h] = []
result[:h][0] = { d: [] }
result[:h][0][:name] = term.name if term.lower_name != term.name
term.descriptions.each do |description|
description_hash = { f: description.content }
description_hash[:e] = description.examples.map(&:content) if description.examples.present?
description_hash[:s] = description.synonyms.alts.map(&:content) if description.synonyms.alts.present?
description_hash[:type] = description.description_type if description.description_type.present?
result[:h][0][:d] << description_hash
end

result
else
{ term: :not_found }
end
end
end
end
end
34 changes: 34 additions & 0 deletions app/api/v1/safolu_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module V1
class SafoluAPI < Base
resources :s do
params do
requires :name, type: String, desc: "蔡中涵大辭典詞彙,對應 Term#name"
end
get ":name" do
dictionary = Dictionary.find_by(name: "蔡中涵大辭典")
term = dictionary.terms.includes(:stem, descriptions: %i[examples synonyms]).find_by(name: params[:name])

if term.present?
result = { t: term.lower_name }
result[:stem] = term.stem.name if term.stem.present?
result[:tag] = "[疊 #{term.repetition}]" if term.repetition.present?

result[:h] = []
result[:h][0] = { d: [] }
result[:h][0][:name] = term.name if term.lower_name != term.name
term.descriptions.each do |description|
description_hash = { f: description.content }
description_hash[:e] = description.examples.map(&:content) if description.examples.present?
description_hash[:s] = description.synonyms.alts.map(&:content) if description.synonyms.alts.present?
description_hash[:r] = description.synonyms.refs.map(&:content) if description.synonyms.refs.present?
result[:h][0][:d] << description_hash
end

result
else
{ term: :not_found }
end
end
end
end
end
35 changes: 35 additions & 0 deletions app/api/v2/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module V2
class Base < ApplicationAPI
version :v2, using: :path

helpers do
def fail400(data: nil)
error!({ status: "fail", data: data }, 400)
end

def fail403(data: nil)
error!({ status: "fail", data: data }, 403)
end

def fail404(data: nil)
error!({ status: "fail", data: data }, 404)
end

def success200(data: nil)
{ status: "success", data: data }
end

def error500(error: nil, message: "something_went_wrong!")
if Rails.env.development? && error.present?
env["api.format"] = :txt
error!("Grape caught this error: #{error.message} (#{error.class})\n#{error.backtrace.join("\n")}")
else
error!({ status: "error", message: message }, 500)
end
end
end

mount TermsAPI
mount SearchesAPI
end
end
32 changes: 32 additions & 0 deletions app/api/v2/searches_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module V2
class SearchesAPI < Base
resources :searches do
params do
requires :q, type: String, desc: "搜尋族語/漢語關鍵字,族語 1~3 字使用精確搜尋,超過 3 字用 sql LIKE 搜尋。漢語一律用 sql LIKE 搜尋 Description#content。"
end
get ":q" do
result = []

if params[:q].match?(/\A[a-zA-Z'’ʼ^]+\z/) # 族語搜尋
case params[:q].size
when 1, 2, 3
Term.includes(:descriptions).select(:id, :name).where(lower_name: params[:q]).group(:name).each do |term|
result << { term: term.name, description: term.short_description }
end
else
Term.select(:id, :name).ransack(lower_name_cont: params[:q]).result.group(:name).each do |term|
result << { term: term.name, description: term.short_description }
end
end
else # 漢語搜尋
term_ids = Description.ransack(content_cont: params[:q]).result.pluck(:term_id)
Term.includes(:descriptions).select(:id, :name).where(id: term_ids).group(:name).each do |term|
result << { term: term.name, description: term.short_description }
end
end

result.sort_by { |element| element[:term].size }
end
end
end
end
67 changes: 67 additions & 0 deletions app/api/v2/terms_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module V2
class TermsAPI < Base
resources :terms do
params do
requires :name, type: String, desc: "所有字典的詞彙,對應 Term#name"
end
get ":name" do
terms = Term.includes(:dictionary, :stem, descriptions: %i[examples synonyms]).where(name: params[:name])

if terms.exists?
result = []

terms.each do |term|
term_hash = {
dictionary: term.dictionary.name,
dialect: term.dictionary.dialect,
name: term.name,
is_stem: term.is_stem,
descriptions: []
}

term_hash[:stem] = term.stem.name if term.stem.present?
term_hash[:lower_name] = term.lower_name if term.name != term.lower_name
term_hash[:repetition] = term.repetition if term.repetition.present?
term_hash[:audio] = term.audio if term.audio.present?

term.descriptions.each do |description|
description_hash = {
content: description.content
}
description_hash[:type] = description.description_type if description.description_type.present?
description_hash[:glossary_serial] = description.glossary_serial if description.glossary_serial.present?
description_hash[:glossary_level] = description.glossary_level if description.glossary_level.present?
description_hash[:image] = description.image if description.image.present?

description.examples.each do |example|
example_hash = { content: example.content }
example_hash[:content_zh] = example.content_zh if example.content_zh.present?

description_hash[:examples] ||= []
description_hash[:examples] << example_hash
end

description.synonyms.each do |synonym|
synonym_hash = {
term_type: synonym.term_type,
content: synonym.content
}

description_hash[:synonyms] ||= []
description_hash[:synonyms] << synonym_hash
end

term_hash[:descriptions] << description_hash
end

result << term_hash
end

result
else
{ term: :not_found }
end
end
end
end
end
6 changes: 3 additions & 3 deletions config/initializers/inflections.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
# end

# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym "RESTful"
# end
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym "API"
end
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

# Use command below to check grape routes
# $ bin/rails grape:routes
mount ApplicationAPI => "/api"

resources :terms, only: %i[index show]

# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
Expand Down
42 changes: 42 additions & 0 deletions lib/tasks/grape.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# from: https://github.com/syedmusamah/grape_on_rails_routes/blob/master/lib/tasks/grape.rake
namespace :grape do
desc "show API routes"
task routes: :environment do
mapping = method_mapping

grape_klasses = ObjectSpace.each_object(Class).select { |klass| klass < Grape::API }
routes = grape_klasses.flat_map(&:routes)
.uniq do |r|
r.send(mapping[:path]) + r.send(mapping[:method]).to_s
end

method_width, path_width, version_width, desc_width = widths(routes, mapping)

puts " #{"Verb".rjust(method_width)} | #{"URI".ljust(path_width)} | #{"Ver".ljust(version_width)} | #{"Description".ljust(desc_width)}"
routes.each do |api|
method = api.send(mapping[:method]).to_s.rjust(method_width)
path = api.send(mapping[:path]).to_s.ljust(path_width)
version = api.send(mapping[:version]).to_s.ljust(version_width)
desc = api.send(mapping[:description]).to_s.ljust(desc_width)
puts " #{method} | #{path} | #{version} | #{desc}"
end
end

def widths(routes, mapping)
[
routes.map { |r| r.send(mapping[:method]).try(:length) }.compact.max || 0,
routes.map { |r| r.send(mapping[:path]).try(:length) }.compact.max || 0,
routes.map { |r| r.send(mapping[:version]).try(:length) }.compact.max || 0,
routes.map { |r| r.send(mapping[:description]).try(:length) }.compact.max || 0
]
end

def method_mapping
{
method: "request_method",
path: "path",
version: "version",
description: "description"
}
end
end

0 comments on commit cfe94f0

Please sign in to comment.