Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ FRONTEND_PORT=80
# HTTPS Port 443 is exposed via Docker Compose.
VITE_API_BASE_URL=/api/v1

# PostgreSQL Metadata Store (External Render DB or local instance)
POSTGRES_HOST=your-render-db-host.render.com
# PostgreSQL Metadata Store (Docker container host 'postgres' or external DB host)
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=fileshare_db
POSTGRES_USER=your_db_username
POSTGRES_PASSWORD=your_db_password
SPRING_DATASOURCE_URL=jdbc:postgresql://your-render-db-host.render.com:5432/fileshare_db?sslmode=require
POSTGRES_USER=fileshare_user
POSTGRES_PASSWORD=replace-with-strong-db-password
SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/fileshare_db

# MinIO S3-Compatible Object Storage
MINIO_ENDPOINT=http://minio:9000
Expand Down
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
name: Build & Test CI Pipeline
name: Continuous Integration (CI)

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build-and-test:
name: Build, Test & Validate Docker
runs-on: ubuntu-latest

steps:
Expand All @@ -31,5 +30,8 @@ jobs:
name: jacoco-coverage-report
path: target/site/jacoco/

- name: Verify Multi-Stage Dockerfile Build
run: docker build -t distributed-file-sharing:latest .
- name: Verify Backend Dockerfile Build
run: docker build -t distributed-file-sharing-backend:latest .

- name: Verify Frontend Dockerfile Build
run: docker build -t distributed-file-sharing-frontend:latest ./frontend
74 changes: 74 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Continuous Deployment (CD)

on:
push:
branches: [ main ]

jobs:
deploy:
name: Deploy to Production EC2
runs-on: ubuntu-latest

steps:
- name: Deploy & Verify on Production EC2 via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script_stop: true
script: |
set -e
cd /home/ubuntu/fileshare

if [ ! -d ".git" ]; then
echo "Repository not found. Cloning repository..."
git clone https://github.com/sanket-rajput/DistributedFileSystem.git .
fi

echo "=== 1. Fetching latest commit and resetting repository ==="
git fetch origin main
git checkout main
git reset --hard ${{ github.sha }}

DEPLOYED_SHA=$(git rev-parse HEAD)

if [ "$DEPLOYED_SHA" != "${{ github.sha }}" ]; then
echo "ERROR: Deployment SHA mismatch!"
echo "Expected: ${{ github.sha }}"
echo "Actual: $DEPLOYED_SHA"
exit 1
fi

echo "SHA verified: $DEPLOYED_SHA"

echo "=== 2. Rebuilding Docker Compose Images ==="
docker compose build

echo "=== 3. Starting Docker Compose Services ==="
docker compose up -d

