bas 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +68 -147
  4. data/lib/bas/bot/base.rb +74 -0
  5. data/lib/bas/bot/compare_wip_limit_count.rb +92 -0
  6. data/lib/bas/bot/fetch_birthdays_from_notion.rb +128 -0
  7. data/lib/bas/bot/fetch_domains_wip_counts_from_notion.rb +121 -0
  8. data/lib/bas/bot/fetch_domains_wip_limit_from_notion.rb +134 -0
  9. data/lib/bas/bot/fetch_emails_from_imap.rb +99 -0
  10. data/lib/bas/bot/fetch_next_week_birthdays_from_notion.rb +142 -0
  11. data/lib/bas/bot/fetch_next_week_ptos_from_notion.rb +162 -0
  12. data/lib/bas/bot/fetch_ptos_from_notion.rb +138 -0
  13. data/lib/bas/bot/format_birthdays.rb +97 -0
  14. data/lib/bas/bot/format_emails.rb +124 -0
  15. data/lib/bas/bot/format_wip_limit_exceeded.rb +97 -0
  16. data/lib/bas/bot/garbage_collector.rb +85 -0
  17. data/lib/bas/bot/humanize_pto.rb +119 -0
  18. data/lib/bas/bot/notify_discord.rb +96 -0
  19. data/lib/bas/read/base.rb +10 -23
  20. data/lib/bas/read/default.rb +16 -0
  21. data/lib/bas/read/postgres.rb +44 -0
  22. data/lib/bas/read/types/response.rb +18 -0
  23. data/lib/bas/utils/discord/integration.rb +43 -0
  24. data/lib/bas/utils/exceptions/function_not_implemented.rb +16 -0
  25. data/lib/bas/utils/exceptions/invalid_process_response.rb +16 -0
  26. data/lib/bas/utils/imap/request.rb +76 -0
  27. data/lib/bas/utils/notion/request.rb +45 -0
  28. data/lib/bas/utils/openai/run_assistant.rb +99 -0
  29. data/lib/bas/utils/postgres/request.rb +50 -0
  30. data/lib/bas/version.rb +1 -1
  31. data/lib/bas/write/base.rb +12 -17
  32. data/lib/bas/write/postgres.rb +45 -0
  33. data/lib/bas/write/postgres_update.rb +49 -0
  34. data/lib/bas.rb +1 -3
  35. metadata +30 -67
  36. data/lib/bas/domain/birthday.rb +0 -25
  37. data/lib/bas/domain/email.rb +0 -34
  38. data/lib/bas/domain/exceptions/function_not_implemented.rb +0 -18
  39. data/lib/bas/domain/issue.rb +0 -22
  40. data/lib/bas/domain/notification.rb +0 -23
  41. data/lib/bas/domain/pto.rb +0 -69
  42. data/lib/bas/domain/work_items_limit.rb +0 -25
  43. data/lib/bas/formatter/base.rb +0 -53
  44. data/lib/bas/formatter/birthday.rb +0 -38
  45. data/lib/bas/formatter/exceptions/invalid_data.rb +0 -15
  46. data/lib/bas/formatter/notification.rb +0 -34
  47. data/lib/bas/formatter/pto.rb +0 -89
  48. data/lib/bas/formatter/support_emails.rb +0 -73
  49. data/lib/bas/formatter/types/response.rb +0 -16
  50. data/lib/bas/formatter/work_items_limit.rb +0 -68
  51. data/lib/bas/process/base.rb +0 -39
  52. data/lib/bas/process/discord/exceptions/invalid_webhook_token.rb +0 -16
  53. data/lib/bas/process/discord/implementation.rb +0 -71
  54. data/lib/bas/process/discord/types/response.rb +0 -22
  55. data/lib/bas/process/openai/base.rb +0 -72
  56. data/lib/bas/process/openai/helper.rb +0 -19
  57. data/lib/bas/process/openai/types/response.rb +0 -27
  58. data/lib/bas/process/openai/use_case/humanize_pto.rb +0 -53
  59. data/lib/bas/process/slack/exceptions/invalid_webhook_token.rb +0 -16
  60. data/lib/bas/process/slack/implementation.rb +0 -70
  61. data/lib/bas/process/slack/types/response.rb +0 -21
  62. data/lib/bas/process/types/response.rb +0 -16
  63. data/lib/bas/read/github/base.rb +0 -57
  64. data/lib/bas/read/github/types/response.rb +0 -27
  65. data/lib/bas/read/github/use_case/repo_issues.rb +0 -17
  66. data/lib/bas/read/imap/base.rb +0 -70
  67. data/lib/bas/read/imap/types/response.rb +0 -27
  68. data/lib/bas/read/imap/use_case/support_emails.rb +0 -26
  69. data/lib/bas/read/notion/base.rb +0 -52
  70. data/lib/bas/read/notion/exceptions/invalid_api_key.rb +0 -15
  71. data/lib/bas/read/notion/exceptions/invalid_database_id.rb +0 -15
  72. data/lib/bas/read/notion/helper.rb +0 -21
  73. data/lib/bas/read/notion/types/response.rb +0 -26
  74. data/lib/bas/read/notion/use_case/birthday_next_week.rb +0 -41
  75. data/lib/bas/read/notion/use_case/birthday_today.rb +0 -29
  76. data/lib/bas/read/notion/use_case/notification.rb +0 -28
  77. data/lib/bas/read/notion/use_case/pto_next_week.rb +0 -71
  78. data/lib/bas/read/notion/use_case/pto_today.rb +0 -30
  79. data/lib/bas/read/notion/use_case/work_items_limit.rb +0 -37
  80. data/lib/bas/read/postgres/base.rb +0 -46
  81. data/lib/bas/read/postgres/helper.rb +0 -16
  82. data/lib/bas/read/postgres/types/response.rb +0 -42
  83. data/lib/bas/read/postgres/use_case/pto_today.rb +0 -32
  84. data/lib/bas/serialize/base.rb +0 -30
  85. data/lib/bas/serialize/github/issues.rb +0 -57
  86. data/lib/bas/serialize/imap/support_emails.rb +0 -56
  87. data/lib/bas/serialize/notion/birthday_today.rb +0 -68
  88. data/lib/bas/serialize/notion/notification.rb +0 -56
  89. data/lib/bas/serialize/notion/pto_today.rb +0 -75
  90. data/lib/bas/serialize/notion/work_items_limit.rb +0 -65
  91. data/lib/bas/serialize/postgres/pto_today.rb +0 -47
  92. data/lib/bas/use_cases/types/config.rb +0 -20
  93. data/lib/bas/use_cases/use_case.rb +0 -42
  94. data/lib/bas/use_cases/use_cases.rb +0 -465
  95. data/lib/bas/write/logs/base.rb +0 -33
  96. data/lib/bas/write/logs/use_case/console_log.rb +0 -22
  97. data/lib/bas/write/notion/base.rb +0 -36
  98. data/lib/bas/write/notion/use_case/empty_notification.rb +0 -38
  99. data/lib/bas/write/notion/use_case/notification.rb +0 -38
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../write/postgres"
6
+ require_relative "../utils/discord/integration"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::NotifyDiscord class serves as a bot implementation to send messages to a
11
+ # Discord readed from a PostgresDB table.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # options = {
17
+ # read_options: {
18
+ # connection: {
19
+ # host: "host",
20
+ # port: 5432,
21
+ # dbname: "bas",
22
+ # user: "postgres",
23
+ # password: "postgres"
24
+ # },
25
+ # db_table: "pto",
26
+ # tag: "HumanizePto"
27
+ # },
28
+ # process_options: {
29
+ # name: "bot name to be shown on discord",
30
+ # webhook: "discord webhook"
31
+ # },
32
+ # write_options: {
33
+ # connection: {
34
+ # host: "host",
35
+ # port: 5432,
36
+ # dbname: "bas",
37
+ # user: "postgres",
38
+ # password: "postgres"
39
+ # },
40
+ # db_table: "pto",
41
+ # tag: "NotifyDiscord"
42
+ # }
43
+ # }
44
+ #
45
+ # bot = Bot::NotifyDiscord.new(options)
46
+ # bot.execute
47
+ #
48
+ class NotifyDiscord < Bot::Base
49
+ # read function to execute the PostgresDB Read component
50
+ #
51
+ def read
52
+ reader = Read::Postgres.new(read_options.merge(conditions))
53
+
54
+ reader.execute
55
+ end
56
+
57
+ # process function to execute the Discord utility to send the PTO's notification
58
+ #
59
+ def process
60
+ return { success: {} } if unprocessable_response
61
+
62
+ response = Utils::Discord::Integration.execute(params)
63
+
64
+ if response.code == 204
65
+ { success: {} }
66
+ else
67
+ { error: { message: response.parsed_response, status_code: response.code } }
68
+ end
69
+ end
70
+
71
+ # write function to execute the PostgresDB write component
72
+ #
73
+ def write
74
+ write = Write::Postgres.new(write_options, process_response)
75
+
76
+ write.execute
77
+ end
78
+
79
+ private
80
+
81
+ def conditions
82
+ {
83
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
84
+ params: [false, read_options[:tag], "unprocessed"]
85
+ }
86
+ end
87
+
88
+ def params
89
+ {
90
+ name: process_options[:name],
91
+ notification: read_response.data["notification"],
92
+ webhook: process_options[:webhook]
93
+ }
94
+ end
95
+ end
96
+ end
data/lib/bas/read/base.rb CHANGED
@@ -1,43 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../domain/exceptions/function_not_implemented"
3
+ require_relative "../utils/exceptions/function_not_implemented"
4
4
 
