qravan 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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