qravan 0.1.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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +61 -0
  4. data/.rspec +3 -0
  5. data/.rspec_status +4 -0
  6. data/.rubocop.yml +13 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +5 -0
  9. data/Gemfile.lock +143 -0
  10. data/LICENSE +5 -0
  11. data/README.md +191 -0
  12. data/Rakefile +39 -0
  13. data/assets/images/qravan.png +0 -0
  14. data/assets/images/qravan_repo.png +0 -0
  15. data/bin/console +15 -0
  16. data/bin/setup +8 -0
  17. data/config.ru +46 -0
  18. data/examples/queries.md +114 -0
  19. data/exe/qravan +7 -0
  20. data/ext/qravan/extconf.rb +5 -0
  21. data/ext/qravan/qravan.c +9 -0
  22. data/ext/qravan/qravan.h +6 -0
  23. data/falcon.rb +12 -0
  24. data/lib/qravan/async_loader.rb +33 -0
  25. data/lib/qravan/cargo.rb +80 -0
  26. data/lib/qravan/credentials/request.rb +54 -0
  27. data/lib/qravan/credentials/response.rb +41 -0
  28. data/lib/qravan/credentials/signature.rb +30 -0
  29. data/lib/qravan/credentials/system.rb +33 -0
  30. data/lib/qravan/credentials.rb +29 -0
  31. data/lib/qravan/model.rb +20 -0
  32. data/lib/qravan/qravan.bundle +0 -0
  33. data/lib/qravan/query/request.rb +40 -0
  34. data/lib/qravan/query/response.rb +26 -0
  35. data/lib/qravan/query.rb +30 -0
  36. data/lib/qravan/resource/resource.rb +35 -0
  37. data/lib/qravan/source/db.rb +35 -0
  38. data/lib/qravan/source/rest.rb +36 -0
  39. data/lib/qravan/source.rb +27 -0
  40. data/lib/qravan/version.rb +35 -0
  41. data/lib/qravan.rb +25 -0
  42. data/logs/db.log +2660 -0
  43. data/models/base/1.0/model.yaml +32 -0
  44. data/models/base/current +1 -0
  45. data/models/current/.keep +0 -0
  46. data/models/current/model.yaml +158 -0
  47. data/models/vehicles/1.0/model.yaml +126 -0
  48. data/qravan.gemspec +48 -0
  49. data/sources/base/1.0/source.yaml +9 -0
  50. data/sources/base/current +1 -0
  51. data/sources/postgres/1.0/source.yaml +10 -0
  52. data/sources/postgres/current +1 -0
  53. data/sources/prostore/1.0/source.yaml +13 -0
  54. data/sources/prostore/current +1 -0
  55. metadata +296 -0
