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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -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 +30 -67
- 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/notification.rb +0 -23
- 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/notification.rb +0 -34
- 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/openai/base.rb +0 -72
- data/lib/bas/process/openai/helper.rb +0 -19
- data/lib/bas/process/openai/types/response.rb +0 -27
- data/lib/bas/process/openai/use_case/humanize_pto.rb +0 -53
- 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/notification.rb +0 -28
- 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/notification.rb +0 -56
- 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 -465
- data/lib/bas/write/logs/base.rb +0 -33
- data/lib/bas/write/logs/use_case/console_log.rb +0 -22
- data/lib/bas/write/notion/base.rb +0 -36
- data/lib/bas/write/notion/use_case/empty_notification.rb +0 -38
- 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 "../
|
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
|