bas 0.3.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +15 -0
- data/CONTRIBUTING.md +9 -0
- data/README.md +68 -147
- data/lib/bas/bot/base.rb +74 -0
- data/lib/bas/bot/compare_wip_limit_count.rb +92 -0
- data/lib/bas/bot/fetch_birthdays_from_notion.rb +128 -0
- data/lib/bas/bot/fetch_domains_wip_counts_from_notion.rb +121 -0
- data/lib/bas/bot/fetch_domains_wip_limit_from_notion.rb +134 -0
- data/lib/bas/bot/fetch_emails_from_imap.rb +99 -0
- data/lib/bas/bot/fetch_next_week_birthdays_from_notion.rb +142 -0
- data/lib/bas/bot/fetch_next_week_ptos_from_notion.rb +162 -0
- data/lib/bas/bot/fetch_ptos_from_notion.rb +138 -0
- data/lib/bas/bot/format_birthdays.rb +97 -0
- data/lib/bas/bot/format_emails.rb +124 -0
- data/lib/bas/bot/format_wip_limit_exceeded.rb +97 -0
- data/lib/bas/bot/garbage_collector.rb +85 -0
- data/lib/bas/bot/humanize_pto.rb +119 -0
- data/lib/bas/bot/notify_discord.rb +96 -0
- data/lib/bas/read/base.rb +10 -23
- data/lib/bas/read/default.rb +16 -0
- data/lib/bas/read/postgres.rb +44 -0
- data/lib/bas/read/types/response.rb +18 -0
- data/lib/bas/utils/discord/integration.rb +43 -0
- data/lib/bas/utils/exceptions/function_not_implemented.rb +16 -0
- data/lib/bas/utils/exceptions/invalid_process_response.rb +16 -0
- data/lib/bas/utils/imap/request.rb +76 -0
- data/lib/bas/utils/notion/request.rb +45 -0
- data/lib/bas/utils/openai/run_assistant.rb +99 -0
- data/lib/bas/utils/postgres/request.rb +50 -0
- data/lib/bas/version.rb +1 -1
- data/lib/bas/write/base.rb +12 -17
- data/lib/bas/write/postgres.rb +45 -0
- data/lib/bas/write/postgres_update.rb +49 -0
- data/lib/bas.rb +1 -3
- metadata +31 -57
- data/lib/bas/domain/birthday.rb +0 -25
- data/lib/bas/domain/email.rb +0 -34
- data/lib/bas/domain/exceptions/function_not_implemented.rb +0 -18
- data/lib/bas/domain/issue.rb +0 -22
- data/lib/bas/domain/pto.rb +0 -69
- data/lib/bas/domain/work_items_limit.rb +0 -25
- data/lib/bas/formatter/base.rb +0 -53
- data/lib/bas/formatter/birthday.rb +0 -38
- data/lib/bas/formatter/exceptions/invalid_data.rb +0 -15
- data/lib/bas/formatter/pto.rb +0 -89
- data/lib/bas/formatter/support_emails.rb +0 -73
- data/lib/bas/formatter/types/response.rb +0 -16
- data/lib/bas/formatter/work_items_limit.rb +0 -68
- data/lib/bas/process/base.rb +0 -39
- data/lib/bas/process/discord/exceptions/invalid_webhook_token.rb +0 -16
- data/lib/bas/process/discord/implementation.rb +0 -71
- data/lib/bas/process/discord/types/response.rb +0 -22
- data/lib/bas/process/slack/exceptions/invalid_webhook_token.rb +0 -16
- data/lib/bas/process/slack/implementation.rb +0 -70
- data/lib/bas/process/slack/types/response.rb +0 -21
- data/lib/bas/process/types/response.rb +0 -16
- data/lib/bas/read/github/base.rb +0 -57
- data/lib/bas/read/github/types/response.rb +0 -27
- data/lib/bas/read/github/use_case/repo_issues.rb +0 -17
- data/lib/bas/read/imap/base.rb +0 -70
- data/lib/bas/read/imap/types/response.rb +0 -27
- data/lib/bas/read/imap/use_case/support_emails.rb +0 -26
- data/lib/bas/read/notion/base.rb +0 -52
- data/lib/bas/read/notion/exceptions/invalid_api_key.rb +0 -15
- data/lib/bas/read/notion/exceptions/invalid_database_id.rb +0 -15
- data/lib/bas/read/notion/helper.rb +0 -21
- data/lib/bas/read/notion/types/response.rb +0 -26
- data/lib/bas/read/notion/use_case/birthday_next_week.rb +0 -41
- data/lib/bas/read/notion/use_case/birthday_today.rb +0 -29
- data/lib/bas/read/notion/use_case/pto_next_week.rb +0 -71
- data/lib/bas/read/notion/use_case/pto_today.rb +0 -30
- data/lib/bas/read/notion/use_case/work_items_limit.rb +0 -37
- data/lib/bas/read/postgres/base.rb +0 -46
- data/lib/bas/read/postgres/helper.rb +0 -16
- data/lib/bas/read/postgres/types/response.rb +0 -42
- data/lib/bas/read/postgres/use_case/pto_today.rb +0 -32
- data/lib/bas/serialize/base.rb +0 -30
- data/lib/bas/serialize/github/issues.rb +0 -57
- data/lib/bas/serialize/imap/support_emails.rb +0 -56
- data/lib/bas/serialize/notion/birthday_today.rb +0 -68
- data/lib/bas/serialize/notion/pto_today.rb +0 -75
- data/lib/bas/serialize/notion/work_items_limit.rb +0 -65
- data/lib/bas/serialize/postgres/pto_today.rb +0 -47
- data/lib/bas/use_cases/types/config.rb +0 -20
- data/lib/bas/use_cases/use_case.rb +0 -42
- data/lib/bas/use_cases/use_cases.rb +0 -389
- data/lib/bas/write/logs/base.rb +0 -33
- data/lib/bas/write/logs/use_case/console_log.rb +0 -22
@@ -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 "../
|
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
|
8
|
-
# Operating as an interface, this class defines essential attributes and methods,
|
9
|
-
# custom
|
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
|
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
|
21
|
-
# Must be overridden by subclasses, with specific logic based on the
|
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>
|
24
|
+
# <b>raises</b> <tt>Utils::Exceptions::FunctionNotImplemented</tt> when missing implementation.
|
25
25
|
#
|
26
26
|
def execute
|
27
|
-
raise
|
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
data/lib/bas/write/base.rb
CHANGED
@@ -1,36 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../
|
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
|
8
|
-
# Operating as an interface, this class defines essential attributes and methods,
|
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
|
21
|
-
# Must be overridden by subclasses, with specific logic based on the
|
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>
|
25
|
+
# <b>raises</b> <tt>Utils::Exceptions::FunctionNotImplemented</tt> when missing implementation.
|
25
26
|
#
|
26
|
-
def execute
|
27
|
-
raise
|
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
|