txdb 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +202 -0
- data/README.md +39 -0
- data/lib/txdb.rb +32 -0
- data/lib/txdb/app.rb +36 -0
- data/lib/txdb/backends.rb +19 -0
- data/lib/txdb/backends/globalize.rb +10 -0
- data/lib/txdb/backends/globalize/backend.rb +19 -0
- data/lib/txdb/backends/globalize/helpers.rb +19 -0
- data/lib/txdb/backends/globalize/reader.rb +63 -0
- data/lib/txdb/backends/globalize/writer.rb +39 -0
- data/lib/txdb/config.rb +52 -0
- data/lib/txdb/connection_string.rb +18 -0
- data/lib/txdb/database.rb +40 -0
- data/lib/txdb/downloader.rb +32 -0
- data/lib/txdb/handlers.rb +6 -0
- data/lib/txdb/handlers/hook_handler.rb +59 -0
- data/lib/txdb/handlers/triggers.rb +9 -0
- data/lib/txdb/handlers/triggers/handler.rb +50 -0
- data/lib/txdb/handlers/triggers/pull_handler.rb +34 -0
- data/lib/txdb/handlers/triggers/push_handler.rb +19 -0
- data/lib/txdb/response.rb +11 -0
- data/lib/txdb/response_helpers.rb +26 -0
- data/lib/txdb/table.rb +36 -0
- data/lib/txdb/transifex_project.rb +17 -0
- data/lib/txdb/tx_resource.rb +52 -0
- data/lib/txdb/uploader.rb +33 -0
- data/lib/txdb/version.rb +3 -0
- data/spec/backends/globalize/helpers_spec.rb +17 -0
- data/spec/backends/globalize/reader_spec.rb +25 -0
- data/spec/backends/globalize/writer_spec.rb +53 -0
- data/spec/backends_spec.rb +30 -0
- data/spec/config_spec.rb +71 -0
- data/spec/connection_string_spec.rb +24 -0
- data/spec/database_spec.rb +33 -0
- data/spec/downloader_spec.rb +36 -0
- data/spec/handlers/hook_handler_spec.rb +73 -0
- data/spec/handlers/triggers/pull_handler_spec.rb +58 -0
- data/spec/handlers/triggers/push_handler_spec.rb +49 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/spec_helpers/env_helpers.rb +13 -0
- data/spec/spec_helpers/globalize_db.rb +60 -0
- data/spec/spec_helpers/test_backend.rb +28 -0
- data/spec/spec_helpers/test_db.rb +76 -0
- data/spec/table_spec.rb +58 -0
- data/spec/test.sqlite3 +0 -0
- data/spec/transifex_project_spec.rb +15 -0
- data/spec/tx_resource_spec.rb +17 -0
- data/spec/uploader_spec.rb +36 -0
- data/txdb.gemspec +22 -0
- metadata +134 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Txdb
|
|
4
|
+
module Backends
|
|
5
|
+
module Globalize
|
|
6
|
+
|
|
7
|
+
class Writer
|
|
8
|
+
include Helpers
|
|
9
|
+
|
|
10
|
+
attr_reader :table
|
|
11
|
+
|
|
12
|
+
def initialize(table)
|
|
13
|
+
@table = table
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write_content(content, locale)
|
|
17
|
+
content[origin_table_name(table.name)].each_pair do |id, fields|
|
|
18
|
+
row = table.db.where(foreign_key.to_sym => id, locale: locale)
|
|
19
|
+
|
|
20
|
+
if row.empty?
|
|
21
|
+
table.db << fields.merge(
|
|
22
|
+
foreign_key.to_sym => id, locale: locale
|
|
23
|
+
)
|
|
24
|
+
else
|
|
25
|
+
row.update(fields)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def foreign_key
|
|
33
|
+
@foreign_key ||= table.name.sub(/_translations/, '_id')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/txdb/config.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Txdb
|
|
4
|
+
class Config
|
|
5
|
+
class << self
|
|
6
|
+
def databases
|
|
7
|
+
@databases ||= raw_config[:databases].map do |database_config|
|
|
8
|
+
Database.new(database_config)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def each_table(&block)
|
|
13
|
+
return to_enum(__method__) unless block_given?
|
|
14
|
+
databases.each { |database| database.tables.each(&block) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def raw_config
|
|
20
|
+
@raw_config ||= begin
|
|
21
|
+
scheme, payload = ENV['TXDB_CONFIG'].split('://')
|
|
22
|
+
send(:"load_#{scheme}", payload)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def load_file(payload)
|
|
27
|
+
deep_symbolize_keys(YAML.load_file(payload))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def load_raw(payload)
|
|
31
|
+
deep_symbolize_keys(YAML.load(payload))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def deep_symbolize_keys(obj)
|
|
35
|
+
case obj
|
|
36
|
+
when Hash
|
|
37
|
+
obj.each_with_object({}) do |(k, v), ret|
|
|
38
|
+
ret[k.to_sym] = deep_symbolize_keys(v)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
when Array
|
|
42
|
+
obj.map do |elem|
|
|
43
|
+
deep_symbolize_keys(elem)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
else
|
|
47
|
+
obj
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Txdb
|
|
2
|
+
class ConnectionString
|
|
3
|
+
TEMPLATES = {
|
|
4
|
+
mysql2: "%{adapter}://%{username}:%{password}@%{host}:%{port}/%{database}",
|
|
5
|
+
sqlite: "%{adapter}://%{database}"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
attr_reader :options
|
|
9
|
+
|
|
10
|
+
def initialize(options = {})
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def string
|
|
15
|
+
TEMPLATES[options[:adapter].to_sym] % options
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'sequel'
|
|
2
|
+
|
|
3
|
+
module Txdb
|
|
4
|
+
class Database
|
|
5
|
+
DEFAULT_HOST = '127.0.0.1'
|
|
6
|
+
DEFAULT_PORT = '3306'
|
|
7
|
+
DEFAULT_POOL = 10
|
|
8
|
+
|
|
9
|
+
attr_reader :adapter, :backend, :username, :password, :host, :port, :database
|
|
10
|
+
attr_reader :pool, :transifex_project, :tables, :connection_string
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@adapter = options.fetch(:adapter)
|
|
14
|
+
@backend = Txdb::Backends.get(options.fetch(:backend))
|
|
15
|
+
@username = options.fetch(:username)
|
|
16
|
+
@password = options.fetch(:password)
|
|
17
|
+
@host = options.fetch(:host, DEFAULT_HOST)
|
|
18
|
+
@port = options.fetch(:port, DEFAULT_PORT)
|
|
19
|
+
@database = options.fetch(:database)
|
|
20
|
+
@pool = options.fetch(:pool, DEFAULT_POOL)
|
|
21
|
+
@transifex_project = TransifexProject.new(options.fetch(:transifex))
|
|
22
|
+
@connection_string = ConnectionString.new(options).string
|
|
23
|
+
@tables = options.fetch(:tables).map do |table_config|
|
|
24
|
+
Table.new(self, table_config)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def db
|
|
29
|
+
@db ||= Sequel.connect(connection_string, max_connections: pool)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def from(*args, &block)
|
|
33
|
+
db.from(*args, &block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def transifex_api
|
|
37
|
+
transifex_project.api
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Txdb
|
|
2
|
+
class Downloader
|
|
3
|
+
class << self
|
|
4
|
+
def download(database)
|
|
5
|
+
new(database).download
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_reader :database
|
|
10
|
+
|
|
11
|
+
def initialize(database)
|
|
12
|
+
@database = database
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def download(locale)
|
|
16
|
+
database.tables.each do |table|
|
|
17
|
+
download_table(table, locale)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def download_table(table, locale)
|
|
22
|
+
content = transifex_api.download(table.resource, locale)
|
|
23
|
+
table.write_content(content, locale)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def transifex_api
|
|
29
|
+
database.transifex_api
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'txgh'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module Txdb
|
|
5
|
+
module Handlers
|
|
6
|
+
class HookHandler
|
|
7
|
+
class << self
|
|
8
|
+
def handle_request(request)
|
|
9
|
+
new(request).handle
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
include ResponseHelpers
|
|
14
|
+
|
|
15
|
+
attr_reader :request
|
|
16
|
+
|
|
17
|
+
def initialize(request)
|
|
18
|
+
@request = request
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def handle
|
|
22
|
+
tables.each do |table|
|
|
23
|
+
content = table.database.transifex_api.download(table.resource, locale)
|
|
24
|
+
table.write_content(content, locale)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
respond_with(200, {})
|
|
28
|
+
rescue => e
|
|
29
|
+
respond_with_error(500, "Internal server error: #{e.message}", e)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def locale
|
|
35
|
+
payload['language']
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def tables
|
|
39
|
+
@tables ||= Txdb::Config.each_table.select do |table|
|
|
40
|
+
table.resource.resource_slug == payload['resource'] &&
|
|
41
|
+
authentic_request?(table.database.transifex_project)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def authentic_request?(project)
|
|
46
|
+
Txgh::TransifexRequestAuth.authentic_request?(
|
|
47
|
+
request, project.webhook_secret
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def payload
|
|
52
|
+
@payload ||= begin
|
|
53
|
+
request.body.rewind
|
|
54
|
+
Hash[URI.decode_www_form(request.body.read)]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Txdb
|
|
2
|
+
module Handlers
|
|
3
|
+
module Triggers
|
|
4
|
+
class Handler
|
|
5
|
+
include ResponseHelpers
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def handle_request(request)
|
|
9
|
+
new(request).handle
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :request
|
|
14
|
+
|
|
15
|
+
def initialize(request)
|
|
16
|
+
@request = request
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def handle_safely
|
|
22
|
+
yield
|
|
23
|
+
rescue => e
|
|
24
|
+
respond_with_error(500, "Internal server error: #{e.message}", e)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def databases
|
|
28
|
+
@databases ||= if database_name = request.params['database']
|
|
29
|
+
Txdb::Config.databases.select do |database|
|
|
30
|
+
database.database == database_name
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
Txdb::Config.databases
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def tables
|
|
38
|
+
@tables ||= each_table_in(databases).select do |table|
|
|
39
|
+
table.name == request.params['table']
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def each_table_in(databases, &block)
|
|
44
|
+
return to_enum(__method__, databases) unless block_given?
|
|
45
|
+
databases.each { |database| database.tables.each(&block) }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Txdb
|
|
2
|
+
module Handlers
|
|
3
|
+
module Triggers
|
|
4
|
+
|
|
5
|
+
class PullHandler < Handler
|
|
6
|
+
def handle
|
|
7
|
+
handle_safely do
|
|
8
|
+
tables.each do |table|
|
|
9
|
+
locales_for(table).each do |locale|
|
|
10
|
+
Downloader.new(table.database).download_table(table, locale)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
respond_with(200, {})
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def locales_for(table)
|
|
21
|
+
locale_cache[table.resource.project_slug] ||=
|
|
22
|
+
table.database.transifex_api
|
|
23
|
+
.get_languages(table.resource.project_slug)
|
|
24
|
+
.map { |locale| locale['language_code'] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def locale_cache
|
|
28
|
+
@locale_cache ||= {}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Txdb
|
|
2
|
+
module Handlers
|
|
3
|
+
module Triggers
|
|
4
|
+
|
|
5
|
+
class PushHandler < Handler
|
|
6
|
+
def handle
|
|
7
|
+
handle_safely do
|
|
8
|
+
tables.each do |table|
|
|
9
|
+
Uploader.new(table.database).upload_table(table)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
respond_with(200, {})
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Txdb
|
|
2
|
+
module ResponseHelpers
|
|
3
|
+
private
|
|
4
|
+
|
|
5
|
+
def respond_with(status, body, e = nil)
|
|
6
|
+
Txdb::Response.new(status, body, e)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def respond_with_error(status, message, e = nil)
|
|
10
|
+
respond_with(status, error(message), e)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def error(message)
|
|
14
|
+
[{ error: message }]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def data(body)
|
|
18
|
+
{ data: body }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# includes these methods in the singleton class as well
|
|
22
|
+
def self.included(base)
|
|
23
|
+
base.extend(self)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/txdb/table.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'txgh'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
|
|
4
|
+
module Txdb
|
|
5
|
+
class Table
|
|
6
|
+
attr_reader :database, :name, :columns
|
|
7
|
+
attr_reader :source_lang
|
|
8
|
+
|
|
9
|
+
def initialize(database, options = {})
|
|
10
|
+
@database = database
|
|
11
|
+
@name = options.fetch(:name)
|
|
12
|
+
@columns = options.fetch(:columns)
|
|
13
|
+
@source_lang = options.fetch(:source_lang)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def db
|
|
17
|
+
database.db.from(name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def resource
|
|
21
|
+
@resource ||= Txdb::TxResource.from_table(self)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read_content
|
|
25
|
+
YAML.dump(
|
|
26
|
+
database.backend.read_content_from(self)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def write_content(content, locale)
|
|
31
|
+
database.backend.write_content_to(
|
|
32
|
+
self, YAML.load(content), locale
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|