Tire suas dúvidas rapidamente com nosso autoatendimento.
-
-
-
- Como solicitar um orçamento?
- expand_more
-
-
- Você pode solicitar um orçamento preenchendo o formulário acima selecionando o assunto "Orçamento". Nossa equipe entrará em contato em até 24 horas úteis.
-
-
-
@@ -152,8 +141,7 @@
Perguntas Frequentes
expand_more
- Nosso suporte técnico funciona de segunda a sexta-feira, das 08:00 às 18:00 (horário de Brasília). Para urgências críticas em condomínios premium, oferecemos plantão 24/7 via canal exclusivo no WhatsApp.
-
+ O Convive é um projeto open source. Dúvidas, bugs e pedidos de ajuda podem ser abertos como issue no GitHub.
@@ -163,7 +151,7 @@
Perguntas Frequentes
expand_more
- Estamos com um roadmap para lançar um aplicativo nativo para ambas as plataformas IOS e Android até o fim de 2026
+ Ainda não. Um aplicativo nativo para iOS e Android está no roadmap de longo prazo, sem data definida — acompanhe o progresso pelas issues do repositório.
+ O Convive é um software open source e self-hosted: o código é público, e cada
+ condomínio (ou pessoa) que o instala roda a própria cópia, no próprio servidor. Isso significa que
+ não existe uma empresa Convive processando dados centralmente — quem controla os
+ dados desta instância é quem administra o servidor onde ela está rodando (geralmente o síndico ou
+ responsável técnico do condomínio).
+
+
+
+
Quais dados são armazenados
+
+
Dados de cadastro: nome, e-mail, apartamento e senha (armazenada com hash, nunca em texto puro).
+
Conteúdo gerado pelo uso: reservas de áreas comuns, ocorrências registradas, comunicados e notificações.
+
Foto de perfil e anexos enviados voluntariamente pelo usuário.
+
Cookies estritamente funcionais: sessão de login e "lembrar-me" (nenhum cookie de rastreamento/publicidade).
+
+
+
+
+
Com quem os dados são compartilhados
+
+ Por padrão, com ninguém fora da própria instância. O único envio a terceiros é o e-mail
+ transacional (recuperação de senha, notificações) via o servidor SMTP configurado pelo
+ administrador da instância.
+
+
+
+
+
Seus direitos
+
+ Para acessar, corrigir ou solicitar a exclusão dos seus dados, entre em contato com quem
+ administra o condomínio/instância que você utiliza — normalmente pelo canal de
+ contato desta própria aplicação.
+
+
+
+
+
Código aberto
+
+ Como o projeto é open source, você pode inspecionar exatamente como os dados são tratados no
+ repositório público no GitHub.
+
+ O Convive é um software livre, distribuído sob a licença
+ MIT.
+ Qualquer pessoa pode obter o código-fonte, instalar e operar sua própria instância.
+
+
+
+
+
Sem garantias
+
+ Assim como a maioria dos projetos open source, o Convive é fornecido "no estado em que se
+ encontra" (as-is), sem garantias de qualquer tipo, conforme os termos da licença MIT. Os
+ mantenedores do projeto não se responsabilizam por perdas, danos ou indisponibilidade
+ decorrentes do uso da aplicação em instâncias de terceiros.
+
+
+
+
+
Responsabilidade pela instância
+
+ Quem instala e opera uma instância do Convive é responsável por essa instância: segurança do
+ servidor, backups, conformidade legal (incluindo LGPD) e relacionamento com os
+ usuários/moradores que a utilizam. Os mantenedores do projeto no GitHub não têm acesso nem
+ controle sobre instâncias de terceiros.
+
+
+
+
+
Uso aceitável
+
+ Ao usar uma instância do Convive, espera-se que você forneça informações verdadeiras e não
+ utilize o sistema para fins ilícitos, ofensivos ou que violem direitos de terceiros.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Convive/src/test/java/com/EC6/Convive/Controller/ComunicadoControllerTest.java b/Convive/src/test/java/com/EC6/Convive/Controller/ComunicadoControllerTest.java
new file mode 100644
index 0000000..82f267a
--- /dev/null
+++ b/Convive/src/test/java/com/EC6/Convive/Controller/ComunicadoControllerTest.java
@@ -0,0 +1,103 @@
+package com.EC6.Convive.Controller;
+
+import com.EC6.Convive.Model.Comunicado;
+import com.EC6.Convive.Model.Moderador;
+import com.EC6.Convive.Model.Morador;
+import com.EC6.Convive.Security.CustomUserDetails;
+import com.EC6.Convive.Service.ComunicadoService;
+import com.EC6.Convive.Service.FileStorageService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class ComunicadoControllerTest {
+
+ @Mock
+ private ComunicadoService comunicadoService;
+ @Mock
+ private FileStorageService fileStorageService;
+ @Mock
+ private RedirectAttributes redirectAttributes;
+ @InjectMocks
+ private ComunicadoController comunicadoController;
+
+ @Test
+ void comunicadoController_CriarComunicado_Moderador_Sucesso() {
+ Moderador moderador = new Moderador();
+ CustomUserDetails userDetails = new CustomUserDetails(moderador);
+ Comunicado comunicado = new Comunicado();
+ when(comunicadoService.insert(any(Comunicado.class))).thenReturn(comunicado);
+
+ String view = comunicadoController.criarComunicado(userDetails, comunicado, null, redirectAttributes);
+
+ assertEquals("redirect:/morador/comunicados", view);
+ verify(comunicadoService, times(1)).insert(any(Comunicado.class));
+ verify(redirectAttributes).addFlashAttribute("sucesso", "Aviso publicado com sucesso!");
+ verify(redirectAttributes, never()).addFlashAttribute(eq("erro"), any());
+ }
+
+ @Test
+ void comunicadoController_CriarComunicado_Moderador_Erro() {
+ Moderador moderador = new Moderador();
+ CustomUserDetails userDetails = new CustomUserDetails(moderador);
+ Comunicado comunicado = new Comunicado();
+ when(comunicadoService.insert(any(Comunicado.class))).thenThrow(new RuntimeException("Falha ao salvar"));
+
+ String view = comunicadoController.criarComunicado(userDetails, comunicado, null, redirectAttributes);
+
+ assertEquals("redirect:/morador/comunicados", view);
+ verify(comunicadoService, times(1)).insert(any(Comunicado.class));
+ verify(redirectAttributes).addFlashAttribute("erro", "Erro ao publicar aviso: Falha ao salvar");
+ verify(redirectAttributes, never()).addFlashAttribute(eq("sucesso"), any());
+ }
+
+ @Test
+ void comunicadoController_CriarComunicado_NaoModerador_SemPermissao() {
+ Morador morador = new Morador();
+ CustomUserDetails userDetails = new CustomUserDetails(morador);
+ Comunicado comunicado = new Comunicado();
+
+ String view = comunicadoController.criarComunicado(userDetails, comunicado, null, redirectAttributes);
+
+ assertEquals("redirect:/morador/comunicados", view);
+ verify(comunicadoService, never()).insert(any(Comunicado.class));
+ verify(redirectAttributes).addFlashAttribute("erro", "Você não tem permissão para publicar comunicados.");
+ }
+
+ @Test
+ void excluirComunicado_Moderador_Sucesso() {
+ Moderador moderador = new Moderador();
+ CustomUserDetails userDetails = new CustomUserDetails(moderador);
+ UUID id = UUID.randomUUID();
+
+ String view = comunicadoController.excluirComunicado(userDetails, id, redirectAttributes);
+
+ assertEquals("redirect:/morador/comunicados", view);
+ verify(comunicadoService, times(1)).delete(id);
+ verify(redirectAttributes).addFlashAttribute("sucesso", "Aviso excluído com sucesso.");
+ verify(redirectAttributes, never()).addFlashAttribute(eq("erro"), any());
+ }
+
+ @Test
+ void excluirComunicado_NaoModerador_SemPermissao() {
+ Morador morador = new Morador();
+ CustomUserDetails userDetails = new CustomUserDetails(morador);
+ UUID id = UUID.randomUUID();
+
+ String view = comunicadoController.excluirComunicado(userDetails, id, redirectAttributes);
+
+ assertEquals("redirect:/morador/comunicados", view);
+ verify(comunicadoService, never()).delete(any());
+ verify(redirectAttributes).addFlashAttribute("erro", "Você não tem permissão para excluir comunicados.");
+ }
+}
diff --git a/Convive/src/test/java/com/EC6/Convive/Controller/OcorrenciaControllerTest.java b/Convive/src/test/java/com/EC6/Convive/Controller/OcorrenciaControllerTest.java
new file mode 100644
index 0000000..b2d8268
--- /dev/null
+++ b/Convive/src/test/java/com/EC6/Convive/Controller/OcorrenciaControllerTest.java
@@ -0,0 +1,96 @@
+package com.EC6.Convive.Controller;
+
+import com.EC6.Convive.Model.CategoriaOcorrencia;
+import com.EC6.Convive.Model.Morador;
+import com.EC6.Convive.Security.CustomUserDetails;
+import com.EC6.Convive.Service.FileStorageService;
+import com.EC6.Convive.Service.OcorrenciaService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class OcorrenciaControllerTest {
+
+ @Mock
+ private OcorrenciaService ocorrenciaService;
+ @Mock
+ private FileStorageService fileStorageService;
+ @Mock
+ private RedirectAttributes redirectAttributes;
+ @InjectMocks
+ private OcorrenciaController ocorrenciaController;
+
+ @Test
+ void excluirOcorrencia_ServicoConfirma_Sucesso() {
+ Morador morador = new Morador();
+ CustomUserDetails userDetails = new CustomUserDetails(morador);
+ UUID id = UUID.randomUUID();
+ when(ocorrenciaService.deleteForUser(id, morador)).thenReturn(true);
+
+ String view = ocorrenciaController.excluirOcorrencia(userDetails, id, redirectAttributes);
+
+ assertEquals("redirect:/morador/ocorrencias", view);
+ verify(ocorrenciaService, times(1)).deleteForUser(id, morador);
+ verify(redirectAttributes).addFlashAttribute("sucessoOcorrencia", "Ocorrência eliminada com sucesso.");
+ verify(redirectAttributes, never()).addFlashAttribute(eq("erroOcorrencia"), any());
+ }
+
+ @Test
+ void excluirOcorrencia_NaoPertenceAoUsuario_Falha() {
+ // Reproduz o cenário do IDOR corrigido na issue #72: o service nega a
+ // exclusão de uma ocorrência que não pertence ao morador autenticado
+ // (nem é moderador), e o controller precisa refletir isso sem excluir nada.
+ Morador morador = new Morador();
+ CustomUserDetails userDetails = new CustomUserDetails(morador);
+ UUID id = UUID.randomUUID();
+ when(ocorrenciaService.deleteForUser(id, morador)).thenReturn(false);
+
+ String view = ocorrenciaController.excluirOcorrencia(userDetails, id, redirectAttributes);
+
+ assertEquals("redirect:/morador/ocorrencias", view);
+ verify(ocorrenciaService, times(1)).deleteForUser(id, morador);
+ verify(redirectAttributes).addFlashAttribute("erroOcorrencia", "Não foi possível eliminar esta ocorrência.");
+ verify(redirectAttributes, never()).addFlashAttribute(eq("sucessoOcorrencia"), any());
+ }
+
+ @Test
+ void registrarNovaOcorrencia_TituloVazio_NaoRegistra() {
+ Morador morador = new Morador();
+ CustomUserDetails userDetails = new CustomUserDetails(morador);
+ MockMultipartFile semAnexo = new MockMultipartFile("imagemEvidencia", new byte[0]);
+
+ String view = ocorrenciaController.registrarNovaOcorrencia(
+ userDetails, " ", CategoriaOcorrencia.OUTRO, "descricao", semAnexo, redirectAttributes);
+
+ assertEquals("redirect:/morador/ocorrencias", view);
+ verify(ocorrenciaService, never()).insert(any());
+ verify(redirectAttributes).addFlashAttribute("erroOcorrencia", "O título é obrigatório.");
+ }
+
+ @Test
+ void registrarNovaOcorrencia_Sucesso() throws Exception {
+ Morador morador = new Morador();
+ CustomUserDetails userDetails = new CustomUserDetails(morador);
+ MockMultipartFile semAnexo = new MockMultipartFile("imagemEvidencia", new byte[0]);
+
+ String view = ocorrenciaController.registrarNovaOcorrencia(
+ userDetails, "Vazamento", CategoriaOcorrencia.INFRAESTRUTURA, "descricao", semAnexo, redirectAttributes);
+
+ assertEquals("redirect:/morador/ocorrencias", view);
+ verify(ocorrenciaService, times(1)).insert(any());
+ verify(redirectAttributes).addFlashAttribute("sucessoOcorrencia", "Ocorrência registrada com sucesso!");
+ verify(fileStorageService, never()).salvarImagemOcorrencia(any());
+ }
+}
diff --git a/Convive/src/test/java/com/EC6/Convive/Service/MoradorServiceTest.java b/Convive/src/test/java/com/EC6/Convive/Service/MoradorServiceTest.java
index a2be913..b9a1529 100644
--- a/Convive/src/test/java/com/EC6/Convive/Service/MoradorServiceTest.java
+++ b/Convive/src/test/java/com/EC6/Convive/Service/MoradorServiceTest.java
@@ -13,6 +13,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@@ -43,5 +44,27 @@ void moradorService_SearchById_ErroNaoEncontrado() {
assertThrows(RuntimeException.class, () -> moradorService.searchById(id));
}
+
+ @Test
+ void moradorService_Update_PersisteInadimplente() {
+ UUID id = UUID.randomUUID();
+ Morador existente = new Morador();
+ existente.setId(id);
+ existente.setInadimplente(true);
+
+ Morador atualizado = new Morador();
+ atualizado.setNome("Maria");
+ atualizado.setEmail("maria@test.com");
+ atualizado.setApartamento(202);
+ atualizado.setInadimplente(false);
+
+ when(moradorRepository.findById(id)).thenReturn(Optional.of(existente));
+ when(moradorRepository.save(any(Morador.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ Morador salvo = moradorService.update(id, atualizado);
+
+ assertFalse(salvo.isInadimplente());
+ verify(moradorRepository).save(existente);
+ }
}
diff --git a/Convive/src/test/java/com/EC6/Convive/Service/OcorrenciaServiceTest.java b/Convive/src/test/java/com/EC6/Convive/Service/OcorrenciaServiceTest.java
index bd5ea92..905ad9a 100644
--- a/Convive/src/test/java/com/EC6/Convive/Service/OcorrenciaServiceTest.java
+++ b/Convive/src/test/java/com/EC6/Convive/Service/OcorrenciaServiceTest.java
@@ -2,6 +2,7 @@
import com.EC6.Convive.Event.OcorrenciaCriadaEvent;
import com.EC6.Convive.Model.CategoriaOcorrencia;
+import com.EC6.Convive.Model.Moderador;
import com.EC6.Convive.Model.Morador;
import com.EC6.Convive.Model.Ocorrencia;
import com.EC6.Convive.Model.Prioridade;
@@ -20,6 +21,8 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -142,4 +145,53 @@ void ocorrenciaService_Update_NaoReaplicaPrioridadePadrao() {
assertEquals(Prioridade.BAIXA, atualizada.getPrioridade());
}
+
+ @Test
+ void ocorrenciaService_DeleteForUser_MoradorAutor_Sucesso() {
+ UUID ocorrenciaId = UUID.randomUUID();
+ UUID usuarioId = UUID.randomUUID();
+ Morador morador = new Morador();
+ morador.setId(usuarioId);
+ Ocorrencia ocorrencia = new Ocorrencia();
+
+ when(ocorrenciaRepository.findByIdAndUsuario_Id(ocorrenciaId, usuarioId))
+ .thenReturn(Optional.of(ocorrencia));
+
+ boolean resultado = ocorrenciaService.deleteForUser(ocorrenciaId, morador);
+
+ assertTrue(resultado);
+ verify(ocorrenciaRepository, times(1)).delete(ocorrencia);
+ }
+
+ @Test
+ void ocorrenciaService_DeleteForUser_MoradorNaoAutor_Falha() {
+ UUID ocorrenciaId = UUID.randomUUID();
+ UUID usuarioId = UUID.randomUUID();
+ Morador morador = new Morador();
+ morador.setId(usuarioId);
+
+ when(ocorrenciaRepository.findByIdAndUsuario_Id(ocorrenciaId, usuarioId))
+ .thenReturn(Optional.empty());
+
+ boolean resultado = ocorrenciaService.deleteForUser(ocorrenciaId, morador);
+
+ assertFalse(resultado);
+ verify(ocorrenciaRepository, never()).delete(any());
+ }
+
+ @Test
+ void ocorrenciaService_DeleteForUser_Moderador_Sucesso() {
+ UUID ocorrenciaId = UUID.randomUUID();
+ Moderador moderador = new Moderador();
+ moderador.setId(UUID.randomUUID());
+ Ocorrencia ocorrencia = new Ocorrencia();
+
+ when(ocorrenciaRepository.findById(ocorrenciaId)).thenReturn(Optional.of(ocorrencia));
+
+ boolean resultado = ocorrenciaService.deleteForUser(ocorrenciaId, moderador);
+
+ assertTrue(resultado);
+ verify(ocorrenciaRepository, times(1)).delete(ocorrencia);
+ verify(ocorrenciaRepository, never()).findByIdAndUsuario_Id(any(), any());
+ }
}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e25e262
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Diogo Santos Rodrigues, Leonardo Rosário Teixeira
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index efbe59d..be87725 100644
--- a/README.md
+++ b/README.md
@@ -1,70 +1,100 @@
# 🏢 Convive - Plataforma de Gestão Condominial
-O **Convive** é um sistema web robusto projetado para modernizar, centralizar e facilitar a convivência e a administração de condomínios. Desenvolvido com foco na eficiência operacional e na clareza de comunicação, a plataforma conecta moradores e a administração (síndicos/moderadores) de forma ágil e segura.
+[](https://github.com/dioguit0s/Convive-WebApp/actions/workflows/ci.yml)
+[](LICENSE)
-## ✨ Funcionalidades Principais
+O **Convive** é uma alternativa **open source e self-hosted** aos sistemas de gestão condominial pagos. Roda na sua própria infraestrutura, com o código-fonte completo aberto — sem mensalidade, sem vendor lock-in.
-A aplicação é dividida em dois domínios principais de acesso, garantindo a separação de responsabilidades e segurança através do Spring Security:
+> **Estado do projeto:** nasceu como projeto de faculdade e hoje é mantido como software livre. Cobre o essencial da gestão condominial (comunicados, reservas de áreas comuns, ocorrências, notificações); recursos como pagamentos, portaria e app mobile ainda não existem — veja [Roadmap](#-roadmap).
-### 👤 Portal do Morador
+## 📸 Telas
-* **Home/Mural**: Visualização de comunicados importantes emitidos pela administração.
-* **Gestão de Reservas**: Interface para solicitação, visualização e cancelamento de reservas de áreas comuns (ex: salão de festas, churrasqueira).
-* **Ocorrências**: Abertura e acompanhamento do status de ocorrências, reclamações ou sugestões.
-* **Notificações**: Central de alertas em tempo real sobre mudanças de status nas solicitações.
+| Landing page | Dashboard do moderador | Portal do morador |
+|---|---|---|
+|  |  |  |
-### 🛡️ Dashboard Operacional (Moderador)
+## 🚀 Quickstart (self-hosted)
-* **Visão Geral**: Dashboard com métricas e gráficos de uso do condomínio.
-* **Triagem de Reservas**: Aprovação, rejeição e gerenciamento de conflitos de agenda nas áreas comuns.
-* **Triagem de Ocorrências**: Análise, mudança de status e resolução de problemas relatados pelos moradores.
-* **Gestão de Moradores**: Controle de usuários, envio de advertências e gerenciamento de perfis.
-* **Comunicação**: Criação e disparo de comunicados globais.
+Pré-requisito: [Docker](https://docs.docker.com/get-docker/) e Docker Compose.
-### ⚙️ Recursos de Sistema
+```bash
+git clone https://github.com/dioguit0s/Convive-WebApp.git
+cd Convive-WebApp
+cp .env.example .env # edite e defina APP_REMEMBER_ME_KEY e demais variáveis
+docker compose up --build
+```
-* **Event-Driven Notifications**: Uso de *Application Events* do Spring (`OcorrenciaCriadaEvent`, `ReservaRejeitadaEvent`, etc.) para desacoplar a lógica de negócio do envio de e-mails.
-* **Recuperação de Senha**: Fluxo completo de *Forgot Password* com geração de tokens seguros e envio de links por e-mail.
-* **CI/CD Integrado**: Pipelines configuradas via GitHub Actions para integração e entrega contínuas.
+A aplicação sobe em `http://localhost:8085` já com dados de exemplo (moradores, áreas comuns, reservas, ocorrências e comunicados fictícios) — veja as credenciais de teste no log de inicialização do container `app`.
----
+Para rodar localmente sem Docker (perfil de desenvolvimento com banco H2 embutido):
-## 🛠️ Tecnologias e Arquitetura
+```bash
+cd Convive
+./mvnw spring-boot:run
+```
+
+## ✨ Funcionalidades
+
+A aplicação é dividida em dois domínios principais de acesso, com separação de responsabilidades via Spring Security:
+
+### 👤 Portal do Morador
+
+* **Home/Mural**: visualização de comunicados importantes emitidos pela administração.
+* **Gestão de Reservas**: solicitação, visualização e cancelamento de reservas de áreas comuns.
+* **Ocorrências**: abertura e acompanhamento de ocorrências, reclamações ou sugestões.
+* **Notificações**: central de alertas sobre mudanças de status nas solicitações.
-O projeto foi construído seguindo o padrão arquitetural **MVC (Model-View-Controller)**, priorizando código limpo e fácil manutenção, características essenciais de uma boa engenharia de software.
+### 🛡️ Dashboard Operacional (Moderador/Síndico)
-**Back-end:**
+* **Visão Geral**: dashboard com métricas e gráficos de uso do condomínio.
+* **Triagem de Reservas**: aprovação, rejeição e gerenciamento de conflitos de agenda.
+* **Triagem de Ocorrências**: análise, mudança de status e resolução de problemas relatados.
+* **Gestão de Moradores**: controle de usuários, advertências e perfis, incluindo flag de inadimplência.
+* **Comunicação**: criação e disparo de comunicados globais.
-* **Java 17+**
-* **Spring Boot** (Web, Data JPA, Security, Mail)
-* **Maven** (Gerenciamento de dependências)
+### ⚙️ Recursos de Sistema
+
+* **Event-Driven Notifications**: *Application Events* do Spring (`OcorrenciaCriadaEvent`, `ReservaRejeitadaEvent`, etc.) para desacoplar regras de negócio do envio de e-mails.
+* **Recuperação de Senha**: fluxo completo de *forgot password* com tokens seguros por e-mail.
+* **CI**: pipeline via GitHub Actions rodando a suíte de testes em cada PR.
+
+## 🆚 Convive vs. SaaS de gestão condominial
+
+| | Convive (open source) | SaaS pago típico |
+|---|---|---|
+| Custo | Gratuito, você hospeda | Mensalidade por unidade/condomínio |
+| Código-fonte | Aberto (MIT) | Fechado |
+| Dados | Ficam na sua própria infraestrutura | Ficam com o fornecedor |
+| Suporte | Comunidade / issues no GitHub | Suporte contratado |
-**Front-end:**
+## 🛠️ Tecnologias e Arquitetura
-* **Thymeleaf** (Motor de templates para renderização Server-Side)
-* **HTML5, CSS3 e JavaScript (Vanilla)**
-* **TailwindCSS** (Estilização utilitária e design responsivo)
+Padrão **MVC (Model-View-Controller)** server-side, sem SPA.
-**Infraestrutura & DevOps:**
+**Back-end:** Java 21, Spring Boot 3.2 (Web, Data JPA, Security, Mail, Validation), Maven.
-* **Banco de Dados Relacional** (Mapeamento via Hibernate/JPA)
-* **GitHub Actions** (Pipelines de CI/Deploy)
+**Front-end:** Thymeleaf (Server-Side Rendering), HTML5, CSS3, JavaScript vanilla, TailwindCSS.
----
+**Infraestrutura:** PostgreSQL em produção (H2 embutido para desenvolvimento local), Docker/Docker Compose para self-hosting, GitHub Actions para CI.
## 📂 Estrutura do Projeto
-O back-end está organizado de forma modular no pacote `src/main/java/com/EC6/Convive/`:
+O back-end está organizado de forma modular no pacote `Convive/src/main/java/com/EC6/Convive/`:
+
+* `Config/`: configurações globais de segurança (`SecurityConfig`), MVC e inicialização de dados (`DataInitializer`).
+* `Controller/`: rotas e fluxo das views (`Moderador`, `Morador`, `Public`).
+* `Model/`: entidades de domínio (`Usuario`, `Reserva`, `Ocorrencia`, etc.).
+* `Repository/`: interfaces Spring Data JPA.
+* `Service/`: regras de negócio isoladas das rotas web.
+* `Event/` & `Listener/`: padrão Observer para notificações assíncronas.
+* `Security/`: implementações customizadas de `UserDetailsService`.
-* `Config/`: Configurações globais de segurança (SecurityConfig), MVC e inicialização de dados.
-* `Controller/`: Controladores responsáveis por expor as rotas e gerenciar o fluxo das views (`Moderador`, `Morador`, `Public`).
-* `Model/`: Entidades de domínio mapeadas para o banco de dados (ex: `Usuario`, `Reserva`, `Ocorrencia`).
-* `Repository/`: Interfaces Spring Data JPA para acesso a dados.
-* `Service/`: Camada de regras de negócio isoladas das rotas web.
-* `Event/ & Listener/`: Implementação de padrão Observer para disparos de e-mail e notificações assíncronas.
-* `Security/`: Implementações customizadas de `UserDetailsService` para autenticação.
+## 🗺️ Roadmap
----
+Itens em aberto e priorização estão rastreados nas [issues do repositório](https://github.com/dioguit0s/Convive-WebApp/issues).
+## 🤝 Contribuindo
+
+Issues e PRs são bem-vindos. Abra uma [issue](https://github.com/dioguit0s/Convive-WebApp/issues/new) descrevendo o problema ou a proposta antes de submeter um PR grande.
## 👨💻 Autores
@@ -76,8 +106,6 @@ O back-end está organizado de forma modular no pacote `src/main/java/com/EC6/Co
*Engenheiro de Software*
[GitHub](https://github.com/leonardorosario)
----
-
## 📄 Licença
-Este projeto está sob a licença [MIT](https://opensource.org/licenses/MIT). Sinta-se à vontade para utilizá-lo e modificá-lo.
+Este projeto está sob a licença [MIT](LICENSE). Sinta-se à vontade para utilizá-lo, modificá-lo e hospedá-lo.
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..f287269
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,41 @@
+services:
+ app:
+ build:
+ context: ./Convive
+ ports:
+ - "8085:8085"
+ environment:
+ SPRING_PROFILES_ACTIVE: prod
+ SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/convive
+ SPRING_DATASOURCE_USERNAME: convive
+ SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:-convive}
+ APP_REMEMBER_ME_KEY: ${APP_REMEMBER_ME_KEY:?defina uma chave secreta em .env}
+ APP_BASE_URL: ${APP_BASE_URL:-http://localhost:8085}
+ SMTP_USERNAME: ${SMTP_USERNAME:-}
+ SMTP_PASSWORD: ${SMTP_PASSWORD:-}
+ APP_CONTACT_TO: ${APP_CONTACT_TO:-}
+ volumes:
+ - uploads:/app/uploads
+ depends_on:
+ db:
+ condition: service_healthy
+ restart: unless-stopped
+
+ db:
+ image: postgres:16-alpine
+ environment:
+ POSTGRES_DB: convive
+ POSTGRES_USER: convive
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-convive}
+ volumes:
+ - db-data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U convive -d convive"]
+ interval: 5s
+ timeout: 5s
+ retries: 10
+ restart: unless-stopped
+
+volumes:
+ db-data:
+ uploads: