bns 0.1.1 → 0.3.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/._.rspec_status +0 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +6 -0
- data/README.md +159 -3
- data/lib/bns/dispatcher/discord/exceptions/invalid_webhook_token.rb +2 -2
- data/lib/bns/dispatcher/discord/implementation.rb +1 -1
- data/lib/bns/dispatcher/slack/exceptions/invalid_webhook_token.rb +16 -0
- data/lib/bns/dispatcher/slack/implementation.rb +51 -0
- data/lib/bns/dispatcher/slack/types/response.rb +21 -0
- data/lib/bns/domain/email.rb +34 -0
- data/lib/bns/domain/work_items_limit.rb +25 -0
- data/lib/bns/fetcher/base.rb +13 -0
- data/lib/bns/fetcher/imap/base.rb +70 -0
- data/lib/bns/fetcher/imap/types/response.rb +27 -0
- data/lib/bns/fetcher/imap/use_case/support_emails.rb +26 -0
- data/lib/bns/fetcher/notion/{pto.rb → base.rb} +11 -7
- data/lib/bns/fetcher/notion/types/response.rb +1 -1
- data/lib/bns/fetcher/notion/use_case/birthday_next_week.rb +41 -0
- data/lib/bns/fetcher/notion/use_case/birthday_today.rb +29 -0
- data/lib/bns/fetcher/notion/use_case/pto_next_week.rb +71 -0
- data/lib/bns/fetcher/notion/use_case/pto_today.rb +30 -0
- data/lib/bns/fetcher/notion/use_case/work_items_limit.rb +37 -0
- data/lib/bns/fetcher/postgres/base.rb +46 -0
- data/lib/bns/fetcher/postgres/helper.rb +16 -0
- data/lib/bns/fetcher/postgres/types/response.rb +42 -0
- data/lib/bns/fetcher/postgres/use_case/pto_today.rb +32 -0
- data/lib/bns/formatter/base.rb +11 -8
- data/lib/bns/formatter/birthday.rb +34 -0
- data/lib/bns/formatter/exceptions/invalid_data.rb +15 -0
- data/lib/bns/formatter/pto.rb +88 -0
- data/lib/bns/formatter/support_emails.rb +69 -0
- data/lib/bns/formatter/work_items_limit.rb +64 -0
- data/lib/bns/mapper/imap/support_emails.rb +56 -0
- data/lib/bns/mapper/notion/{birthday.rb → birthday_today.rb} +13 -21
- data/lib/bns/mapper/notion/{pto.rb → pto_today.rb} +15 -41
- data/lib/bns/mapper/notion/work_items_limit.rb +65 -0
- data/lib/bns/mapper/postgres/pto_today.rb +47 -0
- data/lib/bns/use_cases/use_cases.rb +276 -49
- data/lib/bns/version.rb +1 -1
- metadata +31 -9
- data/lib/bns/fetcher/notion/birthday.rb +0 -53
- data/lib/bns/formatter/discord/birthday.rb +0 -36
- data/lib/bns/formatter/discord/exceptions/invalid_data.rb +0 -17
- data/lib/bns/formatter/discord/pto.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f45452ff8e3569ad4ffed2d32d8cc7801cd5a4379205ca70d7d401814abb7d50
|
4
|
+
data.tar.gz: 64cd26aaa2269270f77f50ec5b1d4371272d4f53d280bed5b0ccd727b0e628b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|

|
6
8
|

|
@@ -150,9 +152,163 @@ use_case.perform
|
|
150
152
|
|
151
153
|
```
|
152
154
|
|
153
|
-
|
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
|
-
|
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
|
|
@@ -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
|
data/lib/bns/fetcher/base.rb
CHANGED
@@ -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
|
15
|
+
# for fetching data from Notion.
|
16
16
|
#
|
17
|
-
class
|
18
|
-
|
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
|
28
|
-
url = "#{
|
31
|
+
def execute(filter)
|
32
|
+
url = "#{NOTION_BASE_URL}/v1/databases/#{config[:database_id]}/query"
|
29
33
|
|
30
|
-
httparty_response = HTTParty.post(url, { body:
|
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
|
|
@@ -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
|