English Version - Portuguese Version
ReminderFriendly is an application designed to create friendly and humorous reminders. In this system, you can add a reminder using natural language and associate it with a fictional character. Your reminder will be sent to you on the correct date, with a message delivered in the humor and personality of the chosen character!
This project is part of a technical assessment for a selection process at Kevi - Retenção de Clientes. The goal is to develop this application using PHP and the Slim Framework for the Back-End and Vue.js for the Front-End, utilizing the OpenAI API for natural language processing of the reminder and the Z-API to send the reminders via WhatsApp.
First, clone the repository:
git clone https://github.com/Dujuniorrr/kevi-test.git
cd kevi-test
To properly configure the system, you need to add the API data to the backend/.env.example
file, including the following required data:
OPENAI_API_KEY=key
Z_API_TOKEN=token
Z_API_INSTANCE=instance
Z_API_CLIENT_TOKEN=client_token
PHONE=5577999999999
The project is divided into a SPA for the User Interface, a REST API for handling data, and a database management system (DBMS) for data persistence.
To make the application portable and create a consistent development environment for anyone who wants to run this application, Docker is used to create containers.
To start the containers, you need to install Docker. Once installed, run the following command at the project root:
docker-compose up --build -d
This command will build the images according to the instructions in Dockerfile-Backend
and Dockerfile-Frontend
and start the service containers as configured in docker-compose.yml
. The back-end service will run on port 9000, the front-end service on port 8000, and the MySQL DBMS on port 4000.
To create the database tables, access the MySQL DBMS container and use the database, then run the script found in backend/database.sql
in the container's bash:
docker exec -ti db bash
mysql -u kevi -p #password: kevi
use kevi;
If you want to test the API using Insomnia
, you can import the file found in docs/Insomnia-api.json
into a new collection.
The back-end of the application is developed in PHP
, using the Slim Framework
to manage API routes and depending on the MySQL
DBMS for data persistence. Additionally, it uses PHPUnit
for testing. The code design follows the principles of hexagonal architecture, where the system is divided into independent layers using the concept of ports and adapters.
This approach aims to keep the internal layers of the application decoupled from specific drivers (such as user interfaces, API endpoints, tests, and queues) and from external resources (such as databases, external APIs, and libraries).
In the context of hexagonal architecture, the more internal layers, which contain the core business and domain rules of the application, operate through interfaces or ports that define clear contracts. The concrete implementations of these interfaces are then adapted to different external resources, following these contracts. This allows the most critical and central classes of the application to remain independent, promoting modularity and simplified maintenance of the system as a whole, due to low coupling and enabling the creation of unit tests by using fakes and mocks that adhere to the existing interfaces.
The front-end of the application uses Vue.js
as the framework for developing the system interface, with business logic written in TypeScript
and testing done with Jest
. Similar to the back-end, the code design follows the principles of hexagonal architecture, where the system is divided into independent layers using the concept of ports and adapters.
This approach aims to keep the internal layers of the application decoupled, particularly from Vue components and external resources (such as APIs and libraries). This design choice was made for the same reasons as in the back-end. It has made the application more flexible and testable, without coupling business rules to components and reducing complexity in the presentation layer.
For a more detailed view of the endpoints, visit the Swagger Documentation.
To create a reminder, access this endpoint via POST method. The route directs the parameters and request body to the CreateReminderController
, which triggers the CreateReminderValidator
to check if the request is valid. If valid, the controller formats the data according to the input DTO of the CreateReminder
command. This command uses the DatabaseCharacterRepository
to check if the requested character exists. If not, an error is returned; otherwise, the Character
is retrieved.
Next, the natural language processing gateway OpenAPINLPGateway
receives the content from the request body along with the character to create a fun reminder using its API via GuzzleHTTPClient
. If the content is not understood, an exception is thrown. If understood successfully, the Reminder
is instantiated, and the entity's data is persisted by the DatabaseReminderRepository
in the database. Finally, an output DTO containing the data of this new reminder is returned as a response.
To send a reminder, access this endpoint via PUT method. The route directs the parameters and request body to the SendReminderController
, which triggers the SendReminder
command. This command uses the DatabaseReminderRepository
to check if the requested entity exists. If not, an error is returned; otherwise, the Reminder
is retrieved.
The reminder then undergoes a mutation process where its send status property must be changed to true
. If it already has this value, it has already been sent, so an exception is thrown.
If the mutation is successful, the message sending gateway ZAPIMessageSenderGateway
sends the processed reminder with the character's humor. If everything goes well, the DatabaseReminderRepository
updates the reminder's status to sent in the database. Finally, a success message is returned as a response.
To delete a reminder, access this endpoint via DELETE method. The route directs the parameters and request body to the DeleteReminderController
, which triggers the DeleteReminder
command, sending the ID of the reminder to be deleted. This command uses the DatabaseReminderRepository
to check if the requested entity exists. If not, an error is returned; otherwise, the Reminder
is retrieved.
Finally, the DatabaseReminderRepository
removes the entity from the database. A success message is returned as a response.
To list reminders, access this endpoint via GET method. The route directs the parameters and request body to the ListRemindersController
, which triggers the ListReminders
command, sending an input DTO with pagination and filter data. This command uses the DatabaseReminderRepository
to list reminders based on the input DTO. An array of Reminder
objects is returned and converted to an output DTO, which the controller returns as JSON.
To list characters, access this endpoint via GET method. The route directs the parameters and request body to the ListCharactersController
, which triggers the ListCharacters
command, sending an input DTO with pagination data. This command uses the DatabaseCharacterRepository
to list characters based on the input DTO. An array of Character
objects is returned and converted to an output DTO, which the controller returns as JSON.
To list reminders by month, access this endpoint via GET method. The route directs the parameters and request body to the ListRemindersByMonthController
, which triggers the ListRemindersByMonth
command, sending the month and year as input data. This command uses the DatabaseReminderRepository
to list reminders based on the input DTO. An array of Reminder
objects is returned and converted to an output DTO, which the controller returns as JSON.
Unit and integration tests were performed with good coverage, and they were easy to implement due to the code design followed. 54 tests were conducted, with 1137 assertions and a 100% success rate.
Following Vue's best practices, the interface is divided into components, each with its responsibility and independence from others, aiming for low coupling. The system has a single View
, rendered when the client requests the base route of the system. The root component, App
, then renders the ManageRemindersView
.
A sibling component to this view, SplashOverlay
, is also rendered and, for a second and a half, displays on the screen showing the system's logo and a progress bar, giving the impression of project loading.
ManageReminderView
has five child components:
Calendar
: Displays a calendar showing the current month's reminders.ReminderList
: Lists reminders associated with the character chosen by the user, having a child componentReminderCard
.ReminderFilter
: Allows changing the number of reminders per page and filtering by sent or unsent.SimpleToast
: Provides visual feedback on actions for the user.AddReminderModal
: Responsible for adding new reminders, with the following child components:ReminderForm
: Allows adding the reminder text and viewing the character chosen by the user.CharacterCard
: A listing of characters to be selected.SimpleToast
: Provides visual feedback on actions for the user.
The class structure in the Front-End is less complex than in the Back-End, but the communication between classes still follows a well-defined interaction pattern, especially focusing on hexagonal architecture. In summary: commands communicate with gateways to perform operations, and gateways use the HTTP client to interact with the API. This structure promotes more modular, flexible, and testable code.
-
Commands and Gateways:
- CreateReminder, DeleteReminder, SendReminder, ListReminder, ListCharacters:
- Each of these command classes receives an instance of the respective gateway (
ReminderGateway
orCharacterGateway
) via dependency injection. - When the
execute
method is called on a command, it delegates the task to the corresponding gateway, passing the necessary parameters. - Example:
CreateReminder
callscreate
onReminderGateway
to add a new reminder.
- Each of these command classes receives an instance of the respective gateway (
- CreateReminder, DeleteReminder, SendReminder, ListReminder, ListCharacters:
-
Gateways and HTTP Client:
- ReminderGateway and APICharacterGateway:
- These gateways use
AxiosHttpClient
to make HTTP calls to the API. - When a method like
list
,send
,delete
,create
, orlist
is called on the gateway, it builds the HTTP request and usesAxiosHttpClient
to send it. - Example:
ReminderGateway
callspost
onAxiosHttpClient
to send a new reminder to the API.
- These gateways use
- ReminderGateway and APICharacterGateway:
-
HTTP Client and API:
- AxiosHttpClient:
- This class is responsible for performing the actual HTTP requests.
- It provides methods such as
get
,post
,delete
,put
, which are used by gateways to interact with the API. - Example:
AxiosHttpClient.post
sends aPOST
request to the API with the new reminder's data.
- AxiosHttpClient:
The system's presentation was developed to provide a sense of comfort and fun. To achieve this result, the use of various colors and components inspired by gamified apps was essential.
Unit and integration tests were performed for the TypeScript classes with good coverage, and they were easy to implement due to the design followed. 8 test suites were created, with 35 tests having assertions and a 100% success rate. It is worth noting that component tests were done manually, and automated tests could be implemented in the future to ensure better quality.
To further enhance the ReminderFriendly application, some of the following features and improvements could be considered:
-
Calendar Integration:
- Integrate with calendar services like Google Calendar and Outlook so that reminders can be automatically synchronized with users' calendars.
-
Multilingual Support:
- Add support for multiple languages to reach a broader audience.
-
Automated Interface Testing:
- Implement automated tests for interface components using tools like Cypress or Selenium and Vitest to ensure user interface stability.
In addition to the documentation present in this README, it is also possible to observe the issues and pull requests closed, which serve as a way to chronologically review the decisions made to achieve the final result.
Finally, I want to thank Kevi - Retenção de Clientes for the opportunity to participate in the selection process, which was very enjoyable and enriching for my professional growth.