echo "=== 4. Polling Application Health Check ==="
SUCCESS=0
for i in $(seq 1 30); do
echo "Polling http://localhost:8080/actuator/health (Attempt $i/30)..."
HTTP_STATUS=$(curl -s -o /tmp/health.json -w "%{http_code}" http://localhost:8080/actuator/health || true)
if [ "$HTTP_STATUS" -eq 200 ] && grep -q '"status":"UP"' /tmp/health.json; then
echo "Health check SUCCESSFUL: $(cat /tmp/health.json)"
SUCCESS=1
break
fi
sleep 5
done

if [ $SUCCESS -ne 1 ]; then
echo "ERROR: Health check failed!"
echo "=== Container Status ==="
docker compose ps
echo "=== App Logs ==="
docker compose logs --tail=100 app
echo "=== Frontend Logs ==="
docker compose logs --tail=100 frontend
exit 1
fi
echo "=== Deployment Completed Successfully ==="
File renamed without changes.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Distributed File Sharing Platform — Full Stack System
# Distributed File Sharing Platform : Full Stack System

[![CI/CD Pipeline](https://img.shields.io/badge/CI%2FCD-passing-brightgreen)](.github/workflows/ci.yml)
[![Java 21](https://img.shields.io/badge/Java-21-blue.svg)](https://www.oracle.com/java/)
Expand Down
14 changes: 7 additions & 7 deletions deploy/setup-ec2.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env bash
# ==============================================================================
# First-Time EC2 Instance Setup Script (Ubuntu 22.04 LTS)
# Installs Docker, Docker Compose, Git, and clones the repository.
# First-Time EC2 Instance Setup Script (Ubuntu 26.04 / 24.04 / 22.04 LTS)
# Installs Docker, Docker Compose, Git, and clones the repository into /home/ubuntu/fileshare
# Usage: ./setup-ec2.sh [REPO_URL]
# ==============================================================================

set -euo pipefail

REPO_URL="${1:-https://github.com/your-username/DisfileSys.git}"
TARGET_DIR="DisfileSys"
REPO_URL="${1:-https://github.com/sanket-rajput/DistributedFileSystem.git}"
TARGET_DIR="/home/ubuntu/fileshare"

echo "=== 1. Updating System Packages ==="
sudo apt-get update -y
Expand All @@ -29,7 +29,7 @@ sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plug
echo "=== 3. Adding 'ubuntu' User to Docker Group ==="
sudo usermod -aG docker ubuntu || true

echo "=== 4. Cloning Repository ==="
echo "=== 4. Cloning Repository into $TARGET_DIR ==="
if [ ! -d "$TARGET_DIR" ]; then
git clone "$REPO_URL" "$TARGET_DIR"
else
Expand All @@ -43,7 +43,7 @@ echo " Setup complete!"
echo " NEXT STEPS:"
echo " 1. Log out and back in (or run 'newgrp docker') so group permissions take effect."
echo " 2. Navigate to repository: cd $TARGET_DIR"
echo " 3. Create your production .env file: cp .env.example .env"
echo " 4. Edit .env with your real Render DB credentials and Elastic IP."
echo " 3. Create production .env file: cp .env.example .env"
echo " 4. Edit .env with real production secrets (JWT_SECRET, POSTGRES_PASSWORD, etc.)."
echo " 5. Start containers: docker compose up -d --build"
echo "=============================================================================="
41 changes: 33 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ services:
- certbot_www:/var/www/certbot
- certbot_certs:/etc/letsencrypt

postgres:
image: postgres:16-alpine
container_name: fileshare-postgres
restart: always

environment:
POSTGRES_DB: ${POSTGRES_DB:-fileshare_db}
POSTGRES_USER: ${POSTGRES_USER:-fileshare_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

ports:
- "127.0.0.1:${POSTGRES_PORT:-5432}:5432"

volumes:
- postgres_data:/var/lib/postgresql/data

healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-fileshare_user} -d ${POSTGRES_DB:-fileshare_db}"]
interval: 5s
timeout: 5s
retries: 5

app:
build:
context: .
Expand All @@ -45,17 +67,17 @@ services:
restart: always

ports:
- "${SERVER_PORT:-8080}:8080"
- "127.0.0.1:${SERVER_PORT:-8080}:8080"

environment:
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-local}
POSTGRES_HOST: ${POSTGRES_HOST}
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_DB: ${POSTGRES_DB:-fileshare_db}
POSTGRES_USER: ${POSTGRES_USER:-fileshare_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL:-jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT:-5432}/${POSTGRES_DB}?sslmode=require}
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER}
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL:-jdbc:postgresql://${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-fileshare_db}}
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-fileshare_user}
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
MINIO_ENDPOINT: http://minio:9000
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
Expand All @@ -68,6 +90,8 @@ services:
JWT_EXPIRATION_MS: ${JWT_EXPIRATION_MS:-86400000}

depends_on:
postgres:
condition: service_healthy
minio:
condition: service_healthy
redis:
Expand Down Expand Up @@ -95,7 +119,7 @@ services:

ports:
- "127.0.0.1:${MINIO_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
- "127.0.0.1:${MINIO_CONSOLE_PORT:-9001}:9001"

volumes:
- minio_data:/data
Expand Down Expand Up @@ -157,6 +181,7 @@ services:
retries: 5

volumes:
postgres_data:
minio_data:
redis_data:
kafka_data:
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,15 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>-Dnet.bytebuddy.experimental=true</argLine>
<argLine>-Dnet.bytebuddy.experimental=true @{argLine}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>-Dnet.bytebuddy.experimental=true</argLine>
<argLine>-Dnet.bytebuddy.experimental=true @{argLine}</argLine>
</configuration>
<executions>
<execution>
Expand Down
104 changes: 104 additions & 0 deletions src/test/java/com/fileshare/auth/security/JwtServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.fileshare.auth.security;

import com.fileshare.user.entity.Role;
import com.fileshare.user.entity.User;
import com.fileshare.user.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.Authentication;

import java.util.Collections;
import java.util.Optional;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class JwtServiceTest {

@Mock
private UserRepository userRepository;

@Mock
private Authentication authentication;

private JwtTokenProvider jwtTokenProvider;
private static final String TEST_SECRET = "404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970";
private static final long EXPIRATION_MS = 3600000L; // 1 hour

@BeforeEach
void setUp() {
jwtTokenProvider = new JwtTokenProvider(TEST_SECRET, EXPIRATION_MS);
}

@Test
@DisplayName("Token generation produces a non-null, well-formed token for a mocked user")
void generateToken_ProducesNonNullValidToken() {
UUID userId = UUID.randomUUID();
User mockUser = User.builder()
.id(userId)
.email("user@example.com")
.passwordHash("hashed_pass")
.role(Role.USER)
.build();

UserPrincipal principal = UserPrincipal.create(mockUser);
when(authentication.getPrincipal()).thenReturn(principal);

String token = jwtTokenProvider.generateToken(authentication);

assertThat(token).isNotNull().isNotEmpty();
assertThat(token.split("\\.")).hasSize(3); // Standard JWT header.payload.signature format
assertThat(jwtTokenProvider.getEmailFromToken(token)).isEqualTo("user@example.com");
assertThat(jwtTokenProvider.getUserIdFromToken(token)).isEqualTo(userId);
}

@Test
@DisplayName("Token validation succeeds for a valid token")
void validateToken_ValidToken_ReturnsTrue() {
UUID userId = UUID.randomUUID();
UserPrincipal principal = new UserPrincipal(userId, "valid@example.com", "pass", Role.USER, Collections.emptyList());
when(authentication.getPrincipal()).thenReturn(principal);

String token = jwtTokenProvider.generateToken(authentication);

boolean isValid = jwtTokenProvider.validateToken(token);

assertThat(isValid).isTrue();
}

@Test
@DisplayName("Token validation fails for an expired token")
void validateToken_ExpiredToken_ReturnsFalse() {
JwtTokenProvider expiredTokenProvider = new JwtTokenProvider(TEST_SECRET, -5000L); // expired 5s ago
UUID userId = UUID.randomUUID();
UserPrincipal principal = new UserPrincipal(userId, "expired@example.com", "pass", Role.USER, Collections.emptyList());
when(authentication.getPrincipal()).thenReturn(principal);

String expiredToken = expiredTokenProvider.generateToken(authentication);

boolean isValid = jwtTokenProvider.validateToken(expiredToken);

assertThat(isValid).isFalse();
}

@Test
@DisplayName("Token validation fails for a tampered token")
void validateToken_TamperedToken_ReturnsFalse() {
UUID userId = UUID.randomUUID();
UserPrincipal principal = new UserPrincipal(userId, "tampered@example.com", "pass", Role.USER, Collections.emptyList());
when(authentication.getPrincipal()).thenReturn(principal);

String validToken = jwtTokenProvider.generateToken(authentication);
String tamperedToken = validToken + "invalid_signature";

boolean isValid = jwtTokenProvider.validateToken(tamperedToken);

assertThat(isValid).isFalse();
}
}
Loading
Loading