314 lines
8.1 KiB
Ruby
Executable file
314 lines
8.1 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
# frozen_string_literal: true
|
|
|
|
require 'connection_pool'
|
|
require 'pg'
|
|
require 'sinatra'
|
|
require 'sinatra/json'
|
|
|
|
$DB_POOL = ConnectionPool.new size: 5, timeout: 5 do
|
|
PG.connect(
|
|
host: 'pg.causa-arcana.com',
|
|
dbname: 'leqsikoni',
|
|
user: 'leqsikoni',
|
|
password: 'ggeucene3ou7mqh2upehhm52tfp5bkcj',
|
|
).tap do |conn|
|
|
conn.type_map_for_results = PG::BasicTypeMapForResults.new conn
|
|
end
|
|
end
|
|
|
|
before do
|
|
headers 'Access-Control-Allow-Origin' => '*'
|
|
end
|
|
|
|
get '/words/:id' do
|
|
Context.call db_pool: $DB_POOL, user_lang_id: 2 do |context|
|
|
word_id = Integer params[:id]
|
|
|
|
word_info = context.word_info(word_id)
|
|
|
|
json({
|
|
primary_form: word_info[:primary_form],
|
|
part_of_speech: context.part_of_speech_name(word_id),
|
|
commentary: word_info[:commentary],
|
|
translations: context.translations(word_id),
|
|
inflections_old: word_info[:inflections],
|
|
inflections: context.inflections(word_id),
|
|
examples: context.examples(word_id),
|
|
})
|
|
end
|
|
end
|
|
|
|
class Context
|
|
private_class_method :new
|
|
|
|
def self.call(db_pool:, **kwargs, &block)
|
|
new(**kwargs).send :call, db_pool: db_pool, &block
|
|
end
|
|
|
|
def initialize(user_lang_id:)
|
|
self.user_lang_id = user_lang_id
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :user_lang_id
|
|
|
|
def call(db_pool:)
|
|
db_pool.with do |db_conn|
|
|
@db_conn = db_conn
|
|
result = yield self
|
|
@db_conn = nil
|
|
result
|
|
end
|
|
end
|
|
|
|
def user_lang_id=(user_lang_id)
|
|
return @user_lang_id = nil if user_lang_id.nil?
|
|
|
|
user_lang_id = Integer user_lang_id
|
|
raise unless user_lang_id.positive?
|
|
|
|
@user_lang_id = user_lang_id
|
|
end
|
|
|
|
public
|
|
|
|
##
|
|
# @return [Hash{Symbol => String}, nil]
|
|
#
|
|
def word_info(word_id)
|
|
word_id = Integer word_id
|
|
|
|
row = @db_conn.exec_params(
|
|
'SELECT primary_form, commentary, inflections FROM words WHERE id = $1',
|
|
[word_id],
|
|
).to_a.first
|
|
|
|
primary_form = String(row['primary_form']).strip.freeze
|
|
primary_form = nil if primary_form.empty?
|
|
|
|
commentary = String(row['commentary']).strip.freeze
|
|
commentary = nil if commentary.empty?
|
|
|
|
inflections = String(row['inflections']).strip.freeze
|
|
inflections = nil if inflections.empty?
|
|
|
|
{
|
|
primary_form: primary_form,
|
|
commentary: commentary,
|
|
inflections: inflections,
|
|
}.freeze
|
|
end
|
|
|
|
##
|
|
# @return [String, nil]
|
|
#
|
|
def part_of_speech_name(word_id)
|
|
word_id = Integer word_id
|
|
|
|
column = @db_conn.exec_params(
|
|
(
|
|
<<~SQL
|
|
SELECT part_names.value
|
|
FROM words
|
|
INNER JOIN parts
|
|
ON words.part_id = parts.id
|
|
INNER JOIN part_names
|
|
ON part_names.part_id = parts.id
|
|
WHERE
|
|
words.id = $1
|
|
AND (
|
|
part_names.name_lang_id = $2
|
|
OR
|
|
$2 IS NULL
|
|
)
|
|
LIMIT 1
|
|
SQL
|
|
),
|
|
[word_id, user_lang_id],
|
|
).values.first&.first
|
|
|
|
str = String(column).strip.freeze
|
|
str unless str.empty?
|
|
end
|
|
|
|
def translations(word_id)
|
|
word_id = Integer word_id
|
|
|
|
values = @db_conn.exec_params(
|
|
(
|
|
<<~SQL
|
|
SELECT
|
|
translation_texts.value,
|
|
translation_texts.commentary
|
|
FROM words
|
|
INNER JOIN translations
|
|
ON translations.word_id = words.id
|
|
INNER JOIN translation_texts
|
|
ON translation_texts.translation_id = translations.id
|
|
WHERE
|
|
word_id = $1
|
|
AND
|
|
translation_texts.lang_id = $2
|
|
ORDER BY translations.index ASC
|
|
SQL
|
|
),
|
|
[word_id, user_lang_id],
|
|
).to_a
|
|
|
|
values.map.with_index do |row, index|
|
|
commentary = String(row['commentary']).strip.freeze
|
|
commentary = nil if commentary.empty?
|
|
|
|
{
|
|
index: index + 1,
|
|
translation: String(row['value']).strip.freeze,
|
|
commentary: commentary,
|
|
}.freeze
|
|
end.freeze
|
|
end
|
|
|
|
def inflections(word_id)
|
|
word_id = Integer word_id
|
|
|
|
result = @db_conn.exec_params(
|
|
(
|
|
<<~SQL
|
|
SELECT inflections.descr, word_forms.value
|
|
FROM words
|
|
INNER JOIN parts
|
|
ON parts.id = words.part_id
|
|
INNER JOIN inflections
|
|
ON inflections.part_id = parts.id
|
|
LEFT JOIN word_forms
|
|
ON word_forms.inflection_id = inflections.id
|
|
WHERE words.id = $1 AND word_forms.word_id = $1
|
|
ORDER BY inflections.descr
|
|
SQL
|
|
),
|
|
[word_id],
|
|
).to_a
|
|
|
|
result.map do |row|
|
|
descr = String(row['descr']).strip.freeze
|
|
value = String(row['value']).strip.freeze
|
|
|
|
{
|
|
descr: descr,
|
|
value: value,
|
|
}
|
|
end.freeze
|
|
end
|
|
|
|
def examples(word_id)
|
|
word_id = Integer word_id
|
|
|
|
left_lang_id = 5 # Georgian
|
|
right_lang_id = 2 # Russian
|
|
|
|
lang_order = left_lang_id < right_lang_id ? 'ASC' : 'DESC'
|
|
|
|
result = @db_conn.exec_params(
|
|
(
|
|
<<~SQL
|
|
SELECT
|
|
foo.example_id,
|
|
array_agg(foo.id ORDER BY foo.language_id #{lang_order})::text[]
|
|
AS ids,
|
|
array_agg(foo.value ORDER BY foo.language_id #{lang_order})::text[]
|
|
AS values
|
|
FROM example_texts foo
|
|
INNER JOIN (
|
|
SELECT example_id, language_id, MIN(index) as min_index
|
|
FROM example_texts
|
|
WHERE
|
|
example_id = ANY(
|
|
SELECT example_texts.example_id
|
|
FROM words
|
|
INNER JOIN word_forms
|
|
ON word_forms.word_id = words.id
|
|
INNER JOIN word_form_example_texts
|
|
ON word_form_example_texts.word_form_id = word_forms.id
|
|
INNER JOIN example_texts
|
|
ON example_texts.id = word_form_example_texts.example_text_id
|
|
WHERE words.id = $1
|
|
GROUP BY example_texts.example_id
|
|
)
|
|
AND
|
|
(language_id = $2 OR language_id = $3)
|
|
GROUP BY example_id, language_id
|
|
) bar
|
|
ON
|
|
foo.example_id = bar.example_id AND
|
|
foo.language_id = bar.language_id AND
|
|
foo.index = bar.min_index
|
|
GROUP BY foo.example_id
|
|
SQL
|
|
),
|
|
[word_id, left_lang_id, right_lang_id],
|
|
).map do |row|
|
|
{
|
|
example_id: Integer(row['example_id']),
|
|
left_id: Integer(row['ids'].first),
|
|
right_id: Integer(row['ids'].last),
|
|
left: String(row['values'].first).strip.freeze,
|
|
right: String(row['values'].last).strip.freeze,
|
|
highlights: [],
|
|
}.freeze
|
|
end.freeze
|
|
|
|
highlights =
|
|
@db_conn.exec_params(
|
|
(
|
|
<<~SQL
|
|
SELECT
|
|
example_texts.id AS example_text_id,
|
|
array_agg(
|
|
word_form_example_texts.pos
|
|
ORDER BY word_form_example_texts.word_form_id
|
|
) AS positions,
|
|
array_agg(
|
|
word_forms.len
|
|
ORDER BY word_forms.id
|
|
) AS lengths
|
|
FROM example_texts
|
|
INNER JOIN word_form_example_texts
|
|
ON word_form_example_texts.example_text_id = example_texts.id
|
|
INNER JOIN word_forms
|
|
ON word_form_example_texts.word_form_id = word_forms.id
|
|
WHERE
|
|
language_id = $1::bigint
|
|
AND
|
|
example_texts.example_id = ANY($2::bigint[])
|
|
GROUP BY example_texts.id
|
|
SQL
|
|
),
|
|
[
|
|
left_lang_id,
|
|
PG::TextEncoder::Array.new.encode(result.map { |h| h[:example_id] }),
|
|
],
|
|
)
|
|
.map do |row|
|
|
example_text_id = Integer(row['example_text_id'])
|
|
|
|
positions = row['positions'].map { |pos| Integer pos }.freeze
|
|
lengths = row['lengths'].map { |len| Integer len }.freeze
|
|
|
|
{
|
|
example_text_id: example_text_id,
|
|
data: positions.zip(lengths).map do |pos, len|
|
|
{ pos: pos, len: len }.freeze
|
|
end.sort_by { |h| h[:pos] }.freeze,
|
|
}
|
|
end.to_a
|
|
|
|
highlights.each do |highlight|
|
|
result
|
|
.find { |item| item[:left_id] == highlight[:example_text_id] }
|
|
&.tap { |item| item[:highlights].replace highlight[:data] }
|
|
end
|
|
|
|
result.each { |h| h[:highlights].freeze }
|
|
end
|
|
end
|