bas 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +6 -0
  4. data/README.md +27 -24
  5. data/lib/bas/domain/issue.rb +22 -0
  6. data/lib/bas/domain/pto.rb +45 -4
  7. data/lib/bas/formatter/base.rb +2 -2
  8. data/lib/bas/formatter/birthday.rb +8 -4
  9. data/lib/bas/formatter/pto.rb +27 -26
  10. data/lib/bas/formatter/support_emails.rb +7 -3
  11. data/lib/bas/formatter/types/response.rb +16 -0
  12. data/lib/bas/formatter/work_items_limit.rb +8 -4
  13. data/lib/bas/process/base.rb +39 -0
  14. data/lib/bas/{dispatcher → process}/discord/exceptions/invalid_webhook_token.rb +1 -1
  15. data/lib/bas/process/discord/implementation.rb +71 -0
  16. data/lib/bas/{dispatcher → process}/discord/types/response.rb +1 -1
  17. data/lib/bas/{dispatcher → process}/slack/exceptions/invalid_webhook_token.rb +1 -1
  18. data/lib/bas/process/slack/implementation.rb +70 -0
  19. data/lib/bas/{dispatcher → process}/slack/types/response.rb +1 -1
  20. data/lib/bas/process/types/response.rb +16 -0
  21. data/lib/bas/{fetcher → read}/base.rb +8 -8
  22. data/lib/bas/read/github/base.rb +57 -0
  23. data/lib/bas/read/github/types/response.rb +27 -0
  24. data/lib/bas/read/github/use_case/repo_issues.rb +17 -0
  25. data/lib/bas/{fetcher → read}/imap/base.rb +7 -7
  26. data/lib/bas/{fetcher → read}/imap/types/response.rb +1 -1
  27. data/lib/bas/read/imap/use_case/support_emails.rb +26 -0
  28. data/lib/bas/{fetcher → read}/notion/base.rb +8 -8
  29. data/lib/bas/{fetcher → read}/notion/helper.rb +1 -1
  30. data/lib/bas/{fetcher → read}/notion/types/response.rb +1 -1
  31. data/lib/bas/{fetcher → read}/notion/use_case/birthday_next_week.rb +6 -6
  32. data/lib/bas/{fetcher → read}/notion/use_case/birthday_today.rb +6 -6
  33. data/lib/bas/{fetcher → read}/notion/use_case/pto_next_week.rb +6 -6
  34. data/lib/bas/{fetcher → read}/notion/use_case/pto_today.rb +6 -6
  35. data/lib/bas/{fetcher → read}/notion/use_case/work_items_limit.rb +5 -5
  36. data/lib/bas/{fetcher → read}/postgres/base.rb +8 -8
  37. data/lib/bas/{fetcher → read}/postgres/helper.rb +1 -1
  38. data/lib/bas/{fetcher → read}/postgres/types/response.rb +1 -1
  39. data/lib/bas/{fetcher → read}/postgres/use_case/pto_today.rb +6 -6
  40. data/lib/bas/{mapper → serialize}/base.rb +7 -7
  41. data/lib/bas/serialize/github/issues.rb +57 -0
  42. data/lib/bas/{mapper → serialize}/imap/support_emails.rb +7 -7
  43. data/lib/bas/{mapper → serialize}/notion/birthday_today.rb +7 -7
  44. data/lib/bas/serialize/notion/pto_today.rb +75 -0
  45. data/lib/bas/{mapper → serialize}/notion/work_items_limit.rb +7 -7
  46. data/lib/bas/{mapper → serialize}/postgres/pto_today.rb +9 -9
  47. data/lib/bas/use_cases/types/config.rb +6 -5
  48. data/lib/bas/use_cases/use_case.rb +13 -10
  49. data/lib/bas/use_cases/use_cases.rb +71 -59
  50. data/lib/bas/version.rb +1 -1
  51. data/lib/bas/write/base.rb +36 -0
  52. data/lib/bas/write/logs/base.rb +33 -0
  53. data/lib/bas/write/logs/use_case/console_log.rb +22 -0
  54. metadata +43 -33
  55. data/lib/bas/dispatcher/base.rb +0 -31
  56. data/lib/bas/dispatcher/discord/implementation.rb +0 -51
  57. data/lib/bas/dispatcher/slack/implementation.rb +0 -51
  58. data/lib/bas/fetcher/imap/use_case/support_emails.rb +0 -26
  59. data/lib/bas/mapper/notion/pto_today.rb +0 -70
  60. /data/lib/bas/{fetcher → read}/notion/exceptions/invalid_api_key.rb +0 -0
  61. /data/lib/bas/{fetcher → read}/notion/exceptions/invalid_database_id.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d8ff973fc126ff9d0af5adf9200417f302a9a86cc50be3d3ef090a8979a22b1
