bas 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +68 -147
  4. data/lib/bas/bot/base.rb +74 -0
  5. data/lib/bas/bot/compare_wip_limit_count.rb +92 -0
  6. data/lib/bas/bot/fetch_birthdays_from_notion.rb +128 -0
  7. data/lib/bas/bot/fetch_domains_wip_counts_from_notion.rb +121 -0
  8. data/lib/bas/bot/fetch_domains_wip_limit_from_notion.rb +134 -0
  9. data/lib/bas/bot/fetch_emails_from_imap.rb +99 -0
  10. data/lib/bas/bot/fetch_next_week_birthdays_from_notion.rb +142 -0
  11. data/lib/bas/bot/fetch_next_week_ptos_from_notion.rb +162 -0
  12. data/lib/bas/bot/fetch_ptos_from_notion.rb +138 -0
  13. data/lib/bas/bot/format_birthdays.rb +97 -0
  14. data/lib/bas/bot/format_emails.rb +124 -0
  15. data/lib/bas/bot/format_wip_limit_exceeded.rb +97 -0
  16. data/lib/bas/bot/garbage_collector.rb +85 -0
  17. data/lib/bas/bot/humanize_pto.rb +119 -0
  18. data/lib/bas/bot/notify_discord.rb +96 -0
  19. data/lib/bas/read/base.rb +10 -23
  20. data/lib/bas/read/default.rb +16 -0
  21. data/lib/bas/read/postgres.rb +44 -0
  22. data/lib/bas/read/types/response.rb +18 -0
  23. data/lib/bas/utils/discord/integration.rb +43 -0
  24. data/lib/bas/utils/exceptions/function_not_implemented.rb +16 -0
  25. data/lib/bas/utils/exceptions/invalid_process_response.rb +16 -0
  26. data/lib/bas/utils/imap/request.rb +76 -0
  27. data/lib/bas/utils/notion/request.rb +45 -0
  28. data/lib/bas/utils/openai/run_assistant.rb +99 -0
  29. data/lib/bas/utils/postgres/request.rb +50 -0
  30. data/lib/bas/version.rb +1 -1
  31. data/lib/bas/write/base.rb +12 -17
  32. data/lib/bas/write/postgres.rb +45 -0
  33. data/lib/bas/write/postgres_update.rb +49 -0
  34. data/lib/bas.rb +1 -3
  35. metadata +30 -67
  36. data/lib/bas/domain/birthday.rb +0 -25
  37. data/lib/bas/domain/email.rb +0 -34
  38. data/lib/bas/domain/exceptions/function_not_implemented.rb +0 -18
  39. data/lib/bas/domain/issue.rb +0 -22
  40. data/lib/bas/domain/notification.rb +0 -23
  41. data/lib/bas/domain/pto.rb +0 -69
  42. data/lib/bas/domain/work_items_limit.rb +0 -25
  43. data/lib/bas/formatter/base.rb +0 -53
  44. data/lib/bas/formatter/birthday.rb +0 -38
  45. data/lib/bas/formatter/exceptions/invalid_data.rb +0 -15
  46. data/lib/bas/formatter/notification.rb +0 -34
  47. data/lib/bas/formatter/pto.rb +0 -89
  48. data/lib/bas/formatter/support_emails.rb +0 -73
  49. data/lib/bas/formatter/types/response.rb +0 -16
  50. data/lib/bas/formatter/work_items_limit.rb +0 -68
  51. data/lib/bas/process/base.rb +0 -39
  52. data/lib/bas/process/discord/exceptions/invalid_webhook_token.rb +0 -16
  53. data/lib/bas/process/discord/implementation.rb +0 -71
  54. data/lib/bas/process/discord/types/response.rb +0 -22
  55. data/lib/bas/process/openai/base.rb +0 -72
  56. data/lib/bas/process/openai/helper.rb +0 -19
  57. data/lib/bas/process/openai/types/response.rb +0 -27
  58. data/lib/bas/process/openai/use_case/humanize_pto.rb +0 -53
  59. data/lib/bas/process/slack/exceptions/invalid_webhook_token.rb +0 -16
  60. data/lib/bas/process/slack/implementation.rb +0 -70
  61. data/lib/bas/process/slack/types/response.rb +0 -21
  62. data/lib/bas/process/types/response.rb +0 -16
  63. data/lib/bas/read/github/base.rb +0 -57
  64. data/lib/bas/read/github/types/response.rb +0 -27
  65. data/lib/bas/read/github/use_case/repo_issues.rb +0 -17
  66. data/lib/bas/read/imap/base.rb +0 -70
  67. data/lib/bas/read/imap/types/response.rb +0 -27
  68. data/lib/bas/read/imap/use_case/support_emails.rb +0 -26
  69. data/lib/bas/read/notion/base.rb +0 -52
  70. data/lib/bas/read/notion/exceptions/invalid_api_key.rb +0 -15
  71. data/lib/bas/read/notion/exceptions/invalid_database_id.rb +0 -15
  72. data/lib/bas/read/notion/helper.rb +0 -21
  73. data/lib/bas/read/notion/types/response.rb +0 -26
  74. data/lib/bas/read/notion/use_case/birthday_next_week.rb +0 -41
  75. data/lib/bas/read/notion/use_case/birthday_today.rb +0 -29
  76. data/lib/bas/read/notion/use_case/notification.rb +0 -28
  77. data/lib/bas/read/notion/use_case/pto_next_week.rb +0 -71
  78. data/lib/bas/read/notion/use_case/pto_today.rb +0 -30
  79. data/lib/bas/read/notion/use_case/work_items_limit.rb +0 -37
  80. data/lib/bas/read/postgres/base.rb +0 -46
  81. data/lib/bas/read/postgres/helper.rb +0 -16
  82. data/lib/bas/read/postgres/types/response.rb +0 -42
  83. data/lib/bas/read/postgres/use_case/pto_today.rb +0 -32
  84. data/lib/bas/serialize/base.rb +0 -30
  85. data/lib/bas/serialize/github/issues.rb +0 -57
  86. data/lib/bas/serialize/imap/support_emails.rb +0 -56
  87. data/lib/bas/serialize/notion/birthday_today.rb +0 -68
  88. data/lib/bas/serialize/notion/notification.rb +0 -56
  89. data/lib/bas/serialize/notion/pto_today.rb +0 -75
  90. data/lib/bas/serialize/notion/work_items_limit.rb +0 -65
  91. data/lib/bas/serialize/postgres/pto_today.rb +0 -47
  92. data/lib/bas/use_cases/types/config.rb +0 -20
  93. data/lib/bas/use_cases/use_case.rb +0 -42
  94. data/lib/bas/use_cases/use_cases.rb +0 -465
  95. data/lib/bas/write/logs/base.rb +0 -33
  96. data/lib/bas/write/logs/use_case/console_log.rb +0 -22
  97. data/lib/bas/write/notion/base.rb +0 -36
  98. data/lib/bas/write/notion/use_case/empty_notification.rb +0 -38
  99. data/lib/bas/write/notion/use_case/notification.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f5359b994975192b92fe8582bd37c23e9ce212bbe7d90bd9b93566421f88cd2
