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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/README.md +39 -0
  4. data/lib/txdb.rb +32 -0
  5. data/lib/txdb/app.rb +36 -0
  6. data/lib/txdb/backends.rb +19 -0
  7. data/lib/txdb/backends/globalize.rb +10 -0
  8. data/lib/txdb/backends/globalize/backend.rb +19 -0
  9. data/lib/txdb/backends/globalize/helpers.rb +19 -0
  10. data/lib/txdb/backends/globalize/reader.rb +63 -0
  11. data/lib/txdb/backends/globalize/writer.rb +39 -0
  12. data/lib/txdb/config.rb +52 -0
  13. data/lib/txdb/connection_string.rb +18 -0
  14. data/lib/txdb/database.rb +40 -0
  15. data/lib/txdb/downloader.rb +32 -0
  16. data/lib/txdb/handlers.rb +6 -0
  17. data/lib/txdb/handlers/hook_handler.rb +59 -0
  18. data/lib/txdb/handlers/triggers.rb +9 -0
  19. data/lib/txdb/handlers/triggers/handler.rb +50 -0
  20. data/lib/txdb/handlers/triggers/pull_handler.rb +34 -0
  21. data/lib/txdb/handlers/triggers/push_handler.rb +19 -0
  22. data/lib/txdb/response.rb +11 -0
  23. data/lib/txdb/response_helpers.rb +26 -0
  24. data/lib/txdb/table.rb +36 -0
  25. data/lib/txdb/transifex_project.rb +17 -0
  26. data/lib/txdb/tx_resource.rb +52 -0
  27. data/lib/txdb/uploader.rb +33 -0
  28. data/lib/txdb/version.rb +3 -0
  29. data/spec/backends/globalize/helpers_spec.rb +17 -0
  30. data/spec/backends/globalize/reader_spec.rb +25 -0
  31. data/spec/backends/globalize/writer_spec.rb +53 -0
  32. data/spec/backends_spec.rb +30 -0
  33. data/spec/config_spec.rb +71 -0
  34. data/spec/connection_string_spec.rb +24 -0
  35. data/spec/database_spec.rb +33 -0
  36. data/spec/downloader_spec.rb +36 -0
  37. data/spec/handlers/hook_handler_spec.rb +73 -0
  38. data/spec/handlers/triggers/pull_handler_spec.rb +58 -0
  39. data/spec/handlers/triggers/push_handler_spec.rb +49 -0
  40. data/spec/spec_helper.rb +20 -0
  41. data/spec/spec_helpers/env_helpers.rb +13 -0
  42. data/spec/spec_helpers/globalize_db.rb +60 -0
  43. data/spec/spec_helpers/test_backend.rb +28 -0
  44. data/spec/spec_helpers/test_db.rb +76 -0
  45. data/spec/table_spec.rb +58 -0
  46. data/spec/test.sqlite3 +0 -0
  47. data/spec/transifex_project_spec.rb +15 -0
  48. data/spec/tx_resource_spec.rb +17 -0
  49. data/spec/uploader_spec.rb +36 -0
  50. data/txdb.gemspec +22 -0
  51. 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
@@ -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,6 @@
1
+ module Txdb
2
+ module Handlers
3
+ autoload :HookHandler, 'txdb/handlers/hook_handler'
4
+ autoload :Triggers, 'txdb/handlers/triggers'
5
+ end
6
+ 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,9 @@
1
+ module Txdb
2
+ module Handlers
3
+ module Triggers
4
+ autoload :Handler, 'txdb/handlers/triggers/handler'
5
+ autoload :PullHandler, 'txdb/handlers/triggers/pull_handler'
6
+ autoload :PushHandler, 'txdb/handlers/triggers/push_handler'
7
+ end
8
+ end
9
+ 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,11 @@
1
+ module Txdb
2
+ class Response
3
+ attr_reader :status, :body, :error
4
+
5
+ def initialize(status, body, error = nil)
6
+ @status = status
7
+ @body = body
8
+ @error = error
9
+ end
10
+ end
11
+ 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