bas 0.1.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 +7 -0
- data/._.rspec_status +0 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/CHANGELOG.md +4 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +66 -0
- data/Gemfile +24 -0
- data/LICENSE +21 -0
- data/README.md +362 -0
- data/Rakefile +12 -0
- data/SECURITY.md +13 -0
- data/lib/bas/dispatcher/base.rb +31 -0
- data/lib/bas/dispatcher/discord/exceptions/invalid_webhook_token.rb +16 -0
- data/lib/bas/dispatcher/discord/implementation.rb +51 -0
- data/lib/bas/dispatcher/discord/types/response.rb +22 -0
- data/lib/bas/dispatcher/slack/exceptions/invalid_webhook_token.rb +16 -0
- data/lib/bas/dispatcher/slack/implementation.rb +51 -0
- data/lib/bas/dispatcher/slack/types/response.rb +21 -0
- data/lib/bas/domain/birthday.rb +25 -0
- data/lib/bas/domain/email.rb +34 -0
- data/lib/bas/domain/exceptions/function_not_implemented.rb +18 -0
- data/lib/bas/domain/pto.rb +28 -0
- data/lib/bas/domain/work_items_limit.rb +25 -0
- data/lib/bas/fetcher/base.rb +43 -0
- data/lib/bas/fetcher/imap/base.rb +70 -0
- data/lib/bas/fetcher/imap/types/response.rb +27 -0
- data/lib/bas/fetcher/imap/use_case/support_emails.rb +26 -0
- data/lib/bas/fetcher/notion/base.rb +52 -0
- data/lib/bas/fetcher/notion/exceptions/invalid_api_key.rb +15 -0
- data/lib/bas/fetcher/notion/exceptions/invalid_database_id.rb +15 -0
- data/lib/bas/fetcher/notion/helper.rb +21 -0
- data/lib/bas/fetcher/notion/types/response.rb +26 -0
- data/lib/bas/fetcher/notion/use_case/birthday_next_week.rb +41 -0
- data/lib/bas/fetcher/notion/use_case/birthday_today.rb +29 -0
- data/lib/bas/fetcher/notion/use_case/pto_next_week.rb +71 -0
- data/lib/bas/fetcher/notion/use_case/pto_today.rb +30 -0
- data/lib/bas/fetcher/notion/use_case/work_items_limit.rb +37 -0
- data/lib/bas/fetcher/postgres/base.rb +46 -0
- data/lib/bas/fetcher/postgres/helper.rb +16 -0
- data/lib/bas/fetcher/postgres/types/response.rb +42 -0
- data/lib/bas/fetcher/postgres/use_case/pto_today.rb +32 -0
- data/lib/bas/formatter/base.rb +53 -0
- data/lib/bas/formatter/birthday.rb +34 -0
- data/lib/bas/formatter/exceptions/invalid_data.rb +15 -0
- data/lib/bas/formatter/pto.rb +88 -0
- data/lib/bas/formatter/support_emails.rb +69 -0
- data/lib/bas/formatter/work_items_limit.rb +64 -0
- data/lib/bas/mapper/base.rb +30 -0
- data/lib/bas/mapper/imap/support_emails.rb +56 -0
- data/lib/bas/mapper/notion/birthday_today.rb +68 -0
- data/lib/bas/mapper/notion/pto_today.rb +70 -0
- data/lib/bas/mapper/notion/work_items_limit.rb +65 -0
- data/lib/bas/mapper/postgres/pto_today.rb +47 -0
- data/lib/bas/use_cases/types/config.rb +19 -0
- data/lib/bas/use_cases/use_case.rb +39 -0
- data/lib/bas/use_cases/use_cases.rb +377 -0
- data/lib/bas/version.rb +6 -0
- data/lib/bas.rb +9 -0
- data/renovate.json +6 -0
- data/sig/business_automation_system.rbs +4 -0
- metadata +107 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../domain/exceptions/function_not_implemented"
|
4
|
+
|
5
|
+
module Dispatcher
|
6
|
+
##
|
7
|
+
# Serves as a foundational structure for implementing specific dispatchers. Acting as an interface,
|
8
|
+
# this class defines essential attributes and methods, providing a blueprint for creating custom
|
9
|
+
# dispatchers tailored to different platforms or services.
|
10
|
+
#
|
11
|
+
class Base
|
12
|
+
attr_reader :webhook, :name
|
13
|
+
|
14
|
+
# Initializes the dispatcher with essential configuration parameters.
|
15
|
+
#
|
16
|
+
def initialize(config)
|
17
|
+
@webhook = config[:webhook]
|
18
|
+
@name = config[:name]
|
19
|
+
end
|
20
|
+
|
21
|
+
# A method meant to send messages to an specific destination depending on the implementation.
|
22
|
+
# Must be overridden by subclasses, with specific logic based on the use case.
|
23
|
+
#
|
24
|
+
# <br>
|
25
|
+
# <b>returns</b> a <tt>Discord::Response</tt>
|
26
|
+
#
|
27
|
+
def dispatch(_payload)
|
28
|
+
raise Domain::Exceptions::FunctionNotImplemented
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dispatcher
|
4
|
+
module Discord
|
5
|
+
module Exceptions
|
6
|
+
##
|
7
|
+
# Domain specific representation when an invalid Discord webhook token is provided to Discord.
|
8
|
+
#
|
9
|
+
class InvalidWebookToken < StandardError
|
10
|
+
def initialize(message = "The provided Webhook token is invalid.")
|
11
|
+
super(message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../base"
|
4
|
+
require_relative "./exceptions/invalid_webhook_token"
|
5
|
+
require_relative "./types/response"
|
6
|
+
|
7
|
+
module Dispatcher
|
8
|
+
module Discord
|
9
|
+
##
|
10
|
+
# This class is an implementation of the Dispatcher::Base interface, specifically designed
|
11
|
+
# for dispatching messages to Discord.
|
12
|
+
#
|
13
|
+
class Implementation < Base
|
14
|
+
# Implements the dispatching logic for the Discord use case. It sends a POST request to
|
15
|
+
# the Discord webhook with the specified payload.
|
16
|
+
#
|
17
|
+
# <br>
|
18
|
+
# <b>Params:</b>
|
19
|
+
# * <tt>String</tt> payload: Payload to be dispatched to discord.
|
20
|
+
# <br>
|
21
|
+
# <b>raises</b> <tt>Exceptions::Discord::InvalidWebookToken</tt> if the provided webhook token is invalid.
|
22
|
+
#
|
23
|
+
# <br>
|
24
|
+
# <b>returns</b> <tt>Dispatcher::Discord::Types::Response</tt>
|
25
|
+
#
|
26
|
+
def dispatch(payload)
|
27
|
+
body = {
|
28
|
+
username: name,
|
29
|
+
avatar_url: "",
|
30
|
+
content: payload
|
31
|
+
}.to_json
|
32
|
+
response = HTTParty.post(webhook, { body: body, headers: { "Content-Type" => "application/json" } })
|
33
|
+
|
34
|
+
discord_response = Dispatcher::Discord::Types::Response.new(response)
|
35
|
+
|
36
|
+
validate_response(discord_response)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def validate_response(response)
|
42
|
+
case response.code
|
43
|
+
when 50_027
|
44
|
+
raise Discord::Exceptions::InvalidWebookToken, response.message
|
45
|
+
else
|
46
|
+
response
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dispatcher
|
4
|
+
module Discord
|
5
|
+
module Types
|
6
|
+
##
|
7
|
+
# Represents a response received from Discord. It encapsulates essential information about the response,
|
8
|
+
# providing a structured way to handle and analyze Discord server responses.
|
9
|
+
#
|
10
|
+
class Response
|
11
|
+
attr_reader :code, :http_code, :message, :response
|
12
|
+
|
13
|
+
def initialize(response)
|
14
|
+
@http_code = response.code
|
15
|
+
@code = response["code"]
|
16
|
+
@message = response.message
|
17
|
+
@response = response.response
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dispatcher
|
4
|
+
module Slack
|
5
|
+
module Exceptions
|
6
|
+
##
|
7
|
+
# Domain specific representation when an invalid webhook token is provided to Slack.
|
8
|
+
#
|
9
|
+
class InvalidWebookToken < StandardError
|
10
|
+
def initialize(message = "The provided Webhook token is invalid.")
|
11
|
+
super(message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../base"
|
4
|
+
require_relative "./exceptions/invalid_webhook_token"
|
5
|
+
require_relative "./types/response"
|
6
|
+
|
7
|
+
module Dispatcher
|
8
|
+
module Slack
|
9
|
+
##
|
10
|
+
# This class is an implementation of the Dispatcher::Base interface, specifically designed
|
11
|
+
# for dispatching messages to Slack.
|
12
|
+
#
|
13
|
+
class Implementation < Base
|
14
|
+
# Implements the dispatching logic for the Slack use case. It sends a POST request to
|
15
|
+
# the Slack webhook with the specified payload.
|
16
|
+
#
|
17
|
+
# <br>
|
18
|
+
# <b>Params:</b>
|
19
|
+
# * <tt>String</tt> payload: Payload to be dispatched to slack.
|
20
|
+
# <br>
|
21
|
+
# <b>raises</b> <tt>Exceptions::Slack::InvalidWebookToken</tt> if the provided webhook token is invalid.
|
22
|
+
#
|
23
|
+
# <br>
|
24
|
+
# <b>returns</b> <tt>Dispatcher::Slack::Types::Response</tt>
|
25
|
+
#
|
26
|
+
def dispatch(payload)
|
27
|
+
body = {
|
28
|
+
username: name,
|
29
|
+
text: payload
|
30
|
+
}.to_json
|
31
|
+
|
32
|
+
response = HTTParty.post(webhook, { body: body, headers: { "Content-Type" => "application/json" } })
|
33
|
+
|
34
|
+
slack_response = Dispatcher::Discord::Types::Response.new(response)
|
35
|
+
|
36
|
+
validate_response(slack_response)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def validate_response(response)
|
42
|
+
case response.http_code
|
43
|
+
when 403
|
44
|
+
raise Dispatcher::Slack::Exceptions::InvalidWebookToken, response.message
|
45
|
+
else
|
46
|
+
response
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dispatcher
|
4
|
+
module Slack
|
5
|
+
module Types
|
6
|
+
##
|
7
|
+
# Represents a response received from Slack. It encapsulates essential information about the response,
|
8
|
+
# providing a structured way to handle and analyze Slack server responses.
|
9
|
+
#
|
10
|
+
class Response
|
11
|
+
attr_reader :body, :http_code, :message
|
12
|
+
|
13
|
+
def initialize(response)
|
14
|
+
@http_code = response.code
|
15
|
+
@message = response.message
|
16
|
+
@body = response.body
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domain
|
4
|
+
##
|
5
|
+
# The Domain::Birthday class provides a domain-specific representation of a Birthday object.
|
6
|
+
# It encapsulates the individual's name and their birthdate, offering a structured way to
|
7
|
+
# handle and manipulate birthday information.
|
8
|
+
class Birthday
|
9
|
+
attr_reader :individual_name, :birth_date
|
10
|
+
|
11
|
+
ATTRIBUTES = %w[individual_name birth_date].freeze
|
12
|
+
|
13
|
+
# Initializes a Domain::Birthday instance with the specified individual name, and date of birth.
|
14
|
+
#
|
15
|
+
# <br>
|
16
|
+
# <b>Params:</b>
|
17
|
+
# * <tt>String</tt> individual_name Name of the individual
|
18
|
+
# * <tt>Date</tt> birth_date Birthdate from the individual
|
19
|
+
#
|
20
|
+
def initialize(individual_name, date)
|
21
|
+
@individual_name = individual_name
|
22
|
+
@birth_date = date
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domain
|
4
|
+
##
|
5
|
+
# The Domain::Email class provides a domain-specific representation of an Email object.
|
6
|
+
# It encapsulates information about an email, including the subject, the sender, and the date.
|
7
|
+
#
|
8
|
+
class Email
|
9
|
+
attr_reader :subject, :sender
|
10
|
+
attr_accessor :date
|
11
|
+
|
12
|
+
ATTRIBUTES = %w[subject sender date].freeze
|
13
|
+
|
14
|
+
# Initializes a Domain::Email instance with the specified subject, sender, and date.
|
15
|
+
#
|
16
|
+
# <br>
|
17
|
+
# <b>Params:</b>
|
18
|
+
# * <tt>String</tt> email subject.
|
19
|
+
# * <tt>String</tt> Email of the sender.
|
20
|
+
# * <tt>String</tt> Reception date
|
21
|
+
#
|
22
|
+
def initialize(subject, sender, date)
|
23
|
+
@subject = subject
|
24
|
+
@sender = sender
|
25
|
+
@date = parse_to_datetime(date)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse_to_datetime(date)
|
31
|
+
DateTime.parse(date).to_time
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domain
|
4
|
+
module Exceptions
|
5
|
+
##
|
6
|
+
# Provides a domain-specific 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 when a required function
|
8
|
+
# remains unimplemented in a subclass.
|
9
|
+
#
|
10
|
+
class FunctionNotImplemented < StandardError
|
11
|
+
# Initializes the exception with an optional custom error message.
|
12
|
+
#
|
13
|
+
def initialize(message = "The function haven't been implemented yet.")
|
14
|
+
super(message)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domain
|
4
|
+
##
|
5
|
+
# The Domain::Pto class provides a domain-specific representation of a Paid Time Off (PTO) object.
|
6
|
+
# It encapsulates information about an individual's time off, including the individual's name,
|
7
|
+
# the start date, and the end date of the time off period.
|
8
|
+
#
|
9
|
+
class Pto
|
10
|
+
attr_reader :individual_name, :start_date, :end_date
|
11
|
+
|
12
|
+
ATTRIBUTES = %w[individual_name start_date end_date].freeze
|
13
|
+
|
14
|
+
# Initializes a Domain::Pto instance with the specified individual name, start date, and end date.
|
15
|
+
#
|
16
|
+
# <br>
|
17
|
+
# <b>Params:</b>
|
18
|
+
# * <tt>String</tt> individual_name Name of the individual.
|
19
|
+
# * <tt>DateTime</tt> start_date Start day of the PTO.
|
20
|
+
# * <tt>String</tt> end_date End date of the PTO.
|
21
|
+
#
|
22
|
+
def initialize(individual_name, start_date, end_date)
|
23
|
+
@individual_name = individual_name
|
24
|
+
@start_date = start_date
|
25
|
+
@end_date = end_date
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domain
|
4
|
+
##
|
5
|
+
# The Domain::WorkItemsLimit class provides a domain-specific representation of a Work Item object.
|
6
|
+
# It encapsulates information about a work items limit, including the domain and total.
|
7
|
+
#
|
8
|
+
class WorkItemsLimit
|
9
|
+
attr_reader :domain, :total
|
10
|
+
|
11
|
+
ATTRIBUTES = %w[domain total].freeze
|
12
|
+
|
13
|
+
# Initializes a Domain::WorkItemsLimit instance with the specified domain and total.
|
14
|
+
#
|
15
|
+
# <br>
|
16
|
+
# <b>Params:</b>
|
17
|
+
# * <tt>String</tt> 'domain' responsible domain of the work items.
|
18
|
+
# * <tt>String</tt> 'total' total 'in progress' work items.
|
19
|
+
#
|
20
|
+
def initialize(domain, total)
|
21
|
+
@domain = domain
|
22
|
+
@total = total
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../domain/exceptions/function_not_implemented"
|
4
|
+
|
5
|
+
module Fetcher
|
6
|
+
##
|
7
|
+
# The Fetcher::Base class serves as the foundation for implementing specific data fetchers within the Fetcher module.
|
8
|
+
# Operating as an interface, this class defines essential attributes and methods, providing a blueprint for creating
|
9
|
+
# custom fetchers tailored to different data sources.
|
10
|
+
#
|
11
|
+
class Base
|
12
|
+
attr_reader :config
|
13
|
+
|
14
|
+
# Initializes the fetcher with essential configuration parameters.
|
15
|
+
#
|
16
|
+
def initialize(config)
|
17
|
+
@config = config
|
18
|
+
end
|
19
|
+
|
20
|
+
# A method meant to fetch data from an specific source depending on the implementation.
|
21
|
+
# Must be overridden by subclasses, with specific logic based on the use case.
|
22
|
+
#
|
23
|
+
# <br>
|
24
|
+
# <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
|
25
|
+
#
|
26
|
+
def fetch
|
27
|
+
raise Domain::Exceptions::FunctionNotImplemented
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# A method meant to execute the fetch request, 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 execute
|
40
|
+
raise Domain::Exceptions::FunctionNotImplemented
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/imap"
|
4
|
+
require "gmail_xoauth"
|
5
|
+
|
6
|
+
require_relative "../base"
|
7
|
+
require_relative "./types/response"
|
8
|
+
|
9
|
+
module Fetcher
|
10
|
+
module Imap
|
11
|
+
##
|
12
|
+
# This class is an implementation of the Fetcher::Base interface, specifically designed
|
13
|
+
# for fetching data from an IMAP server.
|
14
|
+
#
|
15
|
+
class Base < Fetcher::Base
|
16
|
+
protected
|
17
|
+
|
18
|
+
# Implements the data fetching logic for emails data from an IMAP server.
|
19
|
+
# It connects to an IMAP server inbox, request emails base on a filter,
|
20
|
+
# and returns a validated response.
|
21
|
+
#
|
22
|
+
def execute(email_domain, email_port, token_uri, query)
|
23
|
+
access_token = refresh_token(token_uri)
|
24
|
+
|
25
|
+
imap_fetch(email_domain, email_port, query, access_token)
|
26
|
+
|
27
|
+
Fetcher::Imap::Types::Response.new(@emails)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def imap_fetch(email_domain, email_port, query, access_token)
|
33
|
+
imap = Net::IMAP.new(email_domain, port: email_port, ssl: true)
|
34
|
+
|
35
|
+
imap.authenticate("XOAUTH2", config[:user], access_token)
|
36
|
+
|
37
|
+
imap.examine(config[:inbox])
|
38
|
+
|
39
|
+
@emails = fetch_emails(imap, query)
|
40
|
+
|
41
|
+
imap.logout
|
42
|
+
imap.disconnect
|
43
|
+
end
|
44
|
+
|
45
|
+
def fetch_emails(imap, query)
|
46
|
+
imap.search(query).map do |message_id|
|
47
|
+
imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def refresh_token(token_uri)
|
52
|
+
uri = URI.parse(token_uri)
|
53
|
+
|
54
|
+
response = Net::HTTP.post_form(uri, params)
|
55
|
+
token_data = JSON.parse(response.body)
|
56
|
+
|
57
|
+
token_data["access_token"]
|
58
|
+
end
|
59
|
+
|
60
|
+
def params
|
61
|
+
{
|
62
|
+
"grant_type" => "refresh_token",
|
63
|
+
"refresh_token" => config[:refresh_token],
|
64
|
+
"client_id" => config[:client_id],
|
65
|
+
"client_secret" => config[:client_secret]
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fetcher
|
4
|
+
module Imap
|
5
|
+
module Types
|
6
|
+
##
|
7
|
+
# Represents a response received from the Imap client. It encapsulates essential
|
8
|
+
# information about the response, providing a structured way to handle and analyze
|
9
|
+
# it's responses.
|
10
|
+
class Response
|
11
|
+
attr_reader :status_code, :message, :results
|
12
|
+
|
13
|
+
def initialize(response)
|
14
|
+
if response.empty?
|
15
|
+
@status_code = 404
|
16
|
+
@message = "no result were found"
|
17
|
+
@results = []
|
18
|
+
else
|
19
|
+
@status_code = 200
|
20
|
+
@message = "success"
|
21
|
+
@results = response
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../base"
|
4
|
+
|
5
|
+
module Fetcher
|
6
|
+
module Imap
|
7
|
+
##
|
8
|
+
# This class is an implementation of the Fetcher::Imap::Base interface, specifically designed
|
9
|
+
# for fetching support email from a Google Gmail account.
|
10
|
+
#
|
11
|
+
class SupportEmails < Imap::Base
|
12
|
+
TOKEN_URI = "https://oauth2.googleapis.com/token"
|
13
|
+
EMAIL_DOMAIN = "imap.gmail.com"
|
14
|
+
EMAIL_PORT = 993
|
15
|
+
|
16
|
+
# Implements the data fetching filter for support emails from Google Gmail.
|
17
|
+
#
|
18
|
+
def fetch
|
19
|
+
yesterday = (Time.now - (60 * 60 * 24)).strftime("%e-%b-%Y")
|
20
|
+
query = ["TO", config[:search_email], "SINCE", yesterday]
|
21
|
+
|
22
|
+
execute(EMAIL_DOMAIN, EMAIL_PORT, TOKEN_URI, query)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "httparty"
|
4
|
+
|
5
|
+
require_relative "../base"
|
6
|
+
require_relative "./exceptions/invalid_api_key"
|
7
|
+
require_relative "./exceptions/invalid_database_id"
|
8
|
+
require_relative "./types/response"
|
9
|
+
require_relative "./helper"
|
10
|
+
|
11
|
+
module Fetcher
|
12
|
+
module Notion
|
13
|
+
##
|
14
|
+
# This class is an implementation of the Fetcher::Base interface, specifically designed
|
15
|
+
# for fetching data from Notion.
|
16
|
+
#
|
17
|
+
class Base < Fetcher::Base
|
18
|
+
NOTION_BASE_URL = "https://api.notion.com"
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
# Implements the data fetching logic for data from Notion. It sends a POST
|
23
|
+
# request to the Notion API to query the specified database and returns a validated response.
|
24
|
+
#
|
25
|
+
# <br>
|
26
|
+
# <b>raises</b> <tt>Exceptions::Notion::InvalidApiKey</tt> if the API key provided is incorrect or invalid.
|
27
|
+
#
|
28
|
+
# <b>raises</b> <tt>Exceptions::Notion::InvalidDatabaseId</tt> if the Database id provided is incorrect
|
29
|
+
# or invalid.
|
30
|
+
#
|
31
|
+
def execute(filter)
|
32
|
+
url = "#{NOTION_BASE_URL}/v1/databases/#{config[:database_id]}/query"
|
33
|
+
|
34
|
+
httparty_response = HTTParty.post(url, { body: filter.to_json, headers: headers })
|
35
|
+
|
36
|
+
notion_response = Fetcher::Notion::Types::Response.new(httparty_response)
|
37
|
+
|
38
|
+
Fetcher::Notion::Helper.validate_response(notion_response)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def headers
|
44
|
+
{
|
45
|
+
"Authorization" => "Bearer #{config[:secret]}",
|
46
|
+
"Content-Type" => "application/json",
|
47
|
+
"Notion-Version" => "2022-06-28"
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Exceptions
|
4
|
+
module Notion
|
5
|
+
##
|
6
|
+
# Provides a domain-specific representation for errors that occurs when an invalid API key is provided
|
7
|
+
# for a Notion-related operation.
|
8
|
+
#
|
9
|
+
class InvalidApiKey < StandardError
|
10
|
+
def initialize(message = "The provided API token is invalid.")
|
11
|
+
super(message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Exceptions
|
4
|
+
module Notion
|
5
|
+
##
|
6
|
+
# Provides a domain-specific representation for errors that occurs when an invalid database id is provided
|
7
|
+
# for a Notion-related operation.
|
8
|
+
#
|
9
|
+
class InvalidDatabaseId < StandardError
|
10
|
+
def initialize(message = "The provided id doesn't match any database.")
|
11
|
+
super(message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fetcher
|
4
|
+
module Notion
|
5
|
+
##
|
6
|
+
# Provides common fuctionalities along the Notion domain.
|
7
|
+
#
|
8
|
+
module Helper
|
9
|
+
def self.validate_response(response)
|
10
|
+
case response.status_code
|
11
|
+
when 401
|
12
|
+
raise Exceptions::Notion::InvalidApiKey, response.message
|
13
|
+
when 404
|
14
|
+
raise Exceptions::Notion::InvalidDatabaseId, response.message
|
15
|
+
else
|
16
|
+
response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fetcher
|
4
|
+
module Notion
|
5
|
+
module Types
|
6
|
+
##
|
7
|
+
# Represents a response received from the Notion API. It encapsulates essential information about the response,
|
8
|
+
# providing a structured way to handle and analyze it's responses.
|
9
|
+
class Response
|
10
|
+
attr_reader :status_code, :message, :results
|
11
|
+
|
12
|
+
def initialize(response)
|
13
|
+
if response["results"].nil?
|
14
|
+
@status_code = response["status"]
|
15
|
+
@message = response["message"]
|
16
|
+
@results = []
|
17
|
+
else
|
18
|
+
@status_code = 200
|
19
|
+
@message = "success"
|
20
|
+
@results = response["results"]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|