Tecnologias usadas • Funcionalidades • Demonstrações • Guia de instalação • API Endpoints • Autores • Licença • Créditos • Contato
O Kairos é uma aplicação full-stack robusta para gerenciamento de eventos pessoais, construída com foco em segurança e integridade de dados. Ele possuí um sistema que minimiza a possibilidade de alterações indevidas em eventos. Vamos entender melhor isto?
Enquanto um evento não começou(ou seja, a data atual é anterior a data de início dele), todos os dados dele podem ser alterados. Quando o evento começa, a data de início não pode ser mais alterada e todos os outros dados podem ser alterados. Quando o evento termina, nenhum dado dele pode ser mais alterado. Além disto, eventos já finalizados não podem ser removidos. No caso, estas alterações e remoção não podem ocorrer pelo aplicativo web, mas, pela tela de administração continuam podendo ser alterados e removidos.
Por um lado, não existir estas limitações pode ser útil em contextos, por exemplo, que a data de início de eventos mudam de última hora frequentemente. Por outro lado, a falta destas limitações torna o histórico de eventos facilmente manipuláveis, o que será uma brecha grave de seguranças em cenários com a necessidade de histórico preciso de eventos. Portanto, este projeto foi desenvolvido justamente para usuários que que valorizam a segurança e a rastreabilidade de seus eventos.
Para finalizar, quero destacar que este projeto foi desenvolvido para demonstrar minha proeficiência em desenvolvimento full-stack, desde a arquitetura de banco de dados, lógica de backend, design de API, até a implementação do frontend responsivo e interativo.
Agora, você sabe o básico sobre este app. Quer conhecer mais sobre ele? Só ler o restante abaixo.

