bns 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/._.rspec_status +0 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile +6 -0
  5. data/README.md +159 -3
  6. data/lib/bns/dispatcher/discord/exceptions/invalid_webhook_token.rb +2 -2
  7. data/lib/bns/dispatcher/discord/implementation.rb +1 -1
  8. data/lib/bns/dispatcher/slack/exceptions/invalid_webhook_token.rb +16 -0
  9. data/lib/bns/dispatcher/slack/implementation.rb +51 -0
  10. data/lib/bns/dispatcher/slack/types/response.rb +21 -0
  11. data/lib/bns/domain/email.rb +34 -0
  12. data/lib/bns/domain/work_items_limit.rb +25 -0
  13. data/lib/bns/fetcher/base.rb +13 -0
  14. data/lib/bns/fetcher/imap/base.rb +70 -0
  15. data/lib/bns/fetcher/imap/types/response.rb +27 -0
  16. data/lib/bns/fetcher/imap/use_case/support_emails.rb +26 -0
  17. data/lib/bns/fetcher/notion/{pto.rb → base.rb} +11 -7
  18. data/lib/bns/fetcher/notion/types/response.rb +1 -1
  19. data/lib/bns/fetcher/notion/use_case/birthday_next_week.rb +41 -0
  20. data/lib/bns/fetcher/notion/use_case/birthday_today.rb +29 -0
  21. data/lib/bns/fetcher/notion/use_case/pto_next_week.rb +71 -0
  22. data/lib/bns/fetcher/notion/use_case/pto_today.rb +30 -0
  23. data/lib/bns/fetcher/notion/use_case/work_items_limit.rb +37 -0
  24. data/lib/bns/fetcher/postgres/base.rb +46 -0
  25. data/lib/bns/fetcher/postgres/helper.rb +16 -0
  26. data/lib/bns/fetcher/postgres/types/response.rb +42 -0
  27. data/lib/bns/fetcher/postgres/use_case/pto_today.rb +32 -0
  28. data/lib/bns/formatter/base.rb +11 -8
  29. data/lib/bns/formatter/birthday.rb +34 -0
  30. data/lib/bns/formatter/exceptions/invalid_data.rb +15 -0
  31. data/lib/bns/formatter/pto.rb +88 -0
  32. data/lib/bns/formatter/support_emails.rb +69 -0
  33. data/lib/bns/formatter/work_items_limit.rb +64 -0
  34. data/lib/bns/mapper/imap/support_emails.rb +56 -0
  35. data/lib/bns/mapper/notion/{birthday.rb → birthday_today.rb} +13 -21
  36. data/lib/bns/mapper/notion/{pto.rb → pto_today.rb} +15 -41
  37. data/lib/bns/mapper/notion/work_items_limit.rb +65 -0
  38. data/lib/bns/mapper/postgres/pto_today.rb +47 -0
  39. data/lib/bns/use_cases/use_cases.rb +276 -49
  40. data/lib/bns/version.rb +1 -1
  41. metadata +31 -9
  42. data/lib/bns/fetcher/notion/birthday.rb +0 -53
  43. data/lib/bns/formatter/discord/birthday.rb +0 -36
  44. data/lib/bns/formatter/discord/exceptions/invalid_data.rb +0 -17
  45. data/lib/bns/formatter/discord/pto.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '088295440471344d853d850bf071e7fb885fa824a49bfcd182fe5d6a6de03b6c'
4
- data.tar.gz: 909cec977526aaf735599e1b6dcbf8a0144eeb0dc8f02a37e371a4f90fb62405
3
+ metadata.gz: f45452ff8e3569ad4ffed2d32d8cc7801cd5a4379205ca70d7d401814abb7d50
4
+ data.tar.gz: 64cd26aaa2269270f77f50ec5b1d4371272d4f53d280bed5b0ccd727b0e628b7
5
5
  SHA512:
