diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..954cb5a --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# Copie este arquivo para .env e preencha os valores antes de rodar `docker compose up`. + +# Gere uma string aleatoria propria (ex: `openssl rand -hex 32`). Nunca reutilize o valor padrao do codigo. +APP_REMEMBER_ME_KEY= + +# Senha do banco Postgres usado pelo docker-compose. +POSTGRES_PASSWORD=convive + +# URL publica onde a aplicacao vai ficar acessivel (usada em links de e-mail). +APP_BASE_URL=http://localhost:8085 + +# Credenciais SMTP para envio de e-mails (recuperacao de senha, notificacoes). Opcional para testar localmente. +SMTP_USERNAME= +SMTP_PASSWORD= +APP_CONTACT_TO= diff --git a/Convive/.gitignore b/.gitignore similarity index 90% rename from Convive/.gitignore rename to .gitignore index bab3271..de89e7a 100644 --- a/Convive/.gitignore +++ b/.gitignore @@ -33,6 +33,10 @@ build/ ### VS Code ### .vscode/ +### Cursor ### +.cursor/ +.cursor/rules/ +mvnw.cmd ### DATABASE ### /data/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7004756 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contribuindo com o Convive + +Obrigado pelo interesse em contribuir! O Convive é mantido como projeto open source e self-hosted — issues e PRs são bem-vindos. + +## Rodando o projeto localmente + +Veja a seção "Quickstart" no [README](README.md) para subir a aplicação via `docker compose up` ou via `./mvnw spring-boot:run` (perfil de desenvolvimento com H2 embutido, sem precisar de Docker). + +## Rodando os testes + +```bash +cd Convive +./mvnw clean test +``` + +A suíte roda automaticamente em cada Pull Request via GitHub Actions (`.github/workflows/ci.yml`). + +## Abrindo uma issue + +Antes de abrir um PR grande, abra uma [issue](https://github.com/dioguit0s/Convive-WebApp/issues/new) descrevendo o problema ou a proposta. Isso evita retrabalho caso a abordagem precise de ajuste antes de virar código. + +## Enviando um Pull Request + +* Crie um branch a partir da branch principal do repositório, com um nome descritivo (ex: `fix/nome-do-bug`, `feat/nome-da-feature`). +* Mantenha o PR focado em uma mudança por vez — PRs pequenos são revisados mais rápido. +* Descreva o que mudou e por quê; se corrigir um bug, descreva como reproduzi-lo antes da correção. +* Certifique-se de que `./mvnw clean test` passa localmente antes de abrir o PR. + +## Código de conduta + +Seja respeitoso nas discussões de issues e PRs. Críticas técnicas são bem-vindas; ataques pessoais não. diff --git a/Convive/.dockerignore b/Convive/.dockerignore new file mode 100644 index 0000000..b022cb5 --- /dev/null +++ b/Convive/.dockerignore @@ -0,0 +1,7 @@ +target/ +data/ +uploads/ +.git/ +.idea/ +*.iml +HELP.md diff --git a/Convive/Dockerfile b/Convive/Dockerfile new file mode 100644 index 0000000..5f61637 --- /dev/null +++ b/Convive/Dockerfile @@ -0,0 +1,24 @@ +# syntax=docker/dockerfile:1 +FROM eclipse-temurin:21-jdk AS build +WORKDIR /app + +COPY mvnw . +COPY .mvn .mvn +COPY pom.xml . +RUN ./mvnw -B dependency:go-offline + +COPY src src +RUN ./mvnw -B clean package -DskipTests + +FROM eclipse-temurin:21-jre +WORKDIR /app + +RUN useradd -r convive \ + && mkdir -p /app/uploads \ + && chown -R convive:convive /app + +COPY --from=build /app/target/*.jar app.jar + +USER convive +EXPOSE 8085 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/Convive/pom.xml b/Convive/pom.xml index 59e607b..ebac46f 100644 --- a/Convive/pom.xml +++ b/Convive/pom.xml @@ -50,6 +50,12 @@ h2 + + org.postgresql + postgresql + runtime + + org.springframework.boot spring-boot-starter-test diff --git a/Convive/src/main/java/com/EC6/Convive/Config/DataInitializer.java b/Convive/src/main/java/com/EC6/Convive/Config/DataInitializer.java index 53f4c82..320b179 100644 --- a/Convive/src/main/java/com/EC6/Convive/Config/DataInitializer.java +++ b/Convive/src/main/java/com/EC6/Convive/Config/DataInitializer.java @@ -48,13 +48,6 @@ private void seedUsuarios() { moderador.setStatus("Ativo"); moderadorRepository.save(moderador); - Moderador diogo = new Moderador(); - diogo.setNome("Diogo Santos"); - diogo.setEmail("diogosantos152005@gmail.com"); - diogo.setSenhaHash(passwordEncoder.encode("diogos12")); - diogo.setStatus("Ativo"); - moderadorRepository.save(diogo); - criarMorador("Carlos Mendes", "carlos.mendes@email.com", 101, false); criarMorador("Maria Oliveira", "maria.oliveira@email.com", 205, false); criarMorador("João Pereira", "joao.pereira@email.com", 302, true); diff --git a/Convive/src/main/java/com/EC6/Convive/Config/SecurityConfig.java b/Convive/src/main/java/com/EC6/Convive/Config/SecurityConfig.java index 489caad..dac343d 100644 --- a/Convive/src/main/java/com/EC6/Convive/Config/SecurityConfig.java +++ b/Convive/src/main/java/com/EC6/Convive/Config/SecurityConfig.java @@ -2,6 +2,7 @@ import com.EC6.Convive.Security.CustomUserDetailsService; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; @@ -19,19 +20,32 @@ public class SecurityConfig { private final CustomUserDetailsService userDetailsService; + @Value("${spring.h2.console.enabled:false}") + private boolean h2ConsoleEnabled; + + @Value("${app.remember-me.key:chave-secreta-unica-do-projeto-convive}") + private String rememberMeKey; + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/", "/login", "/features", "/about", "/contact", - "/forgot-password", "/reset-password").permitAll() - .requestMatchers("/css/**", "/js/**", "/img/**", "/h2-console/**").permitAll() + .authorizeHttpRequests(authorize -> { + authorize + .requestMatchers("/", "/login", "/features", "/about", "/contact", + "/privacy", "/terms", + "/forgot-password", "/reset-password").permitAll() + .requestMatchers("/css/**", "/js/**", "/img/**").permitAll(); - .requestMatchers("/morador/**").hasAnyRole("MORADOR", "MODERADOR") - .requestMatchers("/moderador/**").hasRole("MODERADOR") + if (h2ConsoleEnabled) { + authorize.requestMatchers("/h2-console/**").permitAll(); + } - .anyRequest().authenticated() - ) + authorize + .requestMatchers("/morador/**").hasAnyRole("MORADOR", "MODERADOR") + .requestMatchers("/moderador/**").hasRole("MODERADOR") + + .anyRequest().authenticated(); + }) .formLogin(form -> form .loginPage("/login") .usernameParameter("email") @@ -40,7 +54,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .permitAll() ) .rememberMe(remember -> remember - .key("chave-secreta-unica-do-projeto-convive") + .key(rememberMeKey) .userDetailsService(userDetailsService) .rememberMeParameter("remember-me") .tokenValiditySeconds(7 * 24 * 60 * 60) @@ -50,8 +64,18 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .logoutSuccessUrl("/login?logout") .permitAll() ) - .csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**")) - .headers(headers -> headers.frameOptions(frame -> frame.disable())); + .csrf(csrf -> { + if (h2ConsoleEnabled) { + csrf.ignoringRequestMatchers("/h2-console/**"); + } + }) + .headers(headers -> { + if (h2ConsoleEnabled) { + // O console do H2 roda em um iframe; so relaxamos a protecao de clickjacking + // quando ele esta habilitado (nunca em producao). + headers.frameOptions(frame -> frame.disable()); + } + }); return http.build(); } diff --git a/Convive/src/main/java/com/EC6/Convive/Controller/ComunicadoController.java b/Convive/src/main/java/com/EC6/Convive/Controller/ComunicadoController.java index d59a8b2..48d54a2 100644 --- a/Convive/src/main/java/com/EC6/Convive/Controller/ComunicadoController.java +++ b/Convive/src/main/java/com/EC6/Convive/Controller/ComunicadoController.java @@ -68,9 +68,6 @@ public String criarComunicado(@AuthenticationPrincipal CustomUserDetails userDet } catch (Exception e) { redirectAttributes.addFlashAttribute("erro", "Erro ao publicar aviso: " + e.getMessage()); } - - comunicadoService.insert(comunicado); - redirectAttributes.addFlashAttribute("sucesso", "Aviso publicado com sucesso!"); } else { redirectAttributes.addFlashAttribute("erro", "Você não tem permissão para publicar comunicados."); } @@ -79,13 +76,22 @@ public String criarComunicado(@AuthenticationPrincipal CustomUserDetails userDet } @PostMapping("/excluir") - public String excluirComunicado(@RequestParam UUID id, org.springframework.web.servlet.mvc.support.RedirectAttributes redirectAttributes) { - try { - comunicadoService.delete(id); - redirectAttributes.addFlashAttribute("sucesso", "Aviso excluído com sucesso."); - } catch (Exception e) { - redirectAttributes.addFlashAttribute("erro", "Erro ao excluir o aviso."); + public String excluirComunicado(@AuthenticationPrincipal CustomUserDetails userDetails, + @RequestParam UUID id, + RedirectAttributes redirectAttributes) { + Usuario usuario = userDetails.getUsuario(); + + if (usuario instanceof Moderador) { + try { + comunicadoService.delete(id); + redirectAttributes.addFlashAttribute("sucesso", "Aviso excluído com sucesso."); + } catch (Exception e) { + redirectAttributes.addFlashAttribute("erro", "Erro ao excluir o aviso."); + } + } else { + redirectAttributes.addFlashAttribute("erro", "Você não tem permissão para excluir comunicados."); } + return "redirect:/morador/comunicados"; } diff --git a/Convive/src/main/java/com/EC6/Convive/Controller/IndexController.java b/Convive/src/main/java/com/EC6/Convive/Controller/IndexController.java index 9c286bc..8856cc9 100644 --- a/Convive/src/main/java/com/EC6/Convive/Controller/IndexController.java +++ b/Convive/src/main/java/com/EC6/Convive/Controller/IndexController.java @@ -17,4 +17,10 @@ public String index() { @GetMapping("/about") public String about() {return "public/about";} + @GetMapping("/privacy") + public String privacy() {return "public/privacy";} + + @GetMapping("/terms") + public String terms() {return "public/terms";} + } \ No newline at end of file diff --git a/Convive/src/main/java/com/EC6/Convive/Controller/MoradorHomeController.java b/Convive/src/main/java/com/EC6/Convive/Controller/MoradorHomeController.java index 610314a..32d3775 100644 --- a/Convive/src/main/java/com/EC6/Convive/Controller/MoradorHomeController.java +++ b/Convive/src/main/java/com/EC6/Convive/Controller/MoradorHomeController.java @@ -195,25 +195,4 @@ private static LocalDateTime[] resolverPeriodoTurno(LocalDate data, String turno default -> throw new IllegalArgumentException("Turno inválido"); }; } - - @PostMapping("/usuarios/{id}/inadimplencia") - public String alterarInadimplenciaUsuario( - @PathVariable UUID id, - @RequestParam boolean inadimplente, - RedirectAttributes redirectAttributes - ) { - try { - usuarioService.alterarInadimplencia(id, inadimplente); - - if (inadimplente) { - redirectAttributes.addFlashAttribute("sucesso", "Morador marcado como INADIMPLENTE com sucesso."); - } else { - redirectAttributes.addFlashAttribute("sucesso", "Restrição removida. Morador agora está ADIMPLENTE."); - } - } catch (Exception e) { - redirectAttributes.addFlashAttribute("erro", "Erro ao tentar alterar o status do morador."); - } - - return "redirect:/moderador/usuarios"; - } } \ No newline at end of file diff --git a/Convive/src/main/java/com/EC6/Convive/Controller/OcorrenciaController.java b/Convive/src/main/java/com/EC6/Convive/Controller/OcorrenciaController.java index e918f1f..a60eb15 100644 --- a/Convive/src/main/java/com/EC6/Convive/Controller/OcorrenciaController.java +++ b/Convive/src/main/java/com/EC6/Convive/Controller/OcorrenciaController.java @@ -104,12 +104,15 @@ public String registrarNovaOcorrencia( } @PostMapping("/excluir") - public String excluirOcorrencia(@RequestParam UUID id, RedirectAttributes redirectAttributes) { - try { - ocorrenciaService.delete(id); + public String excluirOcorrencia( + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestParam UUID id, + RedirectAttributes redirectAttributes) { + Usuario usuario = userDetails.getUsuario(); + if (ocorrenciaService.deleteForUser(id, usuario)) { redirectAttributes.addFlashAttribute("sucessoOcorrencia", "Ocorrência eliminada com sucesso."); - } catch (Exception e) { - redirectAttributes.addFlashAttribute("erroOcorrencia", "Erro ao eliminar a ocorrência."); + } else { + redirectAttributes.addFlashAttribute("erroOcorrencia", "Não foi possível eliminar esta ocorrência."); } return "redirect:/morador/ocorrencias"; } diff --git a/Convive/src/main/java/com/EC6/Convive/Model/Usuario.java b/Convive/src/main/java/com/EC6/Convive/Model/Usuario.java index 48dc4ca..3781f01 100644 --- a/Convive/src/main/java/com/EC6/Convive/Model/Usuario.java +++ b/Convive/src/main/java/com/EC6/Convive/Model/Usuario.java @@ -33,8 +33,8 @@ public abstract class Usuario { @Column(name = "status") private String status; - @Column(name = "isInadimplente") - private boolean isInadimplente = true; + @Column(name = "isInadimplente", nullable = false) + private boolean isInadimplente = false; @Column(name = "fotoPerfil") private String fotoPerfil; diff --git a/Convive/src/main/java/com/EC6/Convive/Repository/OcorrenciaRepository.java b/Convive/src/main/java/com/EC6/Convive/Repository/OcorrenciaRepository.java index c268b56..50a8504 100644 --- a/Convive/src/main/java/com/EC6/Convive/Repository/OcorrenciaRepository.java +++ b/Convive/src/main/java/com/EC6/Convive/Repository/OcorrenciaRepository.java @@ -20,6 +20,8 @@ public interface OcorrenciaRepository extends JpaRepository { List findByUsuarioId(UUID moradorId); + Optional findByIdAndUsuario_Id(UUID id, UUID usuarioId); + Page findByUsuarioIdOrderByDataRegistroDesc(UUID moradorId, Pageable pageable); @Query(""" diff --git a/Convive/src/main/java/com/EC6/Convive/Service/MoradorService.java b/Convive/src/main/java/com/EC6/Convive/Service/MoradorService.java index 38270a5..9f957ba 100644 --- a/Convive/src/main/java/com/EC6/Convive/Service/MoradorService.java +++ b/Convive/src/main/java/com/EC6/Convive/Service/MoradorService.java @@ -58,6 +58,7 @@ public Morador update(UUID id, Morador moradorAtualizado) { moradorExistente.setNome(moradorAtualizado.getNome()); moradorExistente.setEmail(moradorAtualizado.getEmail()); moradorExistente.setApartamento(moradorAtualizado.getApartamento()); + moradorExistente.setInadimplente(moradorAtualizado.isInadimplente()); return moradorRepository.save(moradorExistente); } diff --git a/Convive/src/main/java/com/EC6/Convive/Service/OcorrenciaService.java b/Convive/src/main/java/com/EC6/Convive/Service/OcorrenciaService.java index cf06c44..a4e5d08 100644 --- a/Convive/src/main/java/com/EC6/Convive/Service/OcorrenciaService.java +++ b/Convive/src/main/java/com/EC6/Convive/Service/OcorrenciaService.java @@ -1,9 +1,11 @@ package com.EC6.Convive.Service; import com.EC6.Convive.Event.OcorrenciaCriadaEvent; +import com.EC6.Convive.Model.Moderador; import com.EC6.Convive.Model.Ocorrencia; import com.EC6.Convive.Model.Prioridade; import com.EC6.Convive.Model.StatusOcorrencia; +import com.EC6.Convive.Model.Usuario; import com.EC6.Convive.Repository.OcorrenciaRepository; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; @@ -83,6 +85,20 @@ public void delete(UUID id) { ocorrenciaRepository.deleteById(id); } + public boolean deleteForUser(UUID ocorrenciaId, Usuario usuario) { + Optional ocorrencia; + if (usuario instanceof Moderador) { + ocorrencia = ocorrenciaRepository.findById(ocorrenciaId); + } else { + ocorrencia = ocorrenciaRepository.findByIdAndUsuario_Id(ocorrenciaId, usuario.getId()); + } + if (ocorrencia.isEmpty()) { + return false; + } + ocorrenciaRepository.delete(ocorrencia.get()); + return true; + } + public List listByUser(UUID moradorId) { return ocorrenciaRepository.findByUsuarioId(moradorId); } diff --git a/Convive/src/main/java/com/EC6/Convive/Service/UsuarioService.java b/Convive/src/main/java/com/EC6/Convive/Service/UsuarioService.java index 810ccb7..97435db 100644 --- a/Convive/src/main/java/com/EC6/Convive/Service/UsuarioService.java +++ b/Convive/src/main/java/com/EC6/Convive/Service/UsuarioService.java @@ -8,7 +8,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.UUID; @@ -52,15 +51,6 @@ public void delete(UUID id) { usuarioRepository.deleteById(id); } - @Transactional - public void alterarInadimplencia(UUID usuarioId, boolean status) { - Usuario usuario = usuarioRepository.findById(usuarioId) - .orElseThrow(() -> new RuntimeException("Usuário não encontrado com o ID: " + usuarioId)); - - usuario.setInadimplente(status); - usuarioRepository.save(usuario); - } - public Usuario update(UUID id, Usuario moradorAtualizado) { Usuario usuario = searchById(id); diff --git a/Convive/src/main/resources/application-prod.properties b/Convive/src/main/resources/application-prod.properties new file mode 100644 index 0000000..aa9c89c --- /dev/null +++ b/Convive/src/main/resources/application-prod.properties @@ -0,0 +1,15 @@ +# Perfil de producao/self-hosting: Postgres real, sem H2 console exposto. +# Ativar com SPRING_PROFILES_ACTIVE=prod (ja definido no docker-compose.yml). + +spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://db:5432/convive} +spring.datasource.username=${SPRING_DATASOURCE_USERNAME:convive} +spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:convive} +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.hibernate.ddl-auto=update + +spring.h2.console.enabled=false + +# Defina uma chave propria e secreta em producao (ex: via variavel de ambiente APP_REMEMBER_ME_KEY). +app.remember-me.key=${APP_REMEMBER_ME_KEY:chave-secreta-unica-do-projeto-convive} diff --git a/Convive/src/main/resources/static/img/screenshots/dashboard-moderador.png b/Convive/src/main/resources/static/img/screenshots/dashboard-moderador.png new file mode 100644 index 0000000..23abb66 Binary files /dev/null and b/Convive/src/main/resources/static/img/screenshots/dashboard-moderador.png differ diff --git a/Convive/src/main/resources/static/img/screenshots/landing.png b/Convive/src/main/resources/static/img/screenshots/landing.png new file mode 100644 index 0000000..d5aa1f3 Binary files /dev/null and b/Convive/src/main/resources/static/img/screenshots/landing.png differ diff --git a/Convive/src/main/resources/static/img/screenshots/portal-morador.png b/Convive/src/main/resources/static/img/screenshots/portal-morador.png new file mode 100644 index 0000000..1af2549 Binary files /dev/null and b/Convive/src/main/resources/static/img/screenshots/portal-morador.png differ diff --git a/Convive/src/main/resources/templates/fragments/public/components.html b/Convive/src/main/resources/templates/fragments/public/components.html index 633352a..0e65fb7 100644 --- a/Convive/src/main/resources/templates/fragments/public/components.html +++ b/Convive/src/main/resources/templates/fragments/public/components.html @@ -48,6 +48,9 @@
Convive
© 2026 Convive Sistemas de Condominio. All rights reserved. diff --git a/Convive/src/main/resources/templates/public/contact.html b/Convive/src/main/resources/templates/public/contact.html index ad70174..2b47a1b 100644 --- a/Convive/src/main/resources/templates/public/contact.html +++ b/Convive/src/main/resources/templates/public/contact.html @@ -134,17 +134,6 @@