4
- data.tar.gz: 846dfe3cd6d719aa0a7cc17ce2219a0dee54a5d89043a475ec93b4786fd688d2
3
+ metadata.gz: 362d8c607c7fbfd0405b4e7e349505f45e12b5218ae9c23416954919920bdb63
4
+ data.tar.gz: 78513d4ae926c12a290ceb200c1aa718d15f05c8e4536a8b58deabb3c9ca17b4
5
5
  SHA512:
6
- metadata.gz: 14ea01f630b4a296fa94a352d14bc00111833d2f5b1867861280c5aa32abca445bdc7ec888c83a4a673109aba2c5e3239fdccc48cc7315477a3b579d200b649c
7
- data.tar.gz: 3b73cf8b1dc7d931c3cd428141f2a3806c1b90c23bb1c0ac2dc755bf16278b679f42405f4d504b5cfbd6d1f09787d065f34fe3a06cee15ed9d684a93d2249971
6
+ metadata.gz: 9a27723bb27bc0e9aefee19b4e9a159b2da7a1171da540f910a5a1affaa940ab7861afbdafadd34fa16ed71ce2284a69503b5e43a0ac1f4f399a3cf99e5aa90f
7
+ data.tar.gz: 0da18b42118777de21d8c7a0a91c8ee32f3499d7a0f0bc8d5671442762cbd4a20bcb5a0daa24d9b1bfac63c1a2bb03bcedfd867c18ee52667b3e5634e79be6b6
data/CHANGELOG.md CHANGED
@@ -1,4 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (16.04.2024)
4
+ - [Rename the Fetcher to Read](https://github.com/kommitters/bas/issues/13)
5
+ - [Rename the Mapper to Serialize](https://github.com/kommitters/bas/issues/14)
6
+ - [Rename the Dispatcher to Process](https://github.com/kommitters/bas/issues/15)
7
+ - [Add the Writer Component](https://github.com/kommitters/bas/issues/16)
8
+
9
+ ## 0.2.0 (04.04.2024)
10
+ - [Add a GitHub fetcher component](https://github.com/kommitters/bas/issues/6)
11
+ - [Allow daily hours pto for more than one day pto](https://github.com/kommitters/bas/issues/8)
12
+
3
13
  ## 0.1.0 (26.03.2024)
4
14
  - [Add the BNS gem code](https://github.com/kommitters/bas/issues/1)
data/Gemfile CHANGED
@@ -5,10 +5,16 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in bas.gemspec
6
6
  gemspec
7
7
 
8
+ gem "jwt", "~> 2.8.1"
9
+
8
10
  gem "rake", "~> 13.0"
9
11
 
10
12
  gem "net-imap", "~> 0.4.10"
11
13
  gem "net-smtp", "~> 0.4.0.1"
14
+
15
+ gem "octokit", "~> 8.1.0"
16
+ gem "openssl", "~> 3.2"
17
+
12
18
  gem "rspec", "~> 3.0"
13
19
  gem "rubocop", "~> 1.21"
14
20
  gem "simplecov", require: false, group: :test
data/README.md CHANGED
@@ -6,11 +6,11 @@ The underlying idea is to develop generic components that can serve a wide range
6
6
 
7
7
  ![Gem Version](https://img.shields.io/gem/v/bas?style=for-the-badge)
8
8
  ![Gem Total Downloads](https://img.shields.io/gem/dt/bas?style=for-the-badge)
9
- ![Build Badge](https://img.shields.io/github/actions/workflow/status/kommitters/bas/ci.yml?branch=project-opensource-config&style=for-the-badge)
9
+ ![Build Badge](https://img.shields.io/github/actions/workflow/status/kommitters/bas/ci.yml?style=for-the-badge)
10
10
  [![Coverage Status](https://img.shields.io/coveralls/github/kommitters/bas?style=for-the-badge)](https://coveralls.io/github/kommitters/bas?branch=main)
11
11
  ![GitHub License](https://img.shields.io/github/license/kommitters/bas?style=for-the-badge)
12
12
  [![OpenSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/kommitters/bas?label=openssf%20scorecard&style=for-the-badge)](https://api.securityscorecards.dev/projects/github.com/kommitters/bas)
13
- [![OpenSSF Best Practices](https://img.shields.io/cii/summary/8383?label=openssf%20best%20practices&style=for-the-badge)](https://bestpractices.coreinfrastructure.org/projects/8383)
13
+ [![OpenSSF Best Practices](https://img.shields.io/cii/summary/8713?label=openssf%20best%20practices&style=for-the-badge)](https://bestpractices.coreinfrastructure.org/projects/8713)
14
14
 
15
15
  ## Installation
16
16
 
@@ -39,27 +39,27 @@ There are 7 currently implemented use cases:
39
39
  * WIP limit exceeded - from Notion to Discord
40
40
  * Support email notification - from IMAP to Discord
41
41
 
42
- For this example we'll analyze the birthday notification use case, bringing data from a notion database, and dispatching the
42
+ For this example we'll analyze the birthday notification use case, bringing data from a notion database, and sending the
43
43
  notifications to a Discord channel.
44
44
 
45
- A *Use Case* object, consists on 4 main components, having it's own responsibility:
45
+ A *Use Case* object, consists on 5 main components, having it's own responsibility:
46
46
 
47
- ### 1. Fetcher - Obtaining the data
47
+ ### 1. Read - Obtaining the data
48
48
 
49
- Specifically, a fetcher is an object in charged of bringing data from a data source. The gem already provides the base interface
50
- for building your own fetcher for your specific data source, or rely on already built classes if they match your purpose.
49
+ Specifically, a reader is an object in charged of bringing data from a data source. The gem already provides the base interface
50
+ for building your own reader for your specific data source, or rely on already built classes if they match your purpose.
51
51
 
52
- The base interface for a fetcher can be found under the `bas/fetcher/base.rb` class. Since this is a implementation of the `Fetcher::Base`
52
+ The base interface for a reader can be found under the `bas/read/base.rb` class. Since this is a implementation of the `Read::Base`
53
53
  for bringing data from a Notion database, it was created on a new namespace for that data source, it can be found under
54
- `/bas/fetcher/notion/use_case/birthday_today.rb`. It implements specific logic for fetching the data and validating the response.
54
+ `/bas/read/notion/use_case/birthday_today.rb`. It implements specific logic for reading the data and validating the response.
55
55
 
56
- ### 2. Mapper - Shaping it
56
+ ### 2. Serialize - Shaping it
57
57
 
58
- The **Mapper** responsibility, is to shape the data using custom types from the app domain, bringing it into a
58
+ The **Serializer** responsibility, is to shape the data using custom types from the app domain, bringing it into a
59
59
  common structure understandable for other components, specifically the **Formatter**.
60
60
 
61
- Because of the use case, the Mapper implementation for it, relies on specific types for representing a Birthday it self. It can be found
62
- under `/bas/mapper/notion/birthday_today.rb`
61
+ Because of the use case, the Serializer implementation for it, relies on specific types for representing a Birthday it self. It can be found
62
+ under `/bas/serialize/notion/birthday_today.rb`
63
63
 
64
64
  ### 3. Formatter - Preparing the message
65
65
 
@@ -67,11 +67,14 @@ The **Formatter**, is in charge of preparing the message to be sent in our notif
67
67
  The template or 'format' to be used should be included in the use case configurations, which we'll review in a further step. It's
68
68
  implementation can be found under `/bas/formatter/birthday.rb`.
69
69
 
70
- ### 4. Dispatcher - Sending your notification
70
+ ### 4. Process - Optional Data Process
71
71
 
72
- Finally, the **Dispatcher** basically, sends or dispatches the formatted message into a destination, since the use case was implemented for
72
+ Finally, the **Process** basically, allow required data process depending on the use case like sending formatted messages into a destination. In this case, since the use case was implemented for
73
73
  Discord, it implements specific logic to communicate with a Discord channel using a webhook. The webhook configuration and name for the 'Sender'
74
- in the channel should be provided with the initial use case configurations. It can be found under `/bas/dispatcher/discord/implementation.rb`
74
+ in the channel should be provided with the initial use case configurations. It can be found under `/bas/process/discord/implementation.rb`
75
+
76
+ ### 5. Write - Apply changes in a destination
77
+ Finally, the **Write** is in charge of creating or updating information in a destination. This is the last step in the pipeline, and it aims to allow the users to apply changes in a destination depending on the use case. These changes can be a transaction in a database, adding files in a cloud storage, or simply creating logs. For the Birthday use case, after the Process sends the notification, the Write creates a Log in the `$stdout`. It can be found under `lib/bas/write/logs/use_case/console_log.rb`
75
78
 
76
79
  ## Examples
77
80
 
@@ -113,7 +116,7 @@ today = Date.now
113
116
  }
114
117
  ```
115
118
 
116
- * A template for the formatter to be used for formatting the payload to dispatch to Discord. For this specific case, the format of the messages should be:
119
+ * A template for the formatter to be used for formatting the payload to be send to Discord. For this specific case, the format of the messages should be:
117
120
 
118
121
  `"NAME, Wishing you a very happy birthday! Enjoy your special day! :birthday: :gift:"`
119
122
 
@@ -139,13 +142,13 @@ filter = {
139
142
  }
140
143
 
141
144
  options = {
142
- fetch_options: {
145
+ read_options: {
143
146
  base_url: "https://api.notion.com",
144
147
  database_id: NOTION_DATABASE_ID,
145
148
  secret: NOTION_API_INTEGRATION_SECRET,
146
149
  filter: filter
147
150
  },
148
- dispatch_options: {
151
+ process_options: {
149
152
  webhook: "https://discord.com/api/webhooks/1199213527672565760/KmpoIzBet9xYG16oFh8W1RWHbpIqT7UtTBRrhfLcvWZdNiVZCTM-gpil2Qoy4eYEgpdf",
150
153
  name: "Birthday Bot"
151
154
  }
@@ -235,7 +238,7 @@ module Notifier
235
238
  # Service description
236
239
  class UseCaseName
237
240
  def self.notify(*)
238
- options = { fetch_options:, dispatch_options: }
241
+ options = { read_options:, process_options: }
239
242
 
240
243
  begin
241
244
  use_case = UseCases.use_case_build_function(options)
@@ -246,18 +249,18 @@ module Notifier
246
249
  end
247
250
  end
248
251
 
249
- def self.fetch_options
252
+ def self.read_options
250
253
  {
251
254
  base_url: NOTION_BASE_URL,
252
255
  database_id: NOTION_DATABASE_ID,
253
256
  secret: NOTION_SECRET,
254
257
  filter: {
255
- filter: { "and": fetch_filter }
258
+ filter: { "and": read_filter }
256
259
  }
257
260
  }
258
261
  end
259
262
 
260
- def self.fetch_filter
263
+ def self.read_filter
261
264
  today = Common::TimeZone.set_colombia_time_zone.strftime('%F').to_s
262
265
 
263
266
  [
@@ -266,7 +269,7 @@ module Notifier
266
269
  ]
267
270
  end
268
271
 
269
- def self.dispatch_options
272
+ def self.process_options
270
273
  {
271
274
  webhook: DISCORD_WEBHOOK,
272
275
  name: DISCORD_BOT_NAME
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Domain
4
+ ##
5
+ # The Domain::Issue class provides a domain-specific representation of a Github issue object.
6
+ # It encapsulates information about a repository issue, including the title, state, assignees,
7
+ # description, and the repository url.
8
+ #
9
+ class Issue
10
+ attr_reader :title, :state, :assignees, :description, :url
11
+
12
+ ATTRIBUTES = %w[title state assignees description url].freeze
13
+
14
+ def initialize(title, state, assignees, body, url)
15
+ @title = title
16
+ @state = state
17
+ @assignees = assignees
18
+ @description = body
19
+ @url = url
20
+ end
21
+ end
22
+ end
@@ -7,9 +7,9 @@ module Domain
7
7
  # the start date, and the end date of the time off period.
8
8
  #
9
9
  class Pto
10
- attr_reader :individual_name, :start_date, :end_date
10
+ attr_reader :individual_name, :start_date_from, :start_date_to, :end_date_from, :end_date_to
11
11
 
12
- ATTRIBUTES = %w[individual_name start_date end_date].freeze
12
+ ATTRIBUTES = %w[individual_name start_date_from start_date_to end_date_from end_date_to].freeze
13
13
 
14
14
  # Initializes a Domain::Pto instance with the specified individual name, start date, and end date.
15
15
  #
@@ -21,8 +21,49 @@ module Domain
21
21
  #
22
22
  def initialize(individual_name, start_date, end_date)
23
23
  @individual_name = individual_name
24
- @start_date = start_date
25
- @end_date = end_date
24
+
25
+ @start_date_from = start_date[:from]
26
+ @start_date_to = start_date[:to]
27
+ @end_date_from = end_date[:from]
28
+ @end_date_to = end_date[:to]
29
+ end
30
+
31
+ def same_day?
32
+ start_date = extract_date(start_date_from)
33
+ end_date = extract_date(end_date_from)
34
+
35
+ start_date == end_date
36
+ end
37
+
38
+ def format_timezone(timezone)
39
+ @start_date_from = set_timezone(start_date_from, timezone)
40
+ @start_date_to = set_timezone(start_date_to, timezone)
41
+ @end_date_from = set_timezone(end_date_from, timezone)
42
+ @end_date_to = set_timezone(end_date_to, timezone)
43
+ end
44
+
45
+ private
46
+
47
+ def extract_date(date)
48
+ return if date.nil?
49
+
50
+ date.strftime("%F")
51
+ end
52
+
53
+ def build_date_time(date, timezone)
54
+ return if date.nil?
55
+
56
+ date_time = date.include?("T") ? date : "#{date}T00:00:00.000#{timezone}"
57
+
58
+ DateTime.parse(date_time).to_time
59
+ end
60
+
61
+ def set_timezone(date, timezone)
62
+ return if date.nil?
63
+
64
+ date_time = build_date_time(date, timezone)
65
+
66
+ Time.at(date_time, in: timezone)
26
67
  end
27
68
  end
28
69
  end
@@ -20,7 +20,7 @@ module Formatter
20
20
  end
21
21
 
22
22
  # This method is designed to provide a specified format for data from any implementation of
23
- # the Mapper::Base interface.
23
+ # the Serialize::Base interface.
24
24
  # Must be overridden by subclasses, with specific logic based on the use case.
25
25
  #
26
26
  # <br>
@@ -30,7 +30,7 @@ module Formatter
30
30
  # <br>
31
31
  # <b>raises</b> <tt>Domain::Exceptions::FunctionNotImplemented</tt> when missing implementation.
32
32
  #
33
- # <b>returns</b> <tt>String</tt> Formatted payload suitable for a Dispatcher::Base implementation.
33
+ # <b>returns</b> <tt>String</tt> Formatted payload suitable for a Process::Base implementation.
34
34
  #
35
35
  def format(_domain_data)
36
36
  raise Domain::Exceptions::FunctionNotImplemented
@@ -3,32 +3,36 @@
3
3
  require_relative "../domain/birthday"
4
4
  require_relative "./exceptions/invalid_data"
5
5
  require_relative "./base"
6
+ require_relative "./types/response"
6
7
 
7
8
  module Formatter
8
9
  ##
9
10
  # This class implements methods from the Formatter::Base module, tailored to format the
10
- # Domain::Birthday structure for a dispatcher.
11
+ # Domain::Birthday structure for a Process.
11
12
  class Birthday < Base
12
13
  # Implements the logic for building a formatted payload with the given template for birthdays.
13
14
  #
14
15
  # <br>
15
16
  # <b>Params:</b>
16
- # * <tt>List<Domain::Birthday></tt> birthdays_list: list of mapped birthdays.
17
+ # * <tt>List<Domain::Birthday></tt> birthdays_list: list of serialized birthdays.
17
18
  #
18
19
  # <br>
19
20
  # <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
20
21
  #
21
22
  # <br>
22
- # <b>returns</b> <tt>String</tt> payload: formatted payload suitable for a Dispatcher.
23
+ # <b>returns</b> <tt>Formatter::Types::Response</tt> formatter response: standard output for
24
+ # the formatted payload suitable for a Process.
23
25
  #
24
26
  def format(birthdays_list)
25
27
  raise Formatter::Exceptions::InvalidData unless birthdays_list.all? do |brithday|
26
28
  brithday.is_a?(Domain::Birthday)
27
29
  end
28
30
 
29
- birthdays_list.reduce("") do |payload, birthday|
31
+ response = birthdays_list.reduce("") do |payload, birthday|
30
32
  payload + build_template(Domain::Birthday::ATTRIBUTES, birthday)
31
33
  end
34
+
35
+ Formatter::Types::Response.new(response)
32
36
  end
33
37
  end
34
38
  end
@@ -5,11 +5,12 @@ require "date"
5
5
  require_relative "../domain/pto"
6
6
  require_relative "./exceptions/invalid_data"
7
7
  require_relative "./base"
8
+ require_relative "./types/response"
8
9
 
9
10
  module Formatter
10
11
  ##
11
12
  # This class implements methods from the Formatter::Base module, tailored to format the
12
- # Domain::Pto structure for a dispatcher.
13
+ # Domain::Pto structure for a Process.
13
14
  class Pto < Base
14
15
  DEFAULT_TIME_ZONE = "+00:00"
15
16
 
@@ -26,63 +27,63 @@ module Formatter
26
27
  #
27
28
  # <br>
28
29
  # <b>Params:</b>
29
- # * <tt>List<Domain::Pto></tt> pto_list: List of mapped PTO's.
30
+ # * <tt>List<Domain::Pto></tt> pto_list: List of serialized PTO's.
30
31
  #
31
32
  # <br>
32
33
  # <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
33
34
  #
34
35
  # <br>
35
- # <b>returns</b> <tt>String</tt> payload, formatted payload suitable for a Dispatcher.
36
+ # <b>returns</b> <tt>Formatter::Types::Response</tt> formatter response: standard output for
37
+ # the formatted payload suitable for a Process.
36
38
  #
37
39
 
38
40
  def format(ptos_list)
39
41
  raise Formatter::Exceptions::InvalidData unless ptos_list.all? { |pto| pto.is_a?(Domain::Pto) }
40
42
 
41
- ptos_list.reduce("") do |payload, pto|
43
+ ptos_list.each { |pto| pto.format_timezone(@timezone) }
44
+
45
+ response = ptos_list.reduce("") do |payload, pto|
42
46
  built_template = build_template(Domain::Pto::ATTRIBUTES, pto)
43
47
  payload + format_message_by_case(built_template.gsub("\n", ""), pto)
44
48
  end
49
+
50
+ Formatter::Types::Response.new(response)
45
51
  end
46
52
 
47
53
  private
48
54
 
49
55
  def format_message_by_case(built_template, pto)
50
- date_start = format_timezone(pto.start_date).strftime("%F")
51
- date_end = format_timezone(pto.end_date).strftime("%F")
52
-
53
- if date_start == date_end
56
+ if pto.same_day?
54
57
  interval = same_day_interval(pto)
55
- day_message = today?(date_start) ? "today" : "the day #{date_start}"
58
+ day_message = today?(pto.start_date_from) ? "today" : "the day #{pto.start_date_from.strftime("%F")}"
56
59
 
57
60
  "#{built_template} #{day_message} #{interval}\n"
58
61
  else
59
- "#{built_template} from #{date_start} to #{date_end}\n"
62
+ start_date_interval = day_interval(pto.start_date_from, pto.start_date_to)
63
+ end_date_interval = day_interval(pto.end_date_from, pto.end_date_to)
64
+
65
+ "#{built_template} from #{start_date_interval} to #{end_date_interval}\n"
60
66
  end
61
67
  end
62
68
 
63
- def same_day_interval(pto)
64
- time_start = format_timezone(pto.start_date).strftime("%I:%M %P")
65
- time_end = format_timezone(pto.end_date).strftime("%I:%M %P")
69
+ def day_interval(start_date, end_date)
70
+ return start_date.strftime("%F") if end_date.nil?
66
71
 
67
- time_start == time_end ? "all day" : "from #{time_start} to #{time_end}"
68
- end
72
+ time_start = start_date.strftime("%I:%M %P")
73
+ time_end = end_date.strftime("%I:%M %P")
69
74
 
70
- def format_timezone(date)
71
- date_time = build_date(date)
72
-
73
- Time.at(date_time, in: @timezone)
75
+ "#{start_date.strftime("%F")} (#{time_start} - #{time_end})"
74
76
  end
75
77
 
76
- def today?(date)
77
- time_now = Time.now.strftime("%F")
78
+ def same_day_interval(pto)
79
+ time_start = pto.start_date_from.strftime("%I:%M %P")
80
+ time_end = pto.end_date_from.strftime("%I:%M %P")
78
81
 
79
- date == format_timezone(time_now).strftime("%F")
82
+ time_start == time_end ? "all day" : "from #{time_start} to #{time_end}"
80
83
  end
81
84
 
82
- def build_date(date)
83
- date_time = date.include?("T") ? date : "#{date}T00:00:00.000#{@timezone}"
84
-
85
- DateTime.parse(date_time).to_time
85
+ def today?(date)
86
+ date == Time.now(in: @timezone).strftime("%F")
86
87
  end
87
88
  end
88
89
  end
@@ -3,11 +3,12 @@
3
3
  require_relative "../domain/email"
4
4
  require_relative "./exceptions/invalid_data"
5
5
  require_relative "./base"
6
+ require_relative "./types/response"
6
7
 
7
8
  module Formatter
8
9
  ##
9
10
  # This class implements methods from the Formatter::Base module, tailored to format the
10
- # Domain::Email structure for a dispatcher.
11
+ # Domain::Email structure for a Process.
11
12
  class SupportEmails < Base
12
13
  DEFAULT_TIME_ZONE = "+00:00"
13
14
 
@@ -31,16 +32,19 @@ module Formatter
31
32
  # <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
32
33
  #
33
34
  # <br>
34
- # <b>returns</b> <tt>String</tt> payload: formatted payload suitable for a Dispatcher.
35
+ # <b>returns</b> <tt>Formatter::Types::Response</tt> formatter response: standard output for
36
+ # the formatted payload suitable for a Process.
35
37
  #
36
38
  def format(support_emails_list)
37
39
  raise Formatter::Exceptions::InvalidData unless support_emails_list.all? do |support_email|
38
40
  support_email.is_a?(Domain::Email)
39
41
  end
40
42
 
41
- process_emails(support_emails_list).reduce("") do |payload, support_email|
43
+ response = process_emails(support_emails_list).reduce("") do |payload, support_email|
42
44
  payload + build_template(Domain::Email::ATTRIBUTES, support_email)
43
45
  end
46
+
47
+ Formatter::Types::Response.new(response)
44
48
  end
45
49
 
46
50
  private
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Types
5
+ ##
6
+ # Represents a response received from a Formatter. It encapsulates the formatted data to be used by
7
+ # a Process or a Write component.
8
+ class Response
9
+ attr_reader :data
10
+
11
+ def initialize(response)
12
+ @data = response
13
+ end
14
+ end
15
+ end
16
+ end
@@ -3,11 +3,12 @@
3
3
  require_relative "../domain/work_items_limit"
4
4
  require_relative "./exceptions/invalid_data"
5
5
  require_relative "./base"
6
+ require_relative "./types/response"
6
7
 
7
8
  module Formatter
8
9
  ##
9
10
  # This class implements methods from the Formatter::Base module, tailored to format the
10
- # Domain::WorkItemsLimit structure for a dispatcher.
11
+ # Domain::WorkItemsLimit structure for a Process.
11
12
  class WorkItemsLimit < Base
12
13
  DEFAULT_DOMAIN_LIMIT = 6
13
14
 
@@ -24,13 +25,14 @@ module Formatter
24
25
  #
25
26
  # <br>
26
27
  # <b>Params:</b>
27
- # * <tt>List<Domain::WorkItemsLimit></tt> work_items_list: List of mapped work items limits.
28
+ # * <tt>List<Domain::WorkItemsLimit></tt> work_items_list: List of serialized work items limits.
28
29
  #
29
30
  # <br>
30
31
  # <b>raises</b> <tt>Formatter::Exceptions::InvalidData</tt> when invalid data is provided.
31
32
  #
32
33
  # <br>
33
- # <b>returns</b> <tt>String</tt> payload, formatted payload suitable for a Dispatcher.
34
+ # <b>returns</b> <tt>Formatter::Types::Response</tt> formatter response: standard output for
35
+ # the formatted payload suitable for a Process.
34
36
  #
35
37
 
36
38
  def format(work_items_list)
@@ -38,10 +40,12 @@ module Formatter
38
40
  work_item.is_a?(Domain::WorkItemsLimit)
39
41
  end
40
42
 
41
- exceeded_domains(work_items_list).reduce("") do |payload, work_items_limit|
43
+ response = exceeded_domains(work_items_list).reduce("") do |payload, work_items_limit|
42
44
  built_template = build_template(Domain::WorkItemsLimit::ATTRIBUTES, work_items_limit)
43
45
  payload + format_message_by_case(built_template.gsub("\n", ""), work_items_limit)
44
46
  end
47
+
48
+ Formatter::Types::Response.new(response)
45
49
  end
46
50
 
47
51
  private
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../domain/exceptions/function_not_implemented"
4
+ require_relative "./types/response"
5
+
6
+ module Process
7
+ ##
8
+ # Serves as a foundational structure for implementing specific process. Acting as an interface,
9
+ # this class defines essential attributes and methods, providing a blueprint for creating custom
10
+ # process tailored to different platforms or services.
11
+ #
12
+ class Base
13
+ attr_reader :config
14
+
15
+ # Initializes the process with essential configuration parameters.
16
+ #
17
+ def initialize(config = {})
18
+ @config = config
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>Process::Types::Response</tt>: standard output for a process
26
+ #
27
+ def execute(format_response)
28
+ Process::Types::Response.new(format_response.data)
29
+ end
30
+
31
+ protected
32
+
33
+ def valid_format_response(format_response)
34
+ return format_response if format_response.is_a?(Formatter::Types::Response)
35
+
36
+ raise Formatter::Exceptions::InvalidData
37
+ end
38
+ end
39
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dispatcher
3
+ module Process
4
4
  module Discord
5
5
  module Exceptions
6
6
  ##
@@ -0,0 +1,71 @@
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 Process
8
+ module Discord
9
+ ##
10
+ # This class is an implementation of the Process::Base interface, specifically designed
11
+ # for sending messages to Discord.
12
+ #
13
+ class Implementation < Base
14
+ attr_reader :webhook, :name
15
+
16
+ # Initializes the process with essential configuration parameters.
17
+ #
18
+ def initialize(config = {})
19
+ super(config)
20
+
21
+ @webhook = config[:webhook]
22
+ @name = config[:name]
23
+ end
24
+
25
+ # Implements the sending process logic for the Discord use case. It sends a POST request to
26
+ # the Discord webhook with the specified payload.
27
+ #
28
+ # <br>
29
+ # <b>Params:</b>
30
+ # * <tt>Formatter::Types::Response</tt> formatter response: standard formatter response
31
+ # with the Payload to be send to discord.
32
+ # <br>
33
+ # <b>raises</b> <tt>Exceptions::Discord::InvalidWebookToken</tt> if the provided webhook
34
+ # token is invalid.
35
+ #
36
+ # <br>
37
+ # <b>returns</b> <tt>Process::Types::Response</tt>
38
+ #
39
+ def execute(format_response)
40
+ response = valid_format_response(format_response)
41
+
42
+ body = post_body(response.data)
43
+
44
+ response = HTTParty.post(webhook, { body: body, headers: { "Content-Type" => "application/json" } })
45
+
46
+ discord_response = Process::Discord::Types::Response.new(response)
47
+
48
+ validate_response(discord_response)
49
+ end
50
+
51
+ private
52
+
53
+ def post_body(payload)
54
+ {
55
+ username: name,
56
+ avatar_url: "",
57
+ content: payload
58
+ }.to_json
59
+ end
60
+
61
+ def validate_response(response)
62
+ case response.code
63
+ when 50_027
64
+ raise Discord::Exceptions::InvalidWebookToken, response.message
65
+ else
66
+ Process::Types::Response.new(response)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Dispatcher
3
+ module Process
4
4
  module Discord
5
5
  module Types
6
6
  ##