bns 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![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
|
-
|
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
|