4
- data.tar.gz: 3d8a0d0f9a8ea9529f3538600a471d32506fa865d45c8e1621d1d3851b6a5682
3
+ metadata.gz: b15ef30bfbc91578e5b2ef5bf19861f9bda796d0296faa65ae80cd2244521655
4
+ data.tar.gz: 34775b96d78ee72c66a1d7501020d810030bbf6fc0396ec12e0aa39ed58d3735
5
5
  SHA512:
6
- metadata.gz: d88b0b00ca6da83a2168051a685d60bea43766ec677285e129ced8e48dc55660203b8c7994e347f3e043e898838ed6e3910f9e5271f78e22e219a035929f85f9
7
- data.tar.gz: 0a01df01e758f0b97931debeedc150045c1ba5f0978fe475ae775139fed7ed1d622cf66f17740f098ddfabf1d29a48511b83cffb67a97316b0dfeea51b2e5694
6
+ metadata.gz: 7f74a4e1963f29cd2f7cc5d5847d4215cd403281394711570d6c612771ed7036ada74941cd449f1c8c1faf82b7d6f33bb0941ce81b4c39884a240404637e6d6d
7
+ data.tar.gz: 82c7867b2b3af0a16f3ca4780c796bff3ed3ae3e27d5b6e31d91731d2dea7bc367ebd4ac9ad39e54b98c56ef3f686e9bdf5411e54c8dd7fe6359356ca7809e48
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ # 1.0.0 (17.05.2024)
4
+ - [Refactor Project Architecture](https://github.com/kommitters/bas/issues/35)
5
+ - [Refactor PTO today Use Case](https://github.com/kommitters/bas/issues/29)
6
+ - [Refactor PTO next week Use Case](https://github.com/kommitters/bas/issues/30)
7
+ - [Refactor Birthday today Use Case](https://github.com/kommitters/bas/issues/31)
8
+ - [Refactor Birthday next week Use Case](https://github.com/kommitters/bas/issues/32)
9
+ - [Refactor WIP limit Use Case](https://github.com/kommitters/bas/issues/33)
10
+ - [Refactor support email Use Case](https://github.com/kommitters/bas/issues/34)
11
+ - [Add a garbage collector](https://github.com/kommitters/bas/issues/42)
12
+ - [Update read Postgres component](https://github.com/kommitters/bas/issues/45)
13
+ - [Update new table structure](https://github.com/kommitters/bas/issues/46)
14
+
3
15
  # 0.4.0 (19.04.2024)
4
16
  - [[BAS] Add process for OpenAI](https://github.com/kommitters/bas/issues/25)
5
17
 
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # BAS - Business Automation System
2
2
 
3
- Many organizations and individuals rely on automation across various contexts in their daily operations. With BAS, we aim to provide an open-source platform that empowers users to create customized automation systems tailored to their unique requirements. BAS consists of a series of abstract components designed to facilitate the creation of diverse use cases, regardless of context.
3
+ Many organizations and individuals rely on automation across various contexts in their daily operations. With BAS, we aim to provide an open-source platform that empowers users to create customized automation systems tailored to their unique requirements. BAS consists of a series of abstract components designed to facilitate the creation of diverse bots, regardless of context.
4
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.
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 bots to their advantage.
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)
@@ -26,59 +26,38 @@ If bundler is not being used to manage dependencies, install the gem by executin
26
26
 
27
27
  * Ruby 2.6.0 or higher
28
28
 
29
- ## Building my own use case
29
+ ## BOT
30
+ A bot is a tool in charge of executing a specific automation task. The pipeline of a bot consists of reading from a data source, processing a specific task, and then writing a result in storage.
30
31
 
31
- The gem provides with basic interfaces, types, and methods to shape your own use cases in an easy way.
32
+ ## Use case
33
+ A use case is an automation problem greater than the one managed by a single bot. A use case comprises a set of bots that are agnostic between them and each one solves a specific task (parts of the automation problem). To connect the bots, a shared storage should be used. This shared storage could be a PostgresDB database, an S3 bucket, or any kind of storage where the bots will read and write data so another bot can use it to execute their tasks.
32
34
 
33
- There are 7 currently implemented use cases:
34
- * Birthday notifications - from Notion to Discord
35
- * Next Week Birthday notifications - from Notion to Discord
36
- * PTO notifications - from Notion to Discord
37
- * Next Week PTO notifications - from Notion to Discord
38
- * PTO notifications - from Postgres to Slack
39
- * WIP limit exceeded - from Notion to Discord
40
- * Support email notification - from IMAP to Discord
35
+ For example, a system to notify birthdays in a company (automation problem) can be solved with three bots: one to fetch the data from an external data source, one to format the birthday message, and the last one to notify somewhere.
41
36
 
42
- For this example we'll analyze the birthday notification use case, bringing data from a notion database, and sending the
43
- notifications to a Discord channel.
37
+ ## Building my own BOT
44
38
 
45
- A *Use Case* object, consists on 5 main components, having it's own responsibility:
39
+ The gem provides with basic interfaces, types, and methods to shape your own bot in an easy way. Since the process of reading and writing in a shared storage is separated from the main task, two base classes were defined to deal with this executions, leaving the logic of the specific task in the bot file.
46
40
 
47
- ### 1. Read - Obtaining the data
41
+ ### 1. Read - Obtaining the data from the Shared Storage
48
42
 
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.
43
+ Specifically, a reader is an object in charged of bringing data from a shared storage. The gem already provides the base interface
44
+ for building your own reader for your specific shared storage, or rely on already built classes if they match your purpose.
51
45
 
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
- 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/read/notion/use_case/birthday_today.rb`. It implements specific logic for reading the data and validating the response.
46
+ The base interface for a reader can be found under the `bas/read/base.rb` class.
55
47
 
56
- ### 2. Serialize - Shaping it
48
+ ### 2. Write - Apply changes in a shared storage
49
+ The **Write** is in charge of creating or updating information in a shared storage. This is the last step for every BOT. These changes can be a transaction in a database, adding files in a cloud storage, or simply creating logs.
57
50
 
58
- The **Serializer** responsibility, is to shape the data using custom types from the app domain, bringing it into a
59
- common structure understandable for other components, specifically the **Formatter**.
51
+ The base interface for a writer can be found under the `bas/write/base.rb` class.
60
52
 
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`
53
+ ### 3. Bot - Solve a specific automation task
54
+ The bot execute the logic to solve an specific task. For this, it can use the data from the read step, and then returns a processed response to be wrote by the write component. Every bot reads from a shared storage and writes in a shared storage.
63
55
 
64
- ### 3. Formatter - Preparing the message
65
-
66
- The **Formatter**, is in charge of preparing the message to be sent in our notification, and give it the right format with the right data.
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
- implementation can be found under `/bas/formatter/birthday.rb`.
69
-
70
- ### 4. Process - Optional Data Process
71
-
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
- 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/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`
56
+ The base interface for a bot can be found under the `bas/bot/base.rb` class.
78
57
 
79
58
  ## Examples
80
59
 
81
- In this example, we demonstrate how to instantiate a birthday notification use case and execute it in a basic Ruby project. We'll also cover its deployment in a serverless configuration, specifically using a simple Lambda deployment.
60
+ In this example, we demonstrate how to instantiate a birthday notification bot and execute it in a basic Ruby project. We'll also cover its deployment in a serverless configuration, specifically using a simple Lambda deployment.
82
61
 
83
62
  ### Preparing the configurations
84
63
 
@@ -94,74 +73,37 @@ With the following formula for the **BD_this_year** column: `dateAdd(prop("BD"),
94
73
 
95
74
  * A Notion secret, which can be obtained, by creating an integration here: `https://developers.notion.com/`, browsing on the **View my integrations** option, and selecting the **New Integration** or **Create new integration** buttons.
96
75
 
97
- * A webhook key, which can be generated directly on discord on the desired channel, following this instructions: `https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks`
98
-
99
- * A filter, to determine which data to bring from the database, for this specific case, the filter we used is:
100
-
101
- ```
102
- #### file.rb ####
103
- today = Date.now
104
- {
105
- "filter": {
106
- "or": [
107
- {
108
- "property": "BD_this_year",
109
- "date": {
110
- "equals": today
111
- }
112
- }
113
- ]
114
- },
115
- "sorts": []
116
- }
117
- ```
118
-
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:
120
-
121
- `"NAME, Wishing you a very happy birthday! Enjoy your special day! :birthday: :gift:"`
122
-
123
- ### Instantiating the Use Case
76
+ ### Instantiating the FetchBirthdayFromNotion Bot
124
77
 
125
- To instantiate the use case, the `UseCases` module should provide a method for this purpose, for this specific case, the `notify_birthday_from_notion_to_discord`
126
- is the desired one.
78
+ The specific bot can be found in the `bas/bot/fetch_birthdays_from_notion.rb` file.
127
79
 
128
80
  **Normal ruby code**
129
81
  ```
130
- filter = {
131
- "filter": {
132
- "or": [
133
- {
134
- "property": "BD_this_year",
135
- "date": {
136
- "equals": today
137
- }
138
- }
139
- ]
140
- },
141
- "sorts": []
142
- }
143
-
144
82
  options = {
145
- read_options: {
146
- base_url: "https://api.notion.com",
147
- database_id: NOTION_DATABASE_ID,
148
- secret: NOTION_API_INTEGRATION_SECRET,
149
- filter: filter
83
+ process_options: {
84
+ database_id: "notion database id",
85
+ secret: "notion secret"
86
+ },
87
+ write_options: {
88
+ connection: {
89
+ host: "host",
90
+ port: 5432,
91
+ dbname: "bas",
92
+ user: "postgres",
93
+ password: "postgres"
150
94
  },
151
- process_options: {
152
- webhook: "https://discord.com/api/webhooks/1199213527672565760/KmpoIzBet9xYG16oFh8W1RWHbpIqT7UtTBRrhfLcvWZdNiVZCTM-gpil2Qoy4eYEgpdf",
153
- name: "Birthday Bot"
154
- }
95
+ db_table: "use_cases",
96
+ bot_name: "FetchBirthdaysFromNotion"
97
+ }
155
98
  }
156
99
 
157
- use_case = UseCases.notify_birthday_from_notion_to_discord(options)
158
-
159
- use_case.perform
100
+ bot = Bot::FetchBirthdaysFromNotion.new(options)
101
+ bot.execute
160
102
 
161
103
  ```
162
104
 
163
105
  ### Serverless
164
- We'll explain how to configure and deploy a use case with serverless, this example will cover the PTO's notifications use case.
106
+ We'll explain how to configure and deploy a bot with serverless.
165
107
 
166
108
  #### Configuring environment variables
167
109
  Create the environment variables configuration file.
@@ -173,15 +115,11 @@ cp env.yml.example env.yml
173
115
  And put the following env variables
174
116
  ```
175
117
  dev:
176
- NOTION_DATABASE_ID: NOTION_DATABASE_ID
177
- NOTION_SECRET: NOTION_SECRET
178
- DISCORD_WEBHOOK: DISCORD_WEBHOOK
179
- DISCORD_BOT_NAME: DISCORD_BOT_NAME
118
+ BIRTHDAY_NOTION_DATABASE_ID: "BIRTHDAY_NOTION_DATABASE_ID"
119
+ BIRTHDAY_NOTION_SECRET: "BIRTHDAY_NOTION_SECRET"
180
120
  prod:
181
- NOTION_DATABASE_ID: NOTION_DATABASE_ID
182
- NOTION_SECRET: NOTION_SECRET
183
- DISCORD_WEBHOOK: DISCORD_WEBHOOK
184
- DISCORD_BOT_NAME: DISCORD_BOT_NAME
121
+ BIRTHDAY_NOTION_DATABASE_ID: "BIRTHDAY_NOTION_DATABASE_ID"
122
+ BIRTHDAY_NOTION_SECRET: "BIRTHDAY_NOTION_SECRET"
185
123
 
186
124
  ```
187
125
 
@@ -225,60 +163,45 @@ On your serverless configuration, create your lambda function, on your serverles
225
163
  ```ruby
226
164
  # frozen_string_literal: true
227
165
 
228
- require 'bas'
166
+ require 'bas/bot/fetch_birthdays_from_notion'
229
167
 
230
168
  # Initialize the environment variables
231
- NOTION_BASE_URL = 'https://api.notion.com'
232
- NOTION_DATABASE_ID = ENV.fetch('PTO_NOTION_DATABASE_ID')
233
- NOTION_SECRET = ENV.fetch('PTO_NOTION_SECRET')
234
- DISCORD_WEBHOOK = ENV.fetch('PTO_DISCORD_WEBHOOK')
235
- DISCORD_BOT_NAME = ENV.fetch('PTO_DISCORD_BOT_NAME')
169
+ NOTION_DATABASE_ID = ENV.fetch('NOTION_DATABASE_ID')
170
+ NOTION_SECRET = ENV.fetch('NOTION_SECRET')
236
171
 
237
172
  module Notifier
238
173
  # Service description
239
174
  class UseCaseName
240
175
  def self.notify(*)
241
- options = { read_options:, process_options: }
176
+ options = { process_options: , write_options: }
242
177
 
243
178
  begin
244
- use_case = UseCases.use_case_build_function(options)
179
+ use_case = Bot::FetchBirthdaysFromNotion.new(options)
245
180
 
246
- use_case.perform
181
+ use_case.execute
247
182
  rescue StandardError => e
248
183
  { body: { message: e.message } }
249
184
  end
250
185
  end
251
186
 
252
- def self.read_options
253
- {
254
- base_url: NOTION_BASE_URL,
255
- database_id: NOTION_DATABASE_ID,
256
- secret: NOTION_SECRET,
257
- filter: {
258
- filter: { "and": read_filter }
259
- }
260
- }
261
- end
262
-
263
- def self.read_filter
264
- today = Common::TimeZone.set_colombia_time_zone.strftime('%F').to_s
265
-
266
- [
267
- { property: 'Desde?', date: { on_or_before: today } },
268
- { property: 'Hasta?', date: { on_or_after: today } }
269
- ]
270
- end
271
-
272
187
  def self.process_options
273
188
  {
274
- webhook: DISCORD_WEBHOOK,
275
- name: DISCORD_BOT_NAME
189
+ database_id: NOTION_DATABASE_ID,
190
+ secret: NOTION_SECRET
276
191
  }
277
192
  end
278
193
 
279
- def self.format_options
194
+ def self.write_options
280
195
  {
281
- template: ':beach: individual_name is on PTO'
196
+ connection: {
197
+ host: "host",
198
+ port: 5432,
199
+ dbname: "bas",
200
+ user: "postgres",
201
+ password: "postgres"
202
+ },
203
+ db_table: "use_cases",
204
+ bot_name: "FetchBirthdaysFromNotion"
282
205
  }
283
206
  end
284
207
  end
@@ -290,18 +213,16 @@ In the `serverless.yml` file, add a new instance in the `functions` block with t
290
213
 
291
214
  ```bash
292
215
  functions:
293
- ptoNotification:
294
- handler: src/lambdas/pto_notification.Notifier::PTO.notify
216
+ fetchBirthdayFromNotion:
217
+ handler: src/lambdas/birthday_fetch.Bot::Birthday.fetch
295
218
  environment:
296
- PTO_NOTION_DATABASE_ID: ${file(./env.yml):${env:STAGE}.PTO_NOTION_DATABASE_ID}
297
- PTO_NOTION_SECRET: ${file(./env.yml):${env:STAGE}.PTO_NOTION_SECRET}
298
- PTO_DISCORD_WEBHOOK: ${file(./env.yml):${env:STAGE}.PTO_DISCORD_WEBHOOK}
299
- PTO_DISCORD_BOT_NAME: ${file(./env.yml):${env:STAGE}.DISCORD_BOT_NAME}
219
+ BIRTHDAY_NOTION_DATABASE_ID: ${file(./env.yml):${env:STAGE}.BIRTHDAY_NOTION_DATABASE_ID}
220
+ BIRTHDAY_NOTION_SECRET: ${file(./env.yml):${env:STAGE}.BIRTHDAY_NOTION_SECRET}
300
221
  events:
301
222
  - schedule:
302
- name: pto-daily-notification
303
- description: "Notify every 24 hours at 8:30 a.m (UTC-5) from monday to friday"
304
- rate: cron(${file(./env.yml):${env:STAGE}.PTO_SCHEDULER})
223
+ name: birthday-fetch
224
+ description: "Fetch every 24 hours at 8:30 a.m (UTC-5) from monday to friday"
225
+ rate: cron(${file(./env.yml):${env:STAGE}.BIRTHDAY_FETCH_SCHEDULER})
305
226
  enabled: true
306
227
  ```
307
228
 
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../utils/exceptions/function_not_implemented"
4
+ require_relative "../utils/exceptions/invalid_process_response"
5
+ require_relative "../write/postgres_update"
6
+
7
+ module Bot
8
+ ##
9
+ # The Bot::Base class serves as the foundation for implementing specific bots. Operating
10
+ # as an interface, this class defines essential attributes and methods, providing a blueprint
11
+ # for creating custom bots formed by a Read, Process, and Write components.
12
+ #
13
+ class Base
14
+ attr_reader :read_options, :process_options, :write_options
15
+ attr_accessor :read_response, :process_response, :write_response
16
+
17
+ def initialize(config)
18
+ @read_options = config[:read_options] || {}
19
+ @process_options = config[:process_options] || {}
20
+ @write_options = config[:write_options] || {}
21
+ end
22
+
23
+ def execute
24
+ @read_response = read
25
+
26
+ write_read_response_in_process
27
+
28
+ @process_response = process
29
+ raise Utils::Exceptions::InvalidProcessResponse unless process_response.is_a?(Hash)
30
+
31
+ write_read_response_processed
32
+
33
+ @write_response = write
34
+ end
35
+
36
+ protected
37
+
38
+ def read
39
+ raise Utils::Exceptions::FunctionNotImplemented
40
+ end
41
+
42
+ def process
43
+ raise Utils::Exceptions::FunctionNotImplemented
44
+ end
45
+
46
+ def write
47
+ raise Utils::Exceptions::FunctionNotImplemented
48
+ end
49
+
50
+ def unprocessable_response
51
+ read_data = read_response.data
52
+
53
+ read_data.nil? || read_data == {} || read_data.any? { |_key, value| [[], ""].include?(value) }
54
+ end
55
+
56
+ private
57
+
58
+ def write_read_response_in_process
59
+ return if read_options[:avoid_process].eql?(true) || read_response.id.nil?
60
+
61
+ options = { params: { stage: "in process" }, conditions: "id=#{read_response.id}" }
62
+
63
+ Write::PostgresUpdate.new(read_options.merge(options)).execute
64
+ end
65
+
66
+ def write_read_response_processed
67
+ return if read_options[:avoid_process].eql?(true) || read_response.id.nil?
68
+
69
+ options = { params: { stage: "processed" }, conditions: "id=#{read_response.id}" }
70
+
71
+ Write::PostgresUpdate.new(read_options.merge(options)).execute
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../write/postgres"
6
+
7
+ module Bot
8
+ ##
9
+ # The Bot::CompareWipLimitCount class serves as a bot implementation to read domains wip limits and
10
+ # counts from a PostgresDB database, compare the values to find exceeded counts, and write them on
11
+ # a PostgresDB table with a specific format.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # options = {
17
+ # read_options: {
18
+ # connection: {
19
+ # host: "localhost",
20
+ # port: 5432,
21
+ # dbname: "bas",
22
+ # user: "postgres",
23
+ # password: "postgres"
24
+ # },
25
+ # db_table: "use_cases",
26
+ # tag: "FetchDomainsWipLimitFromNotion"
27
+ # },
28
+ # write_options: {
29
+ # connection: {
30
+ # host: "localhost",
31
+ # port: 5432,
32
+ # dbname: "bas",
33
+ # user: "postgres",
34
+ # password: "postgres"
35
+ # },
36
+ # db_table: "use_cases",
37
+ # tag: "CompareWipLimitCount"
38
+ # }
39
+ # }
40
+ #
41
+ # bot = Bot::CompareWipLimitCount.new(options)
42
+ # bot.execute
43
+ #
44
+ class CompareWipLimitCount < Bot::Base
45
+ # read function to execute the PostgresDB Read component
46
+ #
47
+ def read
48
+ reader = Read::Postgres.new(read_options.merge(conditions))
49
+
50
+ reader.execute
51
+ end
52
+
53
+ # Process function to compare the domains wip counts and limits
54
+ #
55
+ def process
56
+ return { success: { exceeded_domain_count: {} } } if unprocessable_response
57
+
58
+ domains_limits = read_response.data["domains_limits"]
59
+ domain_wip_count = read_response.data["domain_wip_count"]
60
+
61
+ exceeded_domain_count = exceedded_counts(domains_limits, domain_wip_count)
62
+
63
+ { success: { exceeded_domain_count: } }
64
+ end
65
+
66
+ # Write function to execute the PostgresDB write component
67
+ #
68
+ def write
69
+ write = Write::Postgres.new(write_options, process_response)
70
+
71
+ write.execute
72
+ end
73
+
74
+ private
75
+
76
+ def conditions
77
+ {
78
+ where: "archived=$1 AND tag=$2 AND stage=$3 ORDER BY inserted_at ASC",
79
+ params: [false, read_options[:tag], "unprocessed"]
80
+ }
81
+ end
82
+
83
+ def exceedded_counts(limits, counts)
84
+ counts.to_a.map do |domain_wip_count|
85
+ domain, count = domain_wip_count
86
+ domain_limit = limits[domain]
87
+
88
+ { domain:, exceeded: count - domain_limit } if count > domain_limit
89
+ end.compact
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base"
4
+ require_relative "../read/postgres"
5
+ require_relative "../utils/notion/request"
6
+ require_relative "../write/postgres"
7
+
8
+ module Bot
9
+ ##
10
+ # The Bot::FetchBirthdaysFromNotion class serves as a bot implementation to read birthdays from a
11
+ # notion database and write them on a PostgresDB table with a specific format.
12
+ #
13
+ # <br>
14
+ # <b>Example</b>
15
+ #
16
+ # options = {
17
+ # process_options: {
18
+ # database_id: "notion database id",
19
+ # secret: "notion secret"
20
+ # },
21
+ # write_options: {
22
+ # connection: {
23
+ # host: "host",
24
+ # port: 5432,
25
+ # dbname: "bas",
26
+ # user: "postgres",
27
+ # password: "postgres"
28
+ # },
29
+ # db_table: "use_cases",
30
+ # tag: "FetchBirthdaysFromNotion"
31
+ # }
32
+ # }
33
+ #
34
+ # bot = Bot::FetchBirthdaysFromNotion.new(options)
35
+ # bot.execute
36
+ #
37
+ class FetchBirthdaysFromNotion < Bot::Base
38
+ # read function to execute the PostgresDB Read component
39
+ #
40
+ def read
41
+ reader = Read::Postgres.new(read_options.merge(conditions))
42
+
43
+ reader.execute
44
+ end
45
+
46
+ # Process function to execute the Notion utility to fetch birthdays from a notion database
47
+ #
48
+ def process
49
+ response = Utils::Notion::Request.execute(params)
50
+
51
+ if response.code == 200
52
+ birthdays_list = normalize_response(response.parsed_response["results"])
53
+
54
+ { success: { birthdays: birthdays_list } }
55
+ else
56
+ { error: { message: response.parsed_response, status_code: response.code } }
57
+ end
58
+ end
59
+
60
+ # Write function to execute the PostgresDB write component
61
+ #
62
+ def write
63
+ write = Write::Postgres.new(write_options, process_response)
64
+
65
+ write.execute
66
+ end
67
+
68
+ private
69
+
70
+ def conditions
71
+ {
72
+ where: "archived=$1 AND tag=$2 ORDER BY inserted_at DESC",
73
+ params: [false, read_options[:tag]]
74
+ }
75
+ end
76
+
77
+ def params
78
+ {
79
+ endpoint: "databases/#{process_options[:database_id]}/query",
80
+ secret: process_options[:secret],
81
+ method: "post",
82
+ body:
83
+ }
84
+ end
85
+
86
+ def body
87
+ today = Time.now.utc.strftime("%F").to_s
88
+
89
+ {
90
+ filter: {
91
+ and: [{ property: "BD_this_year", date: { equals: today } }] + last_edited_condition
92
+ }
93
+ }
94
+ end
95
+
96
+ def last_edited_condition
97
+ return [] if read_response.inserted_at.nil?
98
+
99
+ [
100
+ {
101
+ timestamp: "last_edited_time",
102
+ last_edited_time: { on_or_after: read_response.inserted_at }
103
+ }
104
+ ]
105
+ end
106
+
107
+ def normalize_response(results)
108
+ return [] if results.nil?
109
+
110
+ results.map do |value|
111
+ birthday_fields = value["properties"]
112
+
113
+ {
114
+ "name" => extract_rich_text_field_value(birthday_fields["Complete Name"]),
115
+ "birthday_date" => extract_date_field_value(birthday_fields["BD_this_year"])
116
+ }
117
+ end
118
+ end
119
+
120
+ def extract_rich_text_field_value(data)
121
+ data["rich_text"][0]["plain_text"]
122
+ end
123
+
124
+ def extract_date_field_value(data)
125
+ data["formula"]["date"]["start"]
126
+ end
127
+ end
128
+ end