diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml new file mode 100644 index 0000000..680725d --- /dev/null +++ b/.github/workflows/ci-cd.yaml @@ -0,0 +1,242 @@ +name: CI Pipeline + +on: + push: + branches: [main, docker] + +env: + FRONTEND_IMAGE: routparesh/chattingo-frontend + BACKEND_IMAGE: routparesh/chattingo-backend + +jobs: + # ---------------- FRONTEND TEST ---------------- + frontend-test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install frontend dependencies + working-directory: ./frontend + run: npm install + + - name: Run frontend tests + working-directory: ./frontend + run: | + if [ -f package.json ] && grep -q "\"test\":" package.json; then + npm test -- --coverage + else + echo "⚠️ No test script found in frontend/package.json" + fi + + # ---------------- BACKEND CI (Build + Test + Sonar) ---------------- + backend-ci: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: test@123 + MYSQL_DATABASE: chattingo_db + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping --silent" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Load backend environment variables + working-directory: ./backend + run: | + echo "SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/chattingo_db" >> $GITHUB_ENV + echo "SPRING_DATASOURCE_USERNAME=root" >> $GITHUB_ENV + echo "SPRING_DATASOURCE_PASSWORD=test@123" >> $GITHUB_ENV + echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> $GITHUB_ENV + + - name: Wait for MySQL to be ready + run: | + for i in {1..15}; do + nc -zv 127.0.0.1 3306 && echo "βœ… MySQL is ready!" && break + echo "⏳ Waiting for MySQL..." + sleep 5 + done + + - name: Build and Run Tests + working-directory: ./backend + run: mvn clean verify + + - name: SonarQube Analysis + working-directory: ./backend + run: | + mvn sonar:sonar \ + -Dsonar.projectKey=chattingo-backend \ + -Dsonar.projectName=chattingo-backend \ + -Dsonar.host.url=${{ secrets.SONAR_HOST_URL }} \ + -Dsonar.login=${{ secrets.SONAR_TOKEN }} \ + -Dsonar.java.binaries=target/classes + + # ---------------- SONAR SCAN (Frontend Only) ---------------- + sonar-frontend: + runs-on: ubuntu-latest + needs: [frontend-test, backend-ci] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: SonarQube Scan (frontend) + uses: sonarsource/sonarqube-scan-action@v3 + with: + projectBaseDir: ./frontend + args: > + -Dsonar.projectName=chattingo-frontend + -Dsonar.projectKey=chattingo-frontend + -Dsonar.sources=src + -Dsonar.tests=src + -Dsonar.test.inclusions=**/*.test.js,**/*.spec.js + -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + + # ---------------- SECURITY SCAN ---------------- + security-scan: + runs-on: ubuntu-latest + needs: sonar-frontend + steps: + - name: Checkout full history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run Gitleaks Scan + uses: gitleaks/gitleaks-action@v2 + with: + args: detect --source . --no-banner --verbose + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install Frontend Dependencies + working-directory: ./frontend + run: npm install + + - name: Run npm audit for vulnerabilities + working-directory: ./frontend + run: npm audit --audit-level=low || true + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: OWASP Dependency Check (Backend) + uses: dependency-check/Dependency-Check_Action@main + env: + JAVA_HOME: /opt/jdk + with: + project: chattingo-backend + path: ./backend + format: 'HTML' + out: reports + + - name: Upload Dependency Check Report + uses: actions/upload-artifact@v4 + with: + name: dependency-check-report + path: reports + + - name: Run Trivy Filesystem Scan + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: . + format: 'table' + exit-code: '0' + + - name: Build Images for Scanning + run: | + docker build -t frontend-scan ./frontend + docker build -t backend-scan ./backend + + - name: Run Trivy Image Scan (Frontend) + uses: aquasecurity/trivy-action@master + with: + image-ref: 'frontend-scan' + format: 'table' + exit-code: '0' + + - name: Run Trivy Image Scan (Backend) + uses: aquasecurity/trivy-action@master + with: + image-ref: 'backend-scan' + format: 'table' + exit-code: '0' + + # ---------------- DOCKER BUILD & DEPLOY ---------------- + docker-deploy: + runs-on: ubuntu-latest + needs: security-scan + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and Push Frontend image + working-directory: ./frontend + run: | + FRONTEND_BUILD_ARGS="--build-arg REACT_APP_API_URL=http://chattingo.paresh.work/api" + + docker build $FRONTEND_BUILD_ARGS -t ${{ env.FRONTEND_IMAGE }}:${{ github.sha }} . + docker push ${{ env.FRONTEND_IMAGE }}:${{ github.sha }} + + - name: Build and Push Backend image + working-directory: ./backend + run: | + docker build -t ${{ env.BACKEND_IMAGE }}:${{ github.sha }} . + docker push ${{ env.BACKEND_IMAGE }}:${{ github.sha }} + + - name: Update image tags in Kubernetes manifests + run: | + sed -i "s|routparesh/chattingo-frontend:.*| ${{ env.FRONTEND_IMAGE }}:${{ github.sha }}|g" k8s/chattingo-frontend-deployment.yaml + sed -i "s|routparesh/chattingo-backend:.*| ${{ env.BACKEND_IMAGE }}:${{ github.sha }}|g" k8s/chattingo-backend-deployment.yaml + + - name: Commit updated image tags + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add k8s/*.yaml + git commit -m "update images to ${{ github.sha }}" + git push diff --git a/Readme.md b/Readme.md index 8e16d5a..f70652d 100644 --- a/Readme.md +++ b/Readme.md @@ -4,20 +4,25 @@ A full-stack real-time chat application built with React, Spring Boot, and WebSo ## 🚨 **IMPORTANT: Registration Required** -### **πŸ“ [REGISTER NOW](https://forms.gle/NgNJNg8yQvPaA1Vz9)** +### **πŸ“ [REGISTER NOW](https://forms.gle/NgNJNg8yQvPaA1Vz9)** + **Deadline: September 5, 2025** **What you need to register:** + - Your Name - Email ID - LinkedIn Profile URL - GitHub Profile URL ### **πŸ“€ [SUBMISSION FORM](https://forms.gle/ww3vPN29JTNRqzM27)** + **Deadline: September 10, 2025 (11:59 PM)** ### **πŸ“‚ Repository Access** + **Repository URL**: https://github.com/iemafzalhassan/chattingo + - **Currently**: Private repository (registration phase) - **Will be public**: After registration closes (Sept 6) - **Action Required**: Fork & star the repository once it becomes public @@ -25,6 +30,7 @@ A full-stack real-time chat application built with React, Spring Boot, and WebSo --- ## 🎯 **Hackathon Challenge** + Transform this vanilla application into a production-ready, containerized system with automated deployment! ## πŸ“‹ Table of Contents @@ -43,6 +49,7 @@ Transform this vanilla application into a production-ready, containerized system ## πŸ† Hackathon Overview ### 🎯 Project Goals + - **Build & Deploy**: Create Dockerfiles and containerize the application - **CI/CD Pipeline**: Implement Jenkins automated deployment - **VPS Deployment**: Deploy on Hostinger VPS using modern DevOps practices @@ -50,15 +57,18 @@ Transform this vanilla application into a production-ready, containerized system ### πŸ“… Timeline & Registration #### **Registration Phase (Sept 3-5)** + πŸ“ **[REGISTER HERE](https://forms.gle/NgNJNg8yQvPaA1Vz9)** - Complete by Sept 5 **Registration Form Requirements:** + - Name -- Email ID +- Email ID - LinkedIn Profile - GitHub Profile #### **Event Schedule** + - **Sept 6**: Kickoff session (VPS setup, Docker + Jenkins basics) - **Sept 7-10**: Build period (3 days) - **Sept 10**: Submissions close at 11:59 PM @@ -80,6 +90,7 @@ Transform this vanilla application into a production-ready, containerized system ## πŸ› οΈ Technology Stack ### Frontend + - **React 18** - Modern UI framework - **Redux Toolkit** - State management - **Material-UI** - Component library @@ -88,6 +99,7 @@ Transform this vanilla application into a production-ready, containerized system - **React Router** - Client-side routing ### Backend + - **Spring Boot 3.3.1** - Java framework - **Spring Security** - Authentication & authorization - **Spring Data JPA** - Database operations @@ -96,6 +108,7 @@ Transform this vanilla application into a production-ready, containerized system - **MySQL** - Database ### DevOps (Your Tasks) + - **Docker** - Containerization (YOU BUILD) - **Docker Compose** - Multi-container orchestration (YOU BUILD) - **Jenkins** - CI/CD pipeline (YOU BUILD) @@ -106,6 +119,7 @@ Transform this vanilla application into a production-ready, containerized system ### **Just Registered? Start Here!** #### **Step 1: Fork & Clone** + ```bash # Fork this repository on GitHub: https://github.com/iemafzalhassan/chattingo # Then clone your fork @@ -114,17 +128,21 @@ cd chattingo ``` #### **Step 2: Join Discord** + - **[πŸ—“β”ƒEvents-Announcement](https://discord.gg/jYeffuxs)**: Stay updated - **[πŸ“β”ƒEvents-Chat](https://discord.gg/bHVKCYj4)**: Get technical support #### **Step 3: Local Development Setup** + Follow **[CONTRIBUTING.md](CONTRIBUTING.md)** for detailed setup instructions. #### **Step 4: Follow the Timeline** + - **Sept 7-10**: Build your implementation - **Sept 10**: Submit before 11:59 PM ### **Reference Guides** + - **Detailed Setup & Deployment**: **[CONTRIBUTING.md](CONTRIBUTING.md)** ## πŸ”§ **YOUR TASKS** @@ -134,16 +152,19 @@ Follow **[CONTRIBUTING.md](CONTRIBUTING.md)** for detailed setup instructions. You need to create these files from scratch: #### **Frontend Dockerfile** (3-stage build) + - Stage 1: Node.js build environment -- Stage 2: Build React application +- Stage 2: Build React application - Stage 3: Nginx runtime server #### **Backend Dockerfile** (3-stage build) + - Stage 1: Maven build environment - Stage 2: Build Spring Boot application -- Stage 3: JRE runtime +- Stage 3: JRE runtimee #### **Docker Compose** (Root level) + Create `docker-compose.yml` to orchestrate all services. **Scoring**: Single Stage (2), Two Stage (4), Multi Stage (5) @@ -155,27 +176,27 @@ Create a `Jenkinsfile` with these stages: ```groovy pipeline { agent any - + stages { - stage('Git Clone') { + stage('Git Clone') { // Clone repository from GitHub (2 Marks) } - stage('Image Build') { + stage('Image Build') { // Build Docker images for frontend & backend (2 Marks) } - stage('Filesystem Scan') { + stage('Filesystem Scan') { // Security scan of source code (2 Marks) } - stage('Image Scan') { + stage('Image Scan') { // Vulnerability scan of Docker images (2 Marks) } - stage('Push to Registry') { + stage('Push to Registry') { // Push images to Docker Hub/Registry (2 Marks) } - stage('Update Compose') { + stage('Update Compose') { // Update docker-compose with new image tags (2 Marks) } - stage('Deploy') { + stage('Deploy') { // Deploy to Hostinger VPS (5 Marks) } } @@ -183,6 +204,7 @@ pipeline { ``` ### Additional Requirements + - **Jenkins Shared Library**: 3 Marks - **Active Engagement**: 2 Marks - **Creativity**: 2 Marks @@ -192,6 +214,7 @@ pipeline { - Video (Compulsory): 5 Marks ### **Task 3: VPS Deployment** + - **Hostinger VPS Setup**: Ubuntu 22.04 LTS, 2GB RAM - **Domain Configuration**: Setup your domain with DNS - **SSL Certificate**: Configure HTTPS with Let's Encrypt @@ -202,6 +225,7 @@ pipeline { ## πŸ“± Application Features ### Core Functionality + - βœ… User authentication (JWT) - βœ… Real-time messaging (WebSocket) - βœ… Group chat creation @@ -210,6 +234,7 @@ pipeline { - βœ… Responsive design ### API Endpoints + ``` POST /api/auth/register - User registration POST /api/auth/login - User login @@ -250,9 +275,11 @@ chattingo/ ## πŸŽ₯ **Submission Requirements** ### **πŸ“€ Submission Form: [Submit Here](https://forms.gle/ww3vPN29JTNRqzM27)** + **Deadline: Sept 10, 11:59 PM** ### **Required Submission Fields** + 1. **Name** - Your full name 2. **Email ID** - Contact email 3. **GitHub Repository URL** - Your forked and implemented project @@ -262,7 +289,9 @@ chattingo/ 7. **README URL** - Link to your updated README file ### **Required Deliverables** + 1. **GitHub Repository** with your implementation + - βœ… Dockerfiles (Backend & Frontend - 3-stage builds) - βœ… docker-compose.yml (Root level orchestration) - βœ… Jenkinsfile (Complete CI/CD pipeline) @@ -271,6 +300,7 @@ chattingo/ - βœ… Updated README with deployment instructions 2. **Live Application** deployed on Hostinger VPS + - βœ… Working chat application with HTTPS - βœ… SSL certificate configured - βœ… Domain properly configured @@ -283,6 +313,7 @@ chattingo/ - βœ… Key features demonstration ### **Bonus Points** + 1. **Blog Post** - Technical writeup of your implementation (2 marks) 2. **Additional Features** - Enhancements to the chat app 3. **Monitoring** - Application monitoring and logging @@ -293,34 +324,41 @@ chattingo/ ### **Implementation Flow** (Following Hackathon Timeline) #### **Phase 1: Registration (Sept 3-5)** + 1. **[Register Here](https://forms.gle/NgNJNg8yQvPaA1Vz9)** with your details 2. **Fork this repository**: https://github.com/iemafzalhassan/chattingo 3. **Join Discord** channels for updates and support #### **Phase 2: Kickoff Session (Sept 6)** + - **Attend intro session** - VPS setup guide, Docker & Jenkins basics - **Get your VPS** access and domain setup - **Ask questions** and clarify requirements #### **Phase 3: Build Period (Sept 7-10)** + - **Day 1**: Local development setup β†’ **[CONTRIBUTING.md](CONTRIBUTING.md)** -- **Day 2**: Docker & Jenkins implementation +- **Day 2**: Docker & Jenkins implementation - **Day 3**: VPS deployment β†’ **[CONTRIBUTING.md](CONTRIBUTING.md)** #### **Phase 4: Submission (Sept 10)** + πŸ“€ **[SUBMIT HERE](https://forms.gle/ww3vPN29JTNRqzM27)** before 11:59 PM #### **Phase 5: Results (Sept 11-13)** + - **Sept 11-12**: Judging & reviews - **Sept 13**: Winners announced ## πŸ“ž Support & Resources ### Discord Channels -- **[πŸ—“β”ƒπ–€π—π–Ύπ—‡π—π—Œ-π– π—‡π—‡π—ˆπ—Žπ—‡π–Όπ–Ύπ—†π–Ύπ—‡π—](https://discord.gg/jYeffuxs)**: Stay Active in the Announcement channel for Hackathon Update. + +- **[πŸ—“β”ƒπ–€π—π–Ύπ—‡π—π—Œ-π– π—‡π—‡π—ˆπ—Žπ—‡π–Όπ–Ύπ—†π–Ύπ—‡π—](https://discord.gg/jYeffuxs)**: Stay Active in the Announcement channel for Hackathon Update. - **[πŸ“β”ƒπ–€π—π–Ύπ—‡π—π—Œ-𝖒𝗁𝖺𝗍](https://discord.gg/bHVKCYj4)**: Technical support. ### Reference Links + - [Hackathon Repository](https://github.com/iemafzalhassan/chattingo) - [Docker Documentation](https://docs.docker.com/) - [Jenkins Documentation](https://www.jenkins.io/doc/) @@ -328,14 +366,14 @@ chattingo/ ## πŸ… Judging Criteria -| Component | Marks | Description | -|-----------|-------|-------------| -| Dockerfile | 5 | Multi-stage implementation | -| Jenkinsfile | 17 | Complete CI/CD pipeline | -| Shared Library | 3 | Reusable Jenkins components | -| Engagement | 2 | Active participation | -| Creativity | 2 | Unique features/implementation | -| Documentation | 10 | README, blog, video | +| Component | Marks | Description | +| -------------- | ----- | ------------------------------ | +| Dockerfile | 5 | Multi-stage implementation | +| Jenkinsfile | 17 | Complete CI/CD pipeline | +| Shared Library | 3 | Reusable Jenkins components | +| Engagement | 2 | Active participation | +| Creativity | 2 | Unique features/implementation | +| Documentation | 10 | README, blog, video | --- diff --git a/backend/.env.example b/backend/.env.example index a28bc1d..38d697a 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,10 +1,9 @@ # JWT Configuration JWT_SECRET=your-generated-jwt-secret-key-here - # Database Configuration SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/chattingo_db?createDatabaseIfNotExist=true SPRING_DATASOURCE_USERNAME=root -SPRING_DATASOURCE_PASSWORD=yourpassword +SPRING_DATASOURCE_PASSWORD=test@123 # CORS Configuration CORS_ALLOWED_ORIGINS=http://localhost:3000,https://your-domain.com @@ -16,5 +15,5 @@ SPRING_PROFILES_ACTIVE=development SERVER_PORT=8080 # Production Database (for Docker/VPS deployment) -MYSQL_ROOT_PASSWORD=your-secure-production-password +MYSQL_ROOT_PASSWORD=test@123 MYSQL_DATABASE=chattingo_db diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..fb291ac --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,11 @@ +FROM maven:3.9.9-eclipse-temurin-21 AS build +WORKDIR /app +COPY . . +RUN mvn clean package -DskipTests + +# Stage 2: Run the application using OpenJDK +FROM openjdk:21 +WORKDIR /app +COPY --from=build /app/target/*.jar app.jar +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/src/main/java/com/chattingo/config/AppConfig.java b/backend/src/main/java/com/chattingo/config/AppConfig.java index 85aa0f2..2a1ec34 100644 --- a/backend/src/main/java/com/chattingo/config/AppConfig.java +++ b/backend/src/main/java/com/chattingo/config/AppConfig.java @@ -6,7 +6,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -16,7 +15,7 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; -import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; @Configuration public class AppConfig { @@ -35,41 +34,47 @@ public AppConfig(JwtValidator jwtValidator) { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - - http.sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(authorize -> authorize.requestMatchers("/api/**").authenticated() - .anyRequest().permitAll()) - .addFilterBefore(jwtValidator, BasicAuthenticationFilter.class) - .csrf(csrf -> csrf.disable()) - .cors(cors -> cors.configurationSource(new CorsConfigurationSource() { - @SuppressWarnings("null") - @Override - public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { - CorsConfiguration cfg = new CorsConfiguration(); - - // Parse allowed origins from environment variable - String[] origins = allowedOrigins.split(","); - cfg.setAllowedOrigins(Arrays.asList(origins)); - cfg.setAllowedOriginPatterns(Arrays.asList(origins)); - - // Parse allowed methods from environment variable - String[] methods = allowedMethods.split(","); - cfg.setAllowedMethods(Arrays.asList(methods)); - - cfg.setAllowedHeaders(Collections.singletonList("*")); - cfg.setExposedHeaders(Arrays.asList("Authorization")); - cfg.setAllowCredentials(true); - cfg.setMaxAge(3600L); - - return cfg; - } - })).formLogin(Customizer.withDefaults()).httpBasic(Customizer.withDefaults()); + http + .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/login", "/signup").permitAll() + .requestMatchers("/api/**").authenticated() + .anyRequest().permitAll() + ) + .addFilterBefore(jwtValidator, BasicAuthenticationFilter.class) + .csrf(csrf -> csrf.disable()) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .exceptionHandling(exceptions -> exceptions + .authenticationEntryPoint( + (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized") + ) + ); return http.build(); } @Bean - PasswordEncoder passwordEncoder() { + public CorsConfigurationSource corsConfigurationSource() { + return request -> { + CorsConfiguration cfg = new CorsConfiguration(); + + String[] origins = allowedOrigins.split(","); + cfg.setAllowedOriginPatterns(Arrays.asList(origins)); + + String[] methods = allowedMethods.split(","); + cfg.setAllowedMethods(Arrays.asList(methods)); + + cfg.setAllowedHeaders(Collections.singletonList("*")); + cfg.setExposedHeaders(Collections.singletonList("Authorization")); + cfg.setAllowCredentials(true); + cfg.setMaxAge(3600L); + + return cfg; + }; + } + + @Bean + public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/chattingo/config/JwtValidator.java b/backend/src/main/java/com/chattingo/config/JwtValidator.java index 900e6c4..f700ce8 100644 --- a/backend/src/main/java/com/chattingo/config/JwtValidator.java +++ b/backend/src/main/java/com/chattingo/config/JwtValidator.java @@ -5,13 +5,13 @@ import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -32,35 +32,43 @@ public JwtValidator(@Value("${jwt.secret}") String jwtSecret) { this.key = Keys.hmacShaKeyFor(jwtSecret.getBytes()); } - @SuppressWarnings("null") @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { String jwt = request.getHeader("Authorization"); - if (jwt != null) { + if (jwt != null && jwt.startsWith("Bearer ")) { try { + jwt = jwt.substring(7); // Remove "Bearer " prefix - jwt = jwt.substring(7); - - Claims claim = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jwt).getBody(); + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(jwt) + .getBody(); - String username = String.valueOf(claim.get("email")); - String authorities = String.valueOf(claim.get("authorities")); + String username = String.valueOf(claims.get("email")); + String authorities = String.valueOf(claims.get("authorities")); List auths = AuthorityUtils.commaSeparatedStringToAuthorityList(authorities); Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, auths); - SecurityContextHolder.getContext().setAuthentication(authentication); } catch (Exception e) { - throw new BadCredentialsException("Invalid token recieved..."); + throw new BadCredentialsException("Invalid token received"); } } filterChain.doFilter(request, response); } + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + // Skip JWT validation for public endpoints + String path = request.getServletPath(); + return path.equals("/login") || path.equals("/signup"); + } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 19eda2c..e2a8502 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,15 +1,19 @@ -spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/chattingo_db?createDatabaseIfNotExist=true} +spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:mysql://db:3306/chattingo_db?createDatabaseIfNotExist=true} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=${SPRING_DATASOURCE_USERNAME:root} -spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:} +spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:test@123} spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true +management.endpoints.web.exposure.include=health,info +management.endpoint.health.show-details=always +management.endpoint.health.probes.enabled=true + # JWT secret (fallback for local dev; override via env: JWT_SECRET) -jwt.secret=${JWT_SECRET:change-me-in-prod} +jwt.secret=${JWT_SECRET:htxAPEWvHnRrMlL403iAktlmOFlseDUiPIqsECSQpAM=} # CORS Configuration (can be overridden by environment variables) -cors.allowed.origins=${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost} +cors.allowed.origins=${CORS_ALLOWED_ORIGINS:http://13.222.158.219:3000,http://localhost} cors.allowed.methods=${CORS_ALLOWED_METHODS:GET,POST,PUT,DELETE,OPTIONS} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..6157389 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,43 @@ +version: '3.8' + +services: + db: + image: mysql:latest + environment: + MYSQL_ROOT_PASSWORD: test@123 + MYSQL_DATABASE: chattingo_db + volumes: + - db-data:/var/lib/mysql + healthcheck: + test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost'] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: ./backend + image: routparesh/chattingo-backend:latest + container_name: backend + depends_on: + db: + condition: service_healthy + environment: + DB_HOST: db + DB_USER: root + DB_PASS: test@123 + DB_NAME: usersdb + env_file: + - ./backend/.env + ports: + - '8080:8080' + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + image: routparesh/chattingo-frontend:latest + ports: + - '3000:80' + +volumes: + db-data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..2659d18 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,22 @@ +# 1. Build stage +FROM node:18-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm install + +ARG REACT_APP_API_URL +ENV REACT_APP_API_URL=$REACT_APP_API_URL + +COPY . . +RUN npm run build + +# 2. Nginx stage +FROM nginx:alpine +COPY --from=builder /app/build /usr/share/nginx/html + +# Optional: remove default nginx config and copy your own +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..d9e49b1 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,18 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # React SPA route handling + location / { + try_files $uri /index.html; + } + + # Optional: proxy API calls to backend service + # Uncomment if your frontend calls API like /api/* + # location /api/ { + # proxy_pass http://chattingo-backend-service:8080; + # } +} diff --git a/frontend/src/App.test.js b/frontend/src/App.test.js index 1f03afe..741c11e 100644 --- a/frontend/src/App.test.js +++ b/frontend/src/App.test.js @@ -1,8 +1,15 @@ -import { render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; import App from './App'; +import store from './Redux/store'; -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); +test('renders App component', () => { + render( + + + + + + ); }); diff --git a/frontend/src/Redux/store.jsx b/frontend/src/Redux/store.jsx index d500cd8..b18f24f 100644 --- a/frontend/src/Redux/store.jsx +++ b/frontend/src/Redux/store.jsx @@ -1,15 +1,17 @@ -import { combineReducers, legacy_createStore, applyMiddleware } from "redux"; -import thunk from "redux-thunk"; -import { authReducer } from "./Auth/Reducer"; -import { chatReducer } from "./Chat/Reducer"; -import { messageReducer } from "./Message/Reducer"; +import { applyMiddleware, combineReducers, legacy_createStore } from 'redux'; +import thunk from 'redux-thunk'; +import { authReducer } from './Auth/Reducer'; +import { chatReducer } from './Chat/Reducer'; +import { messageReducer } from './Message/Reducer'; // Combine multiple reducers into a single rootReducer const rootReducer = combineReducers({ - auth: authReducer, // Authentication related state - chat: chatReducer, // Chat related state - message: messageReducer, // Message related state + auth: authReducer, // Authentication related state + chat: chatReducer, // Chat related state + message: messageReducer, // Message related state }); // Create the Redux store with the rootReducer and apply middleware (thunk in this case) export const store = legacy_createStore(rootReducer, applyMiddleware(thunk)); + +export default store; diff --git a/gitops.yaml b/gitops.yaml new file mode 100644 index 0000000..d8f57af --- /dev/null +++ b/gitops.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: chattingo-application + namespace: argocd # Must match the namespace where Argo CD is installed +spec: + project: default # The Argo CD project this application belongs to + source: + repoURL: https://github.com/Routparesh/chattingo.git # URL of the Git repository + targetRevision: main # The branch, tag, or commit hash to deploy + path: k8s # Path within the repository to the Kubernetes manifests + destination: + server: https://kubernetes.default.svc # The target Kubernetes cluster API server URL + namespace: chattingo # The namespace in the target cluster where resources will be deployed + syncPolicy: + automated: + prune: true # Automatically delete resources that are no longer in Git + selfHeal: true # Automatically sync resources if they drift from Git + syncOptions: + - CreateNamespace=true # Create the destination namespace if it does not exist diff --git a/k8s/chattingo-backend-deployment.yaml b/k8s/chattingo-backend-deployment.yaml new file mode 100644 index 0000000..6fc9f9a --- /dev/null +++ b/k8s/chattingo-backend-deployment.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: chattingo-backend-deployment + labels: + app: chattingo-backend +spec: + replicas: 1 + selector: + matchLabels: + app: chattingo-backend + template: + metadata: + labels: + app: chattingo-backend + spec: + containers: + - name: chattingo-backend + image: routparesh/chattingo-backend:d1f8b88c7c26ae078d2ce31e1062b52ae13ab753 + ports: + - containerPort: 8080 + env: + - name: SPRING_DATASOURCE_URL + value: jdbc:mysql://db:3306/chattingo_db?createDatabaseIfNotExist=true + - name: SPRING_DATASOURCE_USERNAME + value: root + - name: SPRING_DATASOURCE_PASSWORD + value: test@123 + - name: CORS_ALLOWED_ORIGINS + value: 'http://chattingo.paresh.work' + - name: CORS_ALLOWED_METHODS + value: 'GET,POST,PUT,DELETE,OPTIONS' + - name: CORS_ALLOWED_HEADERS + value: '*' diff --git a/k8s/chattingo-backend-service.yaml b/k8s/chattingo-backend-service.yaml new file mode 100644 index 0000000..f1b1c24 --- /dev/null +++ b/k8s/chattingo-backend-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: chattingo-backend-service +spec: + selector: + app: chattingo-backend + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 diff --git a/k8s/chattingo-db-deployment.yaml b/k8s/chattingo-db-deployment.yaml new file mode 100644 index 0000000..c9a3d1c --- /dev/null +++ b/k8s/chattingo-db-deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: chattingo-db-deployment + labels: + app: chattingo-db +spec: + replicas: 1 + selector: + matchLabels: + app: chattingo-db + template: + metadata: + labels: + app: chattingo-db + spec: + containers: + - name: chattingo-db + image: mysql:8.0 + env: + - name: MYSQL_ROOT_PASSWORD + value: 'test@123' + - name: MYSQL_DATABASE + value: 'chattingo_db' + ports: + - containerPort: 3306 diff --git a/k8s/chattingo-db-service.yaml b/k8s/chattingo-db-service.yaml new file mode 100644 index 0000000..220ec3c --- /dev/null +++ b/k8s/chattingo-db-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: db +spec: + selector: + app: chattingo-db + ports: + - protocol: TCP + port: 3306 + targetPort: 3306 + type: ClusterIP diff --git a/k8s/chattingo-frontend-deployment.yaml b/k8s/chattingo-frontend-deployment.yaml new file mode 100644 index 0000000..957d24e --- /dev/null +++ b/k8s/chattingo-frontend-deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: chattingo-frontend-deployment + labels: + app: chattingo-frontend +spec: + replicas: 1 + selector: + matchLabels: + app: chattingo-frontend + template: + metadata: + labels: + app: chattingo-frontend + spec: + containers: + - name: chattingo-frontend + image: routparesh/chattingo-frontend:d1f8b88c7c26ae078d2ce31e1062b52ae13ab753 + ports: + - containerPort: 80 diff --git a/k8s/chattingo-frontend-service.yaml b/k8s/chattingo-frontend-service.yaml new file mode 100644 index 0000000..59a77b1 --- /dev/null +++ b/k8s/chattingo-frontend-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: chattingo-frontend-service +spec: + selector: + app: chattingo-frontend + ports: + - protocol: TCP + port: 80 + targetPort: 80 + type: ClusterIP diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..ea388ba --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,37 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: chattingo-ingress + namespace: chattingo + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80}]' + alb.ingress.kubernetes.io/group.name: chattingo-group + alb.ingress.kubernetes.io/healthcheck-path: / + alb.ingress.kubernetes.io/backend-protocol: HTTP + alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120 +spec: + ingressClassName: alb + rules: + - host: chattingo.paresh.work + http: + paths: + # Backend APIs + - path: /api + pathType: Prefix + backend: + service: + name: chattingo-backend-service + port: + number: 8080 + + # Frontend + - path: / + pathType: Prefix + backend: + service: + name: chattingo-frontend-service + port: + number: 80