5
5
  module Read
6
6
  ##
7
- # The Read::Base class serves as the foundation for implementing specific data readers within the Read module.
8
- # Operating as an interface, this class defines essential attributes and methods, providing a blueprint for creating
9
- # custom readers tailored to different data sources.
7
+ # The Read::Base class serves as the foundation for implementing specific data read components within
8
+ # the Read module. Operating as an interface, this class defines essential attributes and methods,
9
+ # providing a blueprint for creating custom read components tailored to different data sources.
10
10
  #
11
11
  class Base
12
12
  attr_reader :config
13
13
 
14
- # Initializes the reader with essential configuration parameters.
14
+ # Initializes the read with essential configuration parameters.
15
15
  #
16
- def initialize(config)
16
+ def initialize(config = {})
17
17
  @config = config
18
18
  end
19
19
 
20
- # A method meant to execute the read request from an specific source depending on the implementation.
21
- # Must be overridden by subclasses, with specific logic based on the use case.
20
+ # A method meant to execute the read request from an specific <b>common storage</b>.
21
+ # Must be overridden by subclasses, with specific logic based on the storage source.
22
22
  #
23
23
  # <br>
24
- # <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
24
+ # <b>raises</b> <tt>Utils::Exceptions::FunctionNotImplemented</tt> when missing implementation.
25
25
  #
