bas 0.4.0 → 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 (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