@@ -0,0 +1,9 @@
1
+ #include "qravan.h"
2
+
3
+ VALUE rb_mQravan;
4
+
5
+ void
6
+ Init_qravan(void)
7
+ {
8
+ rb_mQravan = rb_define_module("Qravan");
9
+ }
@@ -0,0 +1,6 @@
1
+ #ifndef QRAVAN_H
2
+ #define QRAVAN_H 1
3
+
4
+ #include "ruby.h"
5
+
6
+ #endif /* QRAVAN_H */
data/falcon.rb ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env -S falcon host
2
+ # frozen_string_literal: true
3
+
4
+ load :rack, :lets_encrypt_tls, :supervisor
5
+
6
+ hostname = File.basename(__dir__)
7
+ rack hostname, :lets_encrypt_tls do
8
+ cache true
9
+ endpoint Async::HTTP::Endpoint.parse("http://localhost:3300").with(protocol: Async::HTTP::Protocol::HTTP1)
10
+ end
11
+
12
+ supervisor
@@ -0,0 +1,33 @@
1
+ require "async"
2
+ require "async/barrier"
3
+ require "async/http/internet"
4
+
5
+ class AsyncLoader
6
+ def self.perform(urls)
7
+ internet = Async::HTTP::Internet.new
8
+ barrier = Async::Barrier.new
9
+ time = Time.now
10
+ requests = 0
11
+ result = []
12
+ urls.each do |url|
13
+ barrier.async do
14
+ Console.logger.info "AsyncHttp#get: #{url}"
15
+ begin
16
+ response = internet.get(url)
17
+ body = JSON.parse(response.read)
18
+ result << { url: url, body: body }
19
+ requests += 1
20
+ ensure
21
+ response.finish
22
+ end
23
+ Console.logger.info "AsyncHttp#fulfill: #{url}"
24
+ end
25
+ end
26
+
27
+ Console.logger.info "AsyncHttp#wait"
28
+ barrier.wait
29
+ total = Time.now - time
30
+ result.unshift({time: "Duration: #{Time.now - time}s for #{requests} (RPS: #{(requests / total).to_i} r/s)"})
31
+ result.to_json
32
+ end
33
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qravan
4
+ # Load models and sources to cargo
5
+ class Cargo
6
+ attr_accessor :sources, :models, :connections
7
+
8
+ def initialize
9
+ Console.logger.info "Cargo initialized:"
10
+ Console.logger.info "Sources: #{sources.count}"
11
+ Console.logger.info "Models: #{models.count}"
12
+ Console.logger.info "Connections: #{connections.count}"
13
+ end
14
+
15
+ def sources
16
+ @sources ||= preload_sources
17
+ end
18
+
19
+ def models
20
+ @models ||= preload_models
21
+ end
22
+
23
+ def connections
24
+ @connections ||= connect_sources
25
+ end
26
+
27
+ def preload_sources
28
+ @sources = {}
29
+ list("sources").each do |source|
30
+ Console.logger.info "Reading source #{source}"
31
+ preloaded_source = YAML.load_file("#{source}/1.0/source.yaml")
32
+ @sources[preloaded_source.keys.first] = preloaded_source.first.last
33
+ end
34
+ @sources
35
+ end
36
+
37
+ def concat_models
38
+ model_files = {}
39
+ (list("models") - %w[models/current models/base]).each do |model|
40
+ Console.logger.info "Reading model file #{model}/1.0/model.yaml"
41
+ model_files[model] = File.readlines("#{model}/1.0/model.yaml").map(&:chomp)
42
+ end
43
+
44
+ File.open("models/current/model.yaml", "w") do |output_file|
45
+ output_file.puts File.readlines("models/base/1.0/model.yaml").map(&:chomp)
46
+ model_files.each { |elem| output_file.puts elem[1] }
47
+ end
48
+ end
49
+
50
+ def preload_models
51
+ concat_models
52
+ @models = {}
53
+ Console.logger.info "Preparing models"
54
+ preloaded_models = YAML.load_file("models/current/model.yaml")
55
+ preloaded_models["resources"].each do |model|
56
+ @models[model.keys.first] = model
57
+ end
58
+ @models
59
+ end
60
+
61
+ def connect_sources
62
+ @connections = {}
63
+ sources.each do |source|
64
+ case source[1]["type"]
65
+ when "rest"
66
+ @connections[source[0]] = Qravan::Rest.new(source[1])
67
+ when "db"
68
+ @connections[source[0]] = Qravan::Db.new(source[1])
69
+ else
70
+ Console.logger.warn "Not supported resource"
71
+ end
72
+ end
73
+ @connections
74
+ end
75
+
76
+ def list(what = "sources")
77
+ Dir["#{what}/*"]
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qravan
4
+ module Credentials
5
+ # Request credentials
6
+ class Request
7
+ attr_accessor :credentials
8
+
9
+ def initialize(credentials)
10
+ @credentials = credentials["request"]
11
+ end
12
+
13
+ def prepare
14
+ {
15
+ id: id,
16
+ sub_id: sub_id,
17
+ name: name,
18
+ purpose_id: purpose,
19
+ audit: audit,
20
+ audit_id: audit_id,
21
+ audit_token: audit_token
22
+ }
23
+ end
24
+
25
+ def id
26
+ @credentials["id"]
27
+ end
28
+
29
+ def sub_id
30
+ @credentials["sub_id"]
31
+ end
32
+
33
+ def name
34
+ @credentials["name"]
35
+ end
36
+
37
+ def purpose
38
+ @credentials["purpose_id"]
39
+ end
40
+
41
+ def audit
42
+ @credentials["audit"]
43
+ end
44
+
45
+ def audit_id
46
+ @credentials["audit_id"]
47
+ end
48
+
49
+ def audit_token
50
+ @credentials["audit_token"]
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Qravan
6
+ module Credentials
7
+ # Response credentials
8
+ class Response
9
+ attr_accessor :credentials
10
+
11
+ def initialize(response_credentials = {})
12
+ @credentials = response_credentials
13
+ end
14
+
15
+ def prepare
16
+ {
17
+ id: id,
18
+ sub_id: sub_id,
19
+ started_at: started_at,
20
+ finished_at: finished_at
21
+ }
22
+ end
23
+
24
+ def id
25
+ SecureRandom.uuid
26
+ end
27
+
28
+ def sub_id
29
+ SecureRandom.uuid
30
+ end
31
+
32
+ def started_at
33
+ Time.now
34
+ end
35
+
36
+ def finished_at
37
+ Time.now
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qravan
4
+ module Credentials
5
+ # System credentials
6
+ class Signature
7
+ attr_accessor :credentials
8
+
9
+ def initialize(credentials)
10
+ @credentials = credentials["signature"]
11
+ end
12
+
13
+ def prepare
14
+ {
15
+ digest: digest,
16
+ signature: signature
17
+ }
18
+ end
19
+
20
+ def digest
21
+ @credentials["digest"]
22
+ end
23
+
24
+ def signature
25
+ @credentials["signature"]
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ module Qravan
3
+ module Credentials
4
+ # System credentials
5
+ class System
6
+ attr_accessor :credentials
7
+
8
+ def initialize(credentials)
9
+ @credentials = credentials["system"]
10
+ end
11
+
12
+ def prepare
13
+ {
14
+ mnemonic: mnemonic,
15
+ instance_id: instance,
16
+ user_id: user
17
+ }
18
+ end
19
+
20
+ def mnemonic
21
+ @credentials["mnemonic"]
22
+ end
23
+
24
+ def instance
25
+ @credentials["instance_id"]
26
+ end
27
+
28
+ def user
29
+ @credentials["user_id"]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "credentials/system"
2
+ require_relative "credentials/request"
3
+ require_relative "credentials/response"
4
+ require_relative "credentials/signature"
5
+
6
+ module Qravan
7
+ BANNER = %(
8
+ Data API Qravan Server
9
+ Developed at CAPAA by Alexander Panasenkov, 2022
10
+ ).freeze
11
+ module Credentials
12
+ class Query
13
+ attr_accessor :query_credentials
14
+
15
+ def initialize(query_credentials)
16
+ @query_credentials ||= query_credentials
17
+ end
18
+
19
+ def credentials
20
+ {
21
+ response: Qravan::Credentials::Response.new(@query_credentials).prepare,
22
+ system: Qravan::Credentials::System.new(@query_credentials).prepare,
23
+ request: Qravan::Credentials::Request.new(@query_credentials).prepare,
24
+ signature: Qravan::Credentials::Signature.new(@query_credentials).prepare
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qravan
4
+ # Models for data extractions class
5
+ class Model
6
+ attr_accessor :models
7
+
8
+ def initialize(cargo = {})
9
+ @models ||= cargo.models
10
+ end
11
+
12
+ def call(env)
13
+ body = [models.to_json]
14
+ status = 200
15
+ headers = { "content-type" => "application/json" }
16
+
17
+ [status, headers, body]
18
+ end
19
+ end
20
+ end
Binary file
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require "async"
3
+ require "async/barrier"
4
+
5
+ module Qravan
6
+ # Requests processing class
7
+ class Request
8
+ attr_accessor :resources, :sources, :cargo
9
+
10
+ def initialize(request, cargo = {})
11
+ @query = request["query"]
12
+ @credentials = request["credentials"]
13
+ @resources = []
14
+ @cargo ||= cargo
15
+
16
+ validate
17
+ extract_resources
18
+ end
19
+
20
+ def validate
21
+ @query
22
+ end
23
+
24
+ def extract_resources
25
+ barrier = Async::Barrier.new
26
+ barrier.async do
27
+ @query.each do |key, resource|
28
+ @resources << { key => Qravan::Resource.new(key, resource, cargo).extract }
29
+ end
30
+ ensure
31
+ @resources
32
+ end
33
+ barrier.wait
34
+ end
35
+
36
+ def credentials
37
+ @credentials
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qravan
4
+ class Response
5
+ attr_accessor :request
6
+ attr_accessor :resources
7
+ attr_accessor :credentials
8
+
9
+ def initialize(request)
10
+ @request ||= request
11
+ @resources = request.resources
12
+ @credentials = request.credentials
13
+ end
14
+
15
+ def validate
16
+ @request
17
+ end
18
+
19
+ def answer
20
+ {
21
+ "response": @resources,
22
+ "credentials": Qravan::Credentials::Query.new(@credentials).credentials
23
+ }.to_json
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ require_relative "query/request"
3
+ require_relative "query/response"
4
+ require_relative "resource/resource"
5
+ require_relative "source/db"
6
+ require_relative "source/rest"
7
+
8
+ module Qravan
9
+
10
+ # Sources for data extractions class
11
+ class Query
12
+ attr_accessor :cargo
13
+
14
+ def initialize(cargo)
15
+ @cargo ||= cargo
16
+ end
17
+
18
+ def call(env)
19
+ query = Rack::Request.new(env)
20
+ request = Qravan::Request.new(JSON.parse(query.body.read), cargo)
21
+ response = Qravan::Response.new(request)
22
+
23
+ body = [response.answer]
24
+ status = 200
25
+ headers = { "content-type" => "application/json" }
26
+
27
+ [status, headers, body]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module Qravan
3
+ # Resource processing class
4
+ class Resource
5
+ attr_accessor :connections, :cargo
6
+
7
+ def initialize(name, data, cargo = {})
8
+ @resource = name
9
+ @resource_data = data
10
+ @cargo ||= cargo
11
+ @connections = cargo.connections
12
+ end
13
+
14
+ def validate
15
+ @resource_data
16
+ end
17
+
18
+ def extract
19
+ source_name = "#{cargo.models[@resource]["extract"]["source"]["name"]}_source"
20
+ @connections[source_name.to_s].extract(sql)
21
+ end
22
+
23
+ def sql
24
+ [
25
+ :select,
26
+ @resource_data["attributes"].join(","),
27
+ :from,
28
+ cargo.models[@resource]["extract"]["source"]["table"],
29
+ :where,
30
+ @resource_data["conditions"].map { |k, v| "#{k}='#{v}'" }
31
+ .join(" and ")
32
+ ].join(" ")
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async"
4
+ require "sequel"
5
+ require "logger"
6
+ module Qravan
7
+ # Database class
8
+ class Db
9
+ attr_accessor :result, :connection
10
+
11
+ def initialize(source)
12
+ @connection = Sequel.connect(adapter: source["adapter"],
13
+ user: source["user"],
14
+ password: source["password"],
15
+ host: source["host"],
16
+ port: source["port"],
17
+ database: source["database"],
18
+ max_connections: source["max_connections"],
19
+ logger: Logger.new("logs/db.log"))
20
+
21
+ end
22
+
23
+ def extract(sql)
24
+ Console.logger.info "DB EXTRACT: #{sql}"
25
+ @result = []
26
+ time = Time.now
27
+ @connection[sql].each do |row|
28
+ @result << row
29
+ end
30
+ total = Time.now - time
31
+ @result << { time: "DB Duration: #{Time.now - time}s" }
32
+ @result
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async"
4
+ require "async/barrier"
5
+ require "logger"
6
+ module Qravan
7
+ # Database class
8
+ class Rest
9
+ attr_accessor :result, :connection
10
+
11
+ def initialize(source)
12
+ @connection = source
13
+ end
14
+
15
+ def extract(sql = "select * from smevql1.driverlicenseql3;")
16
+ Console.logger.info "REST EXTRACT: #{sql}"
17
+ internet = Async::HTTP::Internet.new
18
+ barrier = Async::Barrier.new
19
+ time = Time.now
20
+ @result = []
21
+ url = "#{@connection["protocol"]}://#{@connection["host"]}:#{@connection["port"]}/#{@connection["path"]}"
22
+ headers = [%w[accept application/json], %w[content-type application/json]]
23
+ body = [@connection["template"] % { request: sql }]
24
+ barrier.async do
25
+ response = internet.post(url, headers, body)
26
+ response_body = JSON.parse(response.read)
27
+ @result << response_body[@connection["payload-path"]]
28
+ ensure
29
+ response.finish
30
+ end
31
+ barrier.wait
32
+ @result << { time: "REST Duration: #{Time.now - time}s " }
33
+ @result
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Qravan
6
+ # Sources for data extractions class
7
+ class Source
8
+ attr_accessor :sources
9
+
10
+ def initialize(cargo = {})
11
+ @sources ||= cargo.sources
12
+ end
13
+
14
+ def call(env)
15
+ body = [unpassworded.to_json]
16
+ status = 200
17
+ headers = { "content-type" => "application/json" }
18
+
19
+ [status, headers, body]
20
+ end
21
+
22
+ def unpassworded
23
+ sources.map { |key, value| sources[key]["password"] = "******" if sources[key]["password"] }
24
+ sources
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qravan
4
+ VERSION = "0.1.0"
5
+ ENV = "development"
6
+
7
+ class Spec
8
+
9
+ def call(env)
10
+ body = [Qravan::Spec.collect_spec.to_json]
11
+ status = 200
12
+ headers = { "content-type" => "application/json" }
13
+
14
+ [status, headers, body]
15
+ end
16
+
17
+ class << self
18
+ def collect_spec
19
+ {
20
+ "spec": {
21
+ "server": {
22
+ "type": "Qravan Server",
23
+ "version": Qravan::VERSION,
24
+ "env": Qravan::ENV
25
+ },
26
+ "protocol": {
27
+ "type": "Qravan Spec",
28
+ "version": "1.0"
29
+ }
30
+ }
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/qravan.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "qravan/version"
4
+ require_relative "qravan/cargo"
5
+ require_relative "qravan/source"
6
+ require_relative "qravan/model"
7
+ require_relative "qravan/query"
8
+ require_relative "qravan/credentials"
9
+
10
+ require "async"
11
+ require "async/http/internet"
12
+ require "redis"
13
+
14
+ module Qravan
15
+ class Error < StandardError; end
16
+
17
+ def self.root
18
+ File.dirname __dir__
19
+ end
20
+
21
+ def self.logs
22
+ File.join root, "logs"
23
+ end
24
+
25
+ end