26
26
  def execute
27
- raise Domain::Exceptions::FunctionNotImplemented
28
- end
29
-
30
- protected
31
-
32
- # A method meant to read from the source, retrieven the required data
33
- # from an specific filter configuration depending on the use case implementation.
34
- # Must be overridden by subclasses, with specific logic based on the use case.
35
- #
36
- # <br>
37
- # <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
38
- #
39
- def read(*_filters)
40
- raise Domain::Exceptions::FunctionNotImplemented
27
+ raise Utils::Exceptions::FunctionNotImplemented
41
28
  end
42
29
  end
43
30
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "./types/response"
5
+
6
+ module Read
7
+ ##
8
+ # This class is an implementation of the Read::Base interface, specifically designed
9
+ # for bots who don't read from a <b>common storage</b>".
10
+ #
11
+ class Default < Read::Base
12
+ def execute
13
+ Read::Types::Response.new
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require_relative "./base"
6
+ require_relative "../utils/postgres/request"
7
+ require_relative "./types/response"
8
+
9
+ module Read
10
+ ##
11
+ # This class is an implementation of the Read::Base interface, specifically designed
12
+ # to read from a PostgresDB used as <b>common storage</b>.
13
+ #
14
+ class Postgres < Read::Base
15
+ # Execute the Postgres utility to read data from the <b>common storage</b>
16
+ #
17
+ def execute
18
+ response = Utils::Postgres::Request.execute(params)
19
+
20
+ unless response.values == []
21
+ id = response.values.first[0]
22
+ data = JSON.parse(response.values.first[1])
23
+ inserted_at = response.values.first[2]
24
+ end
25
+
26
+ Read::Types::Response.new(id, data, inserted_at)
27
+ end
28
+
29
+ private
30
+
31
+ def params
32
+ {
33
+ connection: config[:connection],
34
+ query: build_query
35
+ }
36
+ end
37
+
38
+ def build_query
39
+ query = "SELECT id, data, inserted_at FROM #{config[:db_table]} WHERE status='success' AND #{config[:where]}"
40
+
41
+ [query, config[:params]]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Read
4
+ module Types
5
+ ##
6
+ # Represents a response from a read component. It encapsulates the requested data
7
+ # from the <b>common storage</b> to be processed by a Bot.
8
+ class Response
9
+ attr_reader :id, :data, :inserted_at
10
+
11
+ def initialize(id = nil, response = {}, inserted_at = nil)
12
+ @id = id
13
+ @data = response
14
+ @inserted_at = inserted_at
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httparty"
4
+
5
+ module Utils
6
+ module Discord
7
+ ##
8
+ # This module is a Discord utility for sending messages to Discord.
9
+ #
10
+ module Integration
11
+ # Implements the sending process logic to Discord. It sends a POST request to
12
+ # the Discord webhook with the specified payload.
13
+ #
14
+ # <br>
15
+ # <b>Params:</b>
16
+ # * <tt>webhook</tt> Discord webhook integration.
17
+ # * <tt>name</tt> Name of the discord user to send the message.
18
+ # * <tt>notification</tt> Text of the notification to be sent.
19
+ #
20
+ # <br>
21
+ # <b>returns</b> <tt>HTTParty::Response</tt>
22
+ #
23
+ def self.execute(params)
24
+ HTTParty.post(params[:webhook], { body: body(params), headers: })
25
+ end
26
+
27
+ # Request body
28
+ #
29
+ def self.body(params)
30
+ {
31
+ username: params[:name],
32
+ content: params[:notification]
33
+ }.to_json
34
+ end
35
+
36
+ # Request headers
37
+ #
38
+ def self.headers
39
+ { "Content-Type" => "application/json" }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Utils
4
+ module Exceptions
5
+ ##
6
+ # Representation for errors that occur when a function has not been implemented yet.
7
+ # It inherits from StandardError, and allows developers to raise a specific exception
8
+ # when a required function remains unimplemented in a subclass.
9
+ #
10
+ class FunctionNotImplemented < StandardError
11
+ def initialize(message = "The function haven't been implemented yet.")
12
+ super(message)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Utils
4
+ module Exceptions
5
+ ##
6
+ # Representation for errors that occur when a process function of a Bot returns
7
+ # something different than a Hash type object.
8
+ # It inherits from StandardError.
9
+ #
10
+ class InvalidProcessResponse < StandardError
11
+ def initialize(message = "The Process response should be a Hash type object")
12
+ super(message)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/imap"
4
+ require "gmail_xoauth"
5
+ require "httparty"
6
+
7
+ module Utils
8
+ module Imap
9
+ ##
10
+ # This module is a Imap utility for request emails from an Imap server
11
+ #
12
+ class Request
13
+ def initialize(params, query)
14
+ @refresh_token = params[:refresh_token]
15
+ @client_id = params[:client_id]
16
+ @client_secret = params[:client_secret]
17
+ @token_uri = params[:token_uri]
18
+ @email_domain = params[:email_domain]
19
+ @email_port = params[:email_port]
20
+ @user_email = params[:user_email]
21
+ @inbox = params[:inbox]
22
+ @query = query
23
+
24
+ @emails = []
25
+ end
26
+
27
+ # Execute the imap requets after authenticate the email with the credentials
28
+ #
29
+ def execute
30
+ response = refresh_token
31
+
32
+ return { error: response } unless response["error"].nil?
33
+
34
+ imap_fetch(response["access_token"])
35
+
36
+ { emails: @emails }
37
+ rescue StandardError => e
38
+ { error: e.to_s }
39
+ end
40
+
41
+ private
42
+
43
+ def imap_fetch(access_token)
44
+ imap = Net::IMAP.new(@email_domain, port: @email_port, ssl: true)
45
+
46
+ imap.authenticate("XOAUTH2", @user_email, access_token)
47
+
48
+ imap.examine(@inbox)
49
+
50
+ @emails = fetch_emails(imap)
51
+
52
+ imap.logout
53
+ imap.disconnect
54
+ end
55
+
56
+ def fetch_emails(imap)
57
+ imap.search(@query).map do |message_id|
58
+ { message_id:, message: imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] }
59
+ end
60
+ end
61
+
62
+ def refresh_token
63
+ HTTParty.post(@token_uri, { body: })
64
+ end
65
+
66
+ def body
67
+ {
68
+ "grant_type" => "refresh_token",
69
+ "refresh_token" => @refresh_token,
70
+ "client_id" => @client_id,
71
+ "client_secret" => @client_secret
72
+ }
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httparty"
4
+
5
+ module Utils
6
+ module Notion
7
+ ##
8
+ # This module is a Notion utility for sending request to create, update, or delete
9
+ # Notion resources.
10
+ #
11
+ module Request
12
+ NOTION_BASE_URL = "https://api.notion.com/v1"
13
+
14
+ # Implements the request process logic to Notion.
15
+ #
16
+ # <br>
17
+ # <b>Params:</b>
18
+ # * <tt>method</tt> HTTP request method: post, get, put, etc.
19
+ # * <tt>body</tt> Request body (Hash).
20
+ # * <tt>endpoint</tt> Notion resource endpoint.
21
+ # * <tt>secret</tt> Notion secret.
22
+ #
23
+ # <br>
24
+ # <b>returns</b> <tt>HTTParty::Response</tt>
25
+ #
26
+ def self.execute(params)
27
+ url = "#{NOTION_BASE_URL}/#{params[:endpoint]}"
28
+
29
+ headers = notion_headers(params[:secret])
30
+
31
+ HTTParty.send(params[:method], url, { body: params[:body].to_json, headers: })
32
+ end
33
+
34
+ # Request headers
35
+ #
36
+ def self.notion_headers(secret)
37
+ {
38
+ "Authorization" => "Bearer #{secret}",
39
+ "Content-Type" => "application/json",
40
+ "Notion-Version" => "2022-06-28"
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httparty"
4
+
5
+ module Utils
6
+ module OpenAI
7
+ ##
8
+ # This module is an OpenAI utility for using an already created OpenAI Assistant
9
+ # and get an AI response depending on the Assistant's instructions and prompt.
10
+ #
11
+ module RunAssitant
12
+ OPENAI_BASE_URL = "https://api.openai.com"
13
+ DEFAULT_N_CHOICES = 1
14
+
15
+ # Implements the request process logic to the OpenAI Assitant.
16
+ #
17
+ # <br>
18
+ # <b>Params:</b>
19
+ # * <tt>assistant_id</tt> Assistant id
20
+ # * <tt>prompt</tt> Text that communicates to AI to provide it more context.
21
+ # * <tt>secret</tt> OpenAI Secret API Key.
22
+ #
23
+ # <br>
24
+ # <b>returns</b> <tt>HTTParty::Response</tt>
25
+ #
26
+ def self.execute(params)
27
+ @params = params
28
+
29
+ run = create_thread_and_run
30
+
31
+ return run unless run.code == 200
32
+
33
+ run_fetched = poll_run(run.parsed_response)
34
+
35
+ return run_fetched unless run_fetched["status"] == "completed"
36
+
37
+ list_messages(run_fetched)
38
+ end
39
+
40
+ # Creates an OpenAI Thread and a Run using the given assistant_id and prompt.
41
+ #
42
+ def self.create_thread_and_run
43
+ url = "#{OPENAI_BASE_URL}/v1/threads/runs"
44
+
45
+ HTTParty.post(url, { body:, headers: })
46
+ end
47
+
48
+ # Retrieve the list of messages of a thread.
49
+ #
50
+ def self.list_messages(run)
51
+ url = "#{OPENAI_BASE_URL}/v1/threads/#{run["thread_id"]}/messages"
52
+
53
+ HTTParty.get(url, { headers: })
54
+ end
55
+
56
+ # Request body
57
+ #
58
+ def self.body
59
+ {
60
+ assistant_id: @params[:assistant_id],
61
+ thread: {
62
+ messages: [
63
+ role: "user",
64
+ content: @params[:prompt]
65
+ ]
66
+ }
67
+ }.to_json
68
+ end
69
+
70
+ # Request headers
71
+ #
72
+ def self.headers
73
+ {
74
+ "Authorization" => "Bearer #{@params[:secret]}",
75
+ "Content-Type" => "application/json",
76
+ "OpenAI-Beta" => "assistants=v2"
77
+ }
78
+ end
79
+
80
+ # Polls the Run until it is processed.
81
+ #
82
+ def self.poll_run(run)
83
+ url = "#{OPENAI_BASE_URL}/v1/threads/#{run["thread_id"]}/runs/#{run["id"]}"
84
+
85
+ while true
86
+ run_fetched = HTTParty.get(url, { headers: })
87
+ status = run_fetched["status"]
88
+
89
+ case status
90
+ when "queued", "in_progress", "cancelling" then sleep 1 # Wait one second and poll again
91
+ when "completed", "requires_action", "cancelled", "failed", "expired" then break
92
+ end
93
+ end
94
+
95
+ run_fetched
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+
5
+ module Utils
6
+ module Postgres
7
+ ##
8
+ # This module is a PostgresDB utility to make requests to a Postgres database.
9
+ #
10
+ module Request
11
+ # Implements the request process logic to the PostgresDB table.
12
+ #
13
+ # <br>
14
+ # <b>Params:</b>
15
+ # * <tt>connection</tt> Connection parameters to the database: `host`, `port`, `dbname`, `user`, `password`.
16
+ # * <b>query</b>:
17
+ # * <tt>String</tt>: String with the SQL query to be executed.
18
+ # * <tt>Array</tt>: Two element array, where the first element is the SQL query (string), and the
19
+ # second one an array of elements to be interpolared in the query when using "$1, $2, ...".
20
+ #
21
+ # <br>
22
+ # <b>returns</b> <tt>HTTParty::Response</tt>
23
+ #
24
+ def self.execute(params)
25
+ pg_connection = PG::Connection.new(params[:connection])
26
+
27
+ execute_query(pg_connection, params[:query])
28
+ end
29
+
30
+ # Execute the Postgres query
31
+ #
32
+ # <br>
33
+ # <b>pg_connection</b>: PG::Connection object configured with the database connection.
34
+ # <b>query</b>:
35
+ # * <tt>String</tt>: String with the SQL query to be executed.
36
+ # * <tt>Array</tt>: Two element array, where the first element is the SQL query (string), and the
37
+ # second one an array of elements to be interpolared in the query when using "$1, $2, ...".
38
+ #
39
+ def self.execute_query(pg_connection, query)
40
+ if query.is_a? String
41
+ pg_connection.exec(query)
42
+ else
43
+ sentence, params = query
44
+
45
+ pg_connection.exec_params(sentence, params)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
data/lib/bas/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bas
4
4
  # Gem version
5
- VERSION = "0.4.0"
5
+ VERSION = "1.0.0"
6
6
  end
@@ -1,36 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../domain/exceptions/function_not_implemented"
3
+ require_relative "../utils/exceptions/function_not_implemented"
4
4
 
5
5
  module Write
6
6
  ##
7
- # The Write::Base class serves as the foundation for implementing specific data write within the Write module.
8
- # Operating as an interface, this class defines essential attributes and methods, providing a blueprint for creating
9
- # a custom write.
7
+ # The Write::Base class serves as the foundation for implementing specific data write components within
8
+ # the Write module. Operating as an interface, this class defines essential attributes and methods,
9
+ # providing a blueprint for creating custom write components tailored to different data storages.
10
10
  #
11
11
  class Base
12
- attr_reader :config
12
+ attr_reader :config, :process_response
13
13
 
14
14
  # Initializes the write with essential configuration parameters.
15
15
  #
16
- def initialize(config = {})
16
+ def initialize(config, process_response = nil)
17
17
  @config = config
18
+ @process_response = process_response
18
19
  end
19
20
 
20
- # A method meant to execute the write request to an specific destination depending on the implementation.
21
- # Must be overridden by subclasses, with specific logic based on the use case.
21
+ # A method meant to execute the write request to an specific <b>common storage</b>.
22
+ # Must be overridden by subclasses, with specific logic based on the storage destination.
22
23
  #
23
24
  # <br>
24
- # <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
25
+ # <b>raises</b> <tt>Utils::Exceptions::FunctionNotImplemented</tt> when missing implementation.
25
26
  #
26
- def execute(_process_response)
27
- raise Domain::Exceptions::FunctionNotImplemented
28
- end
29
-
30
- protected
31
-
32
- def write(_method, _data)
33
- raise Domain::Exceptions::FunctionNotImplemented
27
+ def execute
28
+ raise Utils::Exceptions::FunctionNotImplemented
34
29
  end
35
30
  end
36
31
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../version"
5
+ require_relative "../utils/postgres/request"
6
+
7
+ module Write
8
+ ##
9
+ # This class is an implementation of the Write::Base interface, specifically designed
10
+ # to wtite to a PostgresDB used as <b>common storage</b>.
11
+ #
12
+ class Postgres < Write::Base
13
+ PTO_PARAMS = "data, tag, archived, stage, status, error_message, version"
14
+
15
+ # Execute the Postgres utility to write data in the <b>common storage</b>
16
+ #
17
+ def execute
18
+ Utils::Postgres::Request.execute(params)
19
+ end
20
+
21
+ private
22
+
23
+ def params
24
+ {
25
+ connection: config[:connection],
26
+ query: build_query
27
+ }
28
+ end
29
+
30
+ def build_query
31
+ query = "INSERT INTO #{config[:db_table]} (#{PTO_PARAMS}) VALUES ($1, $2, $3, $4, $5, $6, $7);"
32
+ params = build_params
33
+
34
+ [query, params]
35
+ end
36
+
37
+ def build_params
38
+ if process_response[:success]
39
+ [process_response[:success].to_json, config[:tag], false, "unprocessed", "success", nil, Bas::VERSION]
40
+ else
41
+ [nil, config[:tag], false, "unprocessed", "failed", process_response[:error].to_json, Bas::VERSION]
42
+ end
43
+ end
44
+ end
45
+ end