- Base: HTML5, CSS3, JavaScript (ES6+)
- Reatividade: Vue 3(Composition API com script setup) e Quasar 2
- Gerenciamento de estado: Pinia
- Gerenciamento de rotas: Vue-Router
- Calendário: FullCalendar
- CSS: Tailwind CSS
- Requisição HTTP: Axios
- Demais tecnologias: jwt-decode, Bootstrap Icons e SweetAlert2
- Lógica do servidor: Django
- API: Django REST Framework(API)
- Gerenciamento de sessão: Simple JWT
- Git com Conventional Commits e seguindo Git-Flow
Funcionalidades de autenticação:
- Criação de de conta: Processo intuitivo com verificação de e-mail no final para garantir que o novo usuário realmente tem acesso ao e-mail informado.
- Login: O usuário acessa o sistema por meio de e-mail e senha válidos e tem sua sessão mantida por meio de JWT.
- Logout: O usuário pode deslogar manualmente a qualquer momento, bem como ser deslogado automaticamente quando o refresh token não for mais válido.
Funcionalidades de gerenciamento de eventos:
- Criação de Eventos: Adição fácil de novos compromissos e atividades com duração variada (um ou múltiplos dias).
- Visualização em Calendário: Acompanhamento dos eventos de maneira clara e organizada utilizando a biblioteca FullCalendar.
- Edição de Eventos: Edição simples e rápida dos eventos, com restrições inteligentes para garantir a consistência dos dados:
- Não é possível alterar o período de início de um evento após seu início.
- Eventos já finalizados não podem ser editados.
- Remoção de Eventos: Possibilidade de excluir qualquer evento ainda não finalizado.
create-account.mp4
O código que o usuário passa para verificar seu e-mail é um OTP enviado para o respectivo e-mail após a conta ser criada com sucesso.
No momento, não existe restrição nos endpoints de gerenciamento de eventos para usuários sem e-mail verificado. Devido a isto, os usuários sem e-mail verificado não pode fazer login. O objetivo é que em futuras versões um usuário sem o e-mail verificado consiga fazer login e ver o calendário, porém, não consiga agendar nenhum evento.
email-verification.mp4
Conforme demonstra o vídeo abaixo, o login é feito a partir do e-mail e senha do usuário. Quando o login é efetuado com sucesso, a requisição de login, dentre outras informações, retorna os tokens JWT(access token e refresh token).
O access token é utilizado para manter a sessão do usuário ativa durante um período curto. O access token de duração curta foi escolhido para evitar que, em caso de descoberta do access token por outra pessoa, consiga evitar outra pessoa possa fazer requisições com o access token roubado por muito tempo.
Esta abordagem de segurança exige o usuário fazer o login muito frenquentemente. Para evitar este efeito colateral, é utilizado o refresh token para criar um novo access token sempre que a validade do atual acabar. Como o refresh token tem um tempo grande de validade, demora muito tempo até o usuário precisar fazer o login novamente.
Em futuras versões, será aplicada a estratégia de Refresh Token Rotation para tornar o gerenciamento de sessão ainda mais seguro.
login.mp4
Conforme demonstrado no vídeo abaixo, se o usuário estiver deslogado, ele é redirecionado para tela de login sempre que tentar acessar a home.
logout.mp4
Conforme demonstrado no vídeo abaixo, a criação começa clicando na célula do calendário que é respectiva ao dia inicial do evento, podendo o evento durar tanto 1 dia quanto múltiplos dias(o vídeo abaixo só mostra o caso em que o evento dura múltiplos dias). Além disto, os eventos são associados ao id do usuário que os criou. Em futuras versões, será possível convidar outros usuários para participar do evento.
Também conforme mostrado no vídeo abaixo, quando o evento é criado, um "link" para o respectivo evento aparece no calendário conforme a duração dele e a abertura dele é feita clicando nesse "link".
Falando mais sobre a visualização, vale destacar como o carregamento dos eventos no calendário ocorre. Um primeiro filtro para o carregamento é os eventos serem do usuário logado, o que é indispensável para manter a privacidade da lista de eventos de cada usuário. O segundo filtro é o período de tempo da view do calendário para o qual os eventos serão carregados. Por exemplo, os eventos da view semanal só irá crregar os eventos que a duração incluí os dias da respectiva semana. Este segundo filtro tem como objetivo otimizar o tamanho das requisições, pois será muito pesado ter que carregar todos os eventos do usuário sempre que ele mudar de view. Principalmente, no caso de usuários com muito eventos.
create-event.mp4
edit-event.mp4
delete-event.mp4
O objetivo do Kairos é ser um software de agendamento de eventos pessoais. Considerando isto, as seguintes regras são necessárias:
- Eventos não podem ser agendados para uma data anterior a atual.
- O período inicial não pode ser posterior ao período final.
Elas são aplicadas tanto nas requisições de criação e edição de eventos quanto no frontend referente a estas operações.
Além disto, as requisições de criação e edição de eventos quanto o respectivo frontend foram desenvolvidos para não permitir que eventos sejam definidos para períodos que entram em conflito com outros eventos. Isto incluí não permitir que eventos sejam definidos para o exato mesmo período de outros, nem permitir que eventos sejam definidos para um período que tem intersecção com o período de outros eventos. Isto foi criado para evitar conflitos na agenda dos usuários.
Existe a possibilidade de que um usuário precise/queira realizar outros eventos entre o período de algum evento. Por exemplo, ficará 4 dias em um Congresso e precisa fazer uma reunião entre este período. As regras atuais do Kairos não permitiriam haver esses dois eventos na agenda do usuário. Então, em versões futuras, será adicionada a opção do usuário informar se ele permite intersecções de período com o respectivo evento.
Finalizando, o Kairos possuí regras relacionadas a até quando eventos podem ser editados e removidos:
- Enquanto um evento não começou(ou seja, a data atual é anterior a data de início dele), todos os dados dele podem ser alterados.
- Quando o evento começa, a data de início não pode ser mais alterada e todos os outros dados podem ser alterados.
- Quando o evento termina, nenhum dado dele pode ser mais alterado.
- Eventos já finalizados não podem ser removidos.
No caso, estas alterações e remoção não podem ocorrer pelo aplicativo web, mas, pela tela de administração continuam podendo ser alterados e removidos.
Por um lado, não existir estas limitações pode ser útil em contextos, por exemplo, que a data de início de eventos mudam de última hora frequentemente. Por outro lado, a falta destas limitações torna o histórico de eventos facilmente manipuláveis, o que será uma brecha grave de seguranças em cenários com a necessidade de histórico preciso de eventos. Portanto, este projeto foi desenvolvido justamente para usuários que que valorizam a segurança e a rastreabilidade de seus eventos.
Esta é toda a lógica de gerenciamento de eventos neste app. Agora, você pode testar o Kairos para ver esta lógica na prática. Inclusive, abaixo segue o tutorial de como executar ele.
Você precisa ter instalado na sua máquina as seguintes tecnologias:
- Node
- Python
*Obs.: Desenvolvi este projeto com Node 20.x e Python 3.12.x. Não testei o projeto em versões anteriores destas tecnologias.
git clone git@github.com:EmanuelLacerda/kairos-webapp.git
cd kairos-webapp
Neste repositório, há 2 diretórios, sendo um deles a api(server/) e o outro o frontend(web/). Cada repositório possuí dependências próprias. Então, você precisa instalar as dependências individualmente. Abaixo segue como fazer para cada um dos dois.
1° Acesse o diretório "server":
cd server
2° Execute o "pip3 install -r requirements.txt":
pip3 install -r requirements.txt
3° Volte para a raiz do projeto:
cd ..
4° Acesse o diretório "web":
cd web
5° Execute o comando "npm install":
npm install
6° Volte para a raiz do projeto:
cd ..
SECRET_KEY=
DEBUG=
DB_NAME=
DB_USER=
DB_PASSWORD=
DB_HOST=
DB_PORT=
EMAIL_HOST=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
EMAIL_PORT=
Explicação sobre as varíaveis:
- SECRET_KEY: Coloque nesta a SECRET_KEY que você tiver gerado para a aplicação Django.
- DEBUG: Coloque nesta um boolean indicando se o projeto está ou não no debug mode.
- DB_NAME até DB_PORT: Coloque nestas as informações do banco relacional em você salvará os dados. De preferência, utilize um banco PostgreSQL, pois o projeto foi desenvolvido pensando neste banco.
- EMAIL_HOST até EMAIL_PORT: Coloque nestas as informações do host de e-mail que será utilizado para enviar as mensagens via e-mail.
1° Acesse o diretório server:
cd server
2° Execute as migrations:
python3 manage.py migrate
3° Execute a api:
python3 manage.py runserver
4° Acesse o diretório web:
cd web
5° Execute o frontend:
npx quasar dev #ou
quasar dev #este segundo comando só funcionará se tiver o Quasar instalado globalmente
*Obs.: Para o projeto funcionar corretamente, api e frontend devem estar em execução ao mesmo tempo.
A API provém os seguintes endpoints:
rota | descrição |
---|---|
POST /auth/register/ | Registra um novo usuário Ver detalhes |
POST /auth/verify-email/ | Verifica o e-mail de um usuário Ver detalhes |
POST /auth/login/ | Retorna o access e o refresh token de um usuário Ver detalhes |
POST /auth/logout/ | Adiciona o refresh token de um usuário na blacklist Ver detalhes |
GET /auth/profile/ | Verifica a validade do access token de um usuário Ver detalhes |
POST /auth/token/refresh/ | Gera um novo access token para um usuário Ver detalhes |
POST /auth/password-reset/ | Solicita mudança de senha Ver detalhes |
GET /auth/password-reset-confirm/{uidb64}/{passwordResetToken}/ | Verifica se o token de mudança de senha é válido Ver detalhes |
PATCH /auth/set-new-password/ | Muda a senha Ver detalhes |
GET /events | Pega a lista de todos os eventos. Ver detalhes |
GET /events/{eventUUID}/ | Pega um evento específico por UUID. Ver detalhes |
POST /events | Registra um novo evento Ver detalhes |
PUT /events/{eventUUID}/ | Edita os dados de um evento específico por UUID |
PATCH /events/{eventUUID}/ | Edita os dados de um evento específico por UUID |
DELETE /events/{eventUUID}/ | Remove um evento específico por UUID |
GET /users/{userId}/eventUUID/ | Pega todos os eventos de um usuário específico por UUID. Ver detalhes |
{
name: 'Emanuel de Souza Lacerda',
email: 'emanuellacerda@gmail.com',
password: 'te@12klLA',
confirm_password: 'te@12klLA'
}
{
name: 'Emanuel de Souza Lacerda',
email: 'emanuellacerda@gmail.com'
}
{
code: '238657'
}
{
message: "O e-mail foi verificado com sucesso"
}
{
email: 'emanuellacerda@gmail.com',
password: 'te@12klLA'
}
{
name: 'Emanuel de Souza Lacerda',
email: 'emanuellacerda@gmail.com',
access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzQxNTQ5NjUxLCJpYXQiOjE3NDE1NDkwNTEsImp0aSI6IjViYTg3MzZkMDQ4ZTQ5YTk4ZGI3ZWJhNmFmYjA0YjlkIiwidXNlcl9pZCI6OH0.T69yVAqEFlAEcj8ODabqjheaYn7jEBMQZagat6EU0aI',
refresh_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTc0MTYzNTQ1MSwiaWF0IjoxNzQxNTQ5MDUxLCJqdGkiOiJmNWE3ZTJiMzcyNTQ0ZmViYjIwZTE4OTdiYjVkZDcxMyIsInVzZXJfaWQiOjh9.FX2a-g85tgb4iL9A-ISRe10RxG34za0QxmJslcnVgjk'
}
{
refresh_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6InVzZXIiLCJleHAiOjE2ODg4ODg4ODh9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
}
headers: {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzQxNTQ5NjUxLCJpYXQiOjE3NDE1NDkwNTEsImp0aSI6IjViYTg3MzZkMDQ4ZTQ5YTk4ZGI3ZWJhNmFmYjA0YjlkIiwidXNlcl9pZCI6OH0.T69yVAqEFlAEcj8ODabqjheaYn7jEBMQZagat6EU0aI'
}
{
message: 'Access token válido'
}
{
refresh: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6InVzZXIiLCJleHAiOjE2ODg4ODg4ODh9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
}
{
access: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzQxNTU1Nzk1LCJpYXQiOjE3NDE1NTQyMDEsImp0aSI6IjQxZjlhMDgzZDc3NTQ1Nzc5MzVlYjU1ZDdkMmIxYWUzIiwidXNlcl9pZCI6MX0.LVBBXKoWAc2zj8mB68lMK1-X9fc4BULajVg1AL29Cyw'
}
{
email: 'emanuellacerda@gmail.com'
}
{
message: 'Um link foi enviado para seu e-mail para redefinir sua senha'
}
{
success: true,
message: 'Credenciais são válidas',
uidb64: 'OA',
token: 'cmfa98-977a1ffcda77d9d43fee15f27c0e3e28'
}
{
"password": "758$JFJ388lka@",
"confirm_password": "758$JFJ388lka@",
"uidb64": "OA",
"token": "cmf783-35b27aa704b3d4b6e9010f6b1ab79c54"
}
{
message: 'Sua senha foi redefinida com sucesso'
}
[
{
id: 'f75b6d05-439c-4af1-a2d9-8f7eb6efe19b',
created_at: '2025-02-17T13:44:37.436230Z',
updated_at: '2025-02-23T20:19:03.735480Z',
active: true,
description: 'Reunião de planejamento do projeto X',
start: '2025-03-01T10:00:00Z',
end: '2025-03-01T12:00:00Z',
creator: 1,
participants: []
},
{
id: 'a1b2c3d4-e5f6-7890-1234-567890abcdef',
created_at: '2025-02-20T09:00:00.000000Z',
updated_at: '2025-02-22T15:30:00.000000Z',
active: true,
description: 'Aniversário da equipe',
start: '2025-03-15T14:00:00Z',
end: '2025-03-15T17:00:00Z',
creator: 4,
participants: []
}
]
{
id: 'a1b2c3d4-e5f6-7890-1234-567890abcdef',
created_at: '2025-02-20T09:00:00.000000Z',
updated_at: '2025-02-22T15:30:00.000000Z',
active: true,
description: 'Aniversário da equipe',
start: '2025-03-15T14:00:00Z',
end: '2025-03-15T17:00:00Z',
creator: 4,
participants: []
}
{
description: 'Consulta médica de rotina',
start: '2025-03-20T11:00:00Z',
end: '2025-03-20T12:00:00Z',
creator: 6
}
{
id: '123e4567-e89b-12d3-a456-426614174000',
created_at: '2025-02-23T20:30:00.000000Z',
updated_at: '2025-02-23T20:30:00.000000Z',
active: true,
description: 'Consulta médica de rotina',
start: '2025-03-20T11:00:00Z',
end: '2025-03-20T12:00:00Z',
creator: 6,
participants: []
}
[
{
id: '123e4567-e89b-12d3-a456-426614174000',
created_at: '2025-03-05T08:30:00.000000Z',
updated_at: '2025-03-05T09:15:00.000000Z',
active: true,
description: 'Consulta médica de rotina',
start: '2025-03-20T11:00:00Z',
end: '2025-03-20T12:00:00Z',
creator: 6,
participants: []
}
]
Ao longo do conteúdo acima, falo as funcionalidades/melhorias futuras. Abaixo segue a lista completa e organizada, com o link do respectivo issue em cada funcionalidade:
- Restrição de criação, edição e remoção de eventos a somente usuários com e-mail verificado - Issue #3;
- Estratégia de Refresh Token Rotation no gerenciamento de sessão com JWT - Issue #8;
- Convidar outros usuários para participar dos eventos criados pelo usuário - Issue #9.
- Definir que um evento pode ter intersecção de período com outros eventos - Issue #10.
- Emanuel Lacerda - Desenvolvedor - @EmanuelLacerda