6
- metadata.gz: 473e834fb819c4f8b36a053643b883b2cf5704c89aa470f9a58c063e148fbdfa7c8f78ac745b85f9ef193066f4e00e4b084f2add8385a58f892759297a23fa1f
7
- data.tar.gz: bc15074b2628619f25786b8da2edcd5a0ec20eb5154df50027450886bc7e2f3c6cceba8897081ef78d0ac9770d19c2ca349ff799c64842bdef8ef4736a6b6fe6
6
+ metadata.gz: a61ce154f73bcf97b76828f437cf4cbd0af1809458532de9ec232343e9a92c85ec118d336a5f13b4eb95cb8e792e2b10bd941c90c79c720c2ae7b4fd5272543f
7
+ data.tar.gz: '088fc7b072e971ebc37259cc2e017bcc22aa69d9c8d2f035958bb33b578108235686f889c1faccdb6a6566e39b44cddacc6a356c737a81924cebd10d6379d057'
data/._.rspec_status ADDED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (19.03.2024)
4
+ - [Add Component - Email fetcher](https://github.com/kommitters/bns/issues/40)
5
+ - [Use case - Email based notifications](https://github.com/kommitters/bns/issues/45)
6
+ - [Dynamic WIP limit value](https://github.com/kommitters/bns/issues/48)
7
+
8
+ ## 0.2.0 (29.02.2024)
9
+ - [Add a Postgres fetcher component](https://github.com/kommitters/bns/issues/28)
10
+ - [Use case - PTO's Postgres-Slack implementation](https://github.com/kommitters/bns/issues/30)
11
+ - [Slack dispatcher](https://github.com/kommitters/bns/issues/32)
12
+ - [Refactor fetcher components](https://github.com/kommitters/bns/issues/34)
13
+ - [Add Use Case - Boards WI alerts](https://github.com/kommitters/bns/issues/36)
14
+ - [Add Use Case - Next Week Birthday](https://github.com/kommitters/bns/issues/38)
15
+ - [Add Use Case - Next Week PTO's](https://github.com/kommitters/bns/issues/39)
16
+
3
17
  ## 0.1.1 (07.02.2024)
4
18
  - [Add custom templates option](https://github.com/kommitters/bns/issues/25)
5
19
  - [PTO's formatting use cases](https://github.com/kommitters/bns/issues/24)
data/Gemfile CHANGED
@@ -7,6 +7,8 @@ gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
9
 
10
+ gem "net-imap", "~> 0.4.10"
11
+ gem "net-smtp", "~> 0.4.0.1"
10
12
  gem "rspec", "~> 3.0"
11
13
  gem "rubocop", "~> 1.21"
12
14
  gem "simplecov", require: false, group: :test
@@ -16,3 +18,7 @@ gem "vcr"
16
18
  gem "webmock"
17
19
 
18
20
  gem "httparty"
21
+
22
+ gem "pg", "~> 1.5", ">= 1.5.4"
23
+
24
+ gem "gmail_xoauth"
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # BNS - Business Notification System
2
2
 
3
- The business notification system is designed to be a versatile platform, offering key components for building various use cases. It provides an easy-to-use tool for implementing notifications without excessive complexity.
3
+ Many organizations and individuals rely on automatic notifications across various contexts in their daily operations. With BNS, we aim to provide an open-source platform that empowers users to create customized notification systems tailored to their unique requirements. BNS consists of a series of abstract components designed to facilitate the creation of diverse use cases, regardless of context.
4
+
5
+ The underlying idea is to develop generic components that can serve a wide range of needs, this approach ensures that all members of the community can leverage the platform's evolving suite of components and use cases to their advantage.
4
6
 
5
7
  ![Gem Version](https://img.shields.io/gem/v/bns?style=for-the-badge)
6
8
  ![Gem Total Downloads](https://img.shields.io/gem/dt/bns?style=for-the-badge)
@@ -150,9 +152,163 @@ use_case.perform
150
152
 
151
153
  ```
152
154
 
153
- **Serverless**
155
+ ### Serverless
156
+ We'll explain how to configure and deploy a use case with serverless, this example will cover the PTO's notifications use case.
157
+
158
+ #### Configuring environment variables
159
+ Create the environment variables configuration file.
160
+
161
+ ```bash
162
+ cp env.yml.example env.yml
163
+ ```
154
164
 
155
- Examples of different use cases, and how to configure and deploy the lambdas can be found on: `https://github.com/kommitters/bns_serverless`
165
+ And put the following env variables
166
+ ```
167
+ dev:
168
+ NOTION_DATABASE_ID: NOTION_DATABASE_ID
169
+ NOTION_SECRET: NOTION_SECRET
170
+ DISCORD_WEBHOOK: DISCORD_WEBHOOK
171
+ DISCORD_BOT_NAME: DISCORD_BOT_NAME
172
+ prod:
173
+ NOTION_DATABASE_ID: NOTION_DATABASE_ID
174
+ NOTION_SECRET: NOTION_SECRET
175
+ DISCORD_WEBHOOK: DISCORD_WEBHOOK
176
+ DISCORD_BOT_NAME: DISCORD_BOT_NAME
177
+
178
+ ```
179
+
180
+ The variables should be defined either in the custom settings section within the `serverless.yml` file to ensure accessibility by all lambdas, or in the environment configuration option for each lambda respectively. For example:
181
+
182
+ ```bash
183
+ # Accessible by all the lambdas
184
+ custom:
185
+ settings:
186
+ api:
187
+ NOTION_DATABASE_ID: ${file(./env.yml):${env:STAGE}.NOTION_DATABASE_ID}
188
+ NOTION_SECRET: ${file(./env.yml):${env:STAGE}.NOTION_SECRET}}
189
+
190
+ # Accessible by the lambda
191
+ functions:
192
+ lambdaName:
193
+ environment:
194
+ NOTION_DATABASE_ID: ${file(./env.yml):${env:STAGE}.NOTION_DATABASE_ID}
195
+ NOTION_SECRET: ${file(./env.yml):${env:STAGE}.NOTION_SECRET}}
196
+ ```
197
+
198
+ #### Schedule
199
+ the schedule is configured using an environment variable containing the cron configuration. For example:
200
+ ```bash
201
+ # env.yml file
202
+ SCHEDULER: cron(0 13 ? * MON-FRI *)
203
+
204
+ # serverless.yml
205
+ functions:
206
+ lambdaName:
207
+ events:
208
+ - schedule:
209
+ rate: ${file(./env.yml):${env:STAGE}.SCHEDULER}
210
+ ```
211
+
212
+ To learn how to modify the cron configuration follow this guide: [Schedule expressions using rate or cron](https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents-expressions.html)
213
+
214
+ #### Building your lambda
215
+ On your serverless configuration, create your lambda function, on your serverless `/src` folder.
216
+
217
+ ```ruby
218
+ # frozen_string_literal: true
219
+
220
+ require 'bns'
221
+
222
+ # Initialize the environment variables
223
+ NOTION_BASE_URL = 'https://api.notion.com'
224
+ NOTION_DATABASE_ID = ENV.fetch('PTO_NOTION_DATABASE_ID')
225
+ NOTION_SECRET = ENV.fetch('PTO_NOTION_SECRET')
226
+ DISCORD_WEBHOOK = ENV.fetch('PTO_DISCORD_WEBHOOK')
227
+ DISCORD_BOT_NAME = ENV.fetch('PTO_DISCORD_BOT_NAME')
228
+
229
+ module Notifier
230
+ # Service description
231
+ class UseCaseName
232
+ def self.notify(*)
233
+ options = { fetch_options:, dispatch_options: }
234
+
235
+ begin
236
+ use_case = UseCases.use_case_build_function(options)
237
+
238
+ use_case.perform
239
+ rescue StandardError => e
240
+ { body: { message: e.message } }
241
+ end
242
+ end
243
+
244
+ def self.fetch_options
245
+ {
246
+ base_url: NOTION_BASE_URL,
247
+ database_id: NOTION_DATABASE_ID,
248
+ secret: NOTION_SECRET,
249
+ filter: {
250
+ filter: { "and": fetch_filter }
251
+ }
252
+ }
253
+ end
254
+
255
+ def self.fetch_filter
256
+ today = Common::TimeZone.set_colombia_time_zone.strftime('%F').to_s
257
+
258
+ [
259
+ { property: 'Desde?', date: { on_or_before: today } },
260
+ { property: 'Hasta?', date: { on_or_after: today } }
261
+ ]
262
+ end
263
+
264
+ def self.dispatch_options
265
+ {
266
+ webhook: DISCORD_WEBHOOK,
267
+ name: DISCORD_BOT_NAME
268
+ }
269
+ end
270
+
271
+ def self.format_options
272
+ {
273
+ template: ':beach: individual_name is on PTO'
274
+ }
275
+ end
276
+ end
277
+ end
278
+ ```
279
+
280
+ #### Configure the lambda
281
+ In the `serverless.yml` file, add a new instance in the `functions` block with this structure:
282
+
283
+ ```bash
284
+ functions:
285
+ ptoNotification:
286
+ handler: src/lambdas/pto_notification.Notifier::PTO.notify
287
+ environment:
288
+ PTO_NOTION_DATABASE_ID: ${file(./env.yml):${env:STAGE}.PTO_NOTION_DATABASE_ID}
289
+ PTO_NOTION_SECRET: ${file(./env.yml):${env:STAGE}.PTO_NOTION_SECRET}
290
+ PTO_DISCORD_WEBHOOK: ${file(./env.yml):${env:STAGE}.PTO_DISCORD_WEBHOOK}
291
+ PTO_DISCORD_BOT_NAME: ${file(./env.yml):${env:STAGE}.DISCORD_BOT_NAME}
292
+ events:
293
+ - schedule:
294
+ name: pto-daily-notification
295
+ description: "Notify every 24 hours at 8:30 a.m (UTC-5) from monday to friday"
296
+ rate: cron(${file(./env.yml):${env:STAGE}.PTO_SCHEDULER})
297
+ enabled: true
298
+ ```
299
+
300
+ #### Deploying
301
+
302
+ Configure the AWS keys:
303
+
304
+ ```bash
305
+ serverless config credentials --provider aws --key YOUR_KEY --secret YOUR_SECRET
306
+ ```
307
+
308
+ Deploy the project:
309
+ ```bash
310
+ STAGE=prod sls deploy --verbose
311
+ ```
156
312
 
157
313
  ## Development
158
314
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dispatcher
4
- module Exceptions
5
- module Discord
4
+ module Discord
5
+ module Exceptions
6
6
  ##
7
7
  # Domain specific representation when an invalid Discord webhook token is provided to Discord.
8
8
  #
@@ -41,7 +41,7 @@ module Dispatcher
41
41
  def validate_response(response)
42
42
  case response.code
43
43
  when 50_027
44
- raise Exceptions::Discord::InvalidWebookToken, response.message
44
+ raise Discord::Exceptions::InvalidWebookToken, response.message
45
45
  else
46
46
  response
47
47
  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,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,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
@@ -26,5 +26,18 @@ module Fetcher
26
26
  def fetch
27
27
  raise Domain::Exceptions::FunctionNotImplemented
28
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
29
42
  end
30
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
@@ -1,21 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "httparty"
4
- require "date"
5
4
 
6
5
  require_relative "../base"
7
6
  require_relative "./exceptions/invalid_api_key"
8
7
  require_relative "./exceptions/invalid_database_id"
9
8
  require_relative "./types/response"
9
+ require_relative "./helper"
10
10
 
11
11
  module Fetcher
12
12
  module Notion
13
13
  ##
14
14
  # This class is an implementation of the Fetcher::Base interface, specifically designed
15
- # for fetching Paid Time Off (PTO) data from Notion.
15
+ # for fetching data from Notion.
16
16
  #
17
- class Pto < Base
18
- # Implements the data fetching logic for PTO's data from Notion. It sends a POST
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
19
23
  # request to the Notion API to query the specified database and returns a validated response.
20
24
  #
21
25
  # <br>
@@ -24,10 +28,10 @@ module Fetcher
24
28
  # <b>raises</b> <tt>Exceptions::Notion::InvalidDatabaseId</tt> if the Database id provided is incorrect
25
29
  # or invalid.
26
30
  #
27
- def fetch
28
- url = "#{config[:base_url]}/v1/databases/#{config[:database_id]}/query"
31
+ def execute(filter)
32
+ url = "#{NOTION_BASE_URL}/v1/databases/#{config[:database_id]}/query"
29
33
 
30
- httparty_response = HTTParty.post(url, { body: config[:filter].to_json, headers: headers })
34
+ httparty_response = HTTParty.post(url, { body: filter.to_json, headers: headers })
31
35
 
32
36
  notion_response = Fetcher::Notion::Types::Response.new(httparty_response)
33
37
 
@@ -13,7 +13,7 @@ module Fetcher
13
13
  if response["results"].nil?
14
14
  @status_code = response["status"]
15
15
  @message = response["message"]
16
- @results = nil
16
+ @results = []
17
17
  else
18
18
  @status_code = 200
19
19
  @message = "success"
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Fetcher
6
+ module Notion
7
+ ##
8
+ # This class is an implementation of the Fetcher::Notion::Base interface, specifically designed
9
+ # for fetching next week birthdays data from Notion.
10
+ #
11
+ class BirthdayNextWeek < Notion::Base
12
+ DAYS_BEFORE_NOTIFY = 8
13
+
14
+ # Implements the data fetching filter for next week Birthdays data from Notion.
15
+ #
16
+ def fetch
17
+ filter = {
18
+ filter: {
19
+ or: [
20
+ { property: "BD_this_year", date: { equals: eight_days_from_now } }
21
+ ]
22
+ }
23
+ }
24
+
25
+ execute(filter)
26
+ end
27
+
28
+ private
29
+
30
+ def eight_days_from_now
31
+ date = Time.now.utc + days_in_second(DAYS_BEFORE_NOTIFY)
32
+
33
+ date.utc.strftime("%F").to_s
34
+ end
35
+
36
+ def days_in_second(days)
37
+ days * 24 * 60 * 60
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Fetcher
6
+ module Notion
7
+ ##
8
+ # This class is an implementation of the Fetcher::Notion::Base interface, specifically designed
9
+ # for fetching birthday data from Notion.
10
+ #
11
+ class BirthdayToday < Notion::Base
12
+ # Implements the data fetching filter for todays Birthdays data from Notion.
13
+ #
14
+ def fetch
15
+ today = Time.now.utc.strftime("%F").to_s
16
+
17
+ filter = {
18
+ filter: {
19
+ or: [
20
+ { property: "BD_this_year", date: { equals: today } }
21
+ ]
22
+ }
23
+ }
24
+
25
+ execute(filter)
26
+ end
27
+ end
28
+ end
29
+ end