Perguntas Frequentes

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.
diff --git a/Convive/src/main/resources/templates/public/privacy.html b/Convive/src/main/resources/templates/public/privacy.html new file mode 100644 index 0000000..2567bb7 --- /dev/null +++ b/Convive/src/main/resources/templates/public/privacy.html @@ -0,0 +1,83 @@ + + + + + + Política de Privacidade - Convive + + + + + + + + + + + + +
+ +
+
+
+

Política de Privacidade

+

Última atualização: julho de 2026.

+ +
+

+ 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. +

+
+
+
+
+
+ +
+ + + diff --git a/Convive/src/main/resources/templates/public/terms.html b/Convive/src/main/resources/templates/public/terms.html new file mode 100644 index 0000000..04a7f3d --- /dev/null +++ b/Convive/src/main/resources/templates/public/terms.html @@ -0,0 +1,76 @@ + + + + + + Termos de Uso - Convive + + + + + + + + + + + + +
+ +
+
+
+

Termos de Uso

+

Última atualização: julho de 2026.

+ +
+
+

O que é o Convive

+

+ 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. +[![CI](https://github.com/dioguit0s/Convive-WebApp/actions/workflows/ci.yml/badge.svg)](https://github.com/dioguit0s/Convive-WebApp/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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 | +|---|---|---| +| ![Landing page do Convive](Convive/src/main/resources/static/img/screenshots/landing.png) | ![Dashboard do moderador](Convive/src/main/resources/static/img/screenshots/dashboard-moderador.png) | ![Portal do morador](Convive/src/main/resources/static/img/screenshots/portal-morador.png) | -### 🛡️ 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: