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: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Backend Docker ignores
target
.git
.github
.env
.idea
.vscode
*.iml
database
backups
24 changes: 16 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
# IMPORTANT: This is a template file. Copy to .env and fill in your values.
# DO NOT commit .env file to version control!

# Database Configuration
DB_URL=jdbc:postgresql://your-host:port/database
# Database Configuration (Supabase PostgreSQL)
DB_URL=jdbc:postgresql://your-host:5432/postgres
DB_USERNAME=your-username
DB_PASSWORD=your-password

# Server Configuration
SERVER_PORT=8080

# JWT Configuration
JWT_SECRET=secret_key
JWT_EXPIRATION=
REFRESH_TOKEN_EXPIRATION=
JWT_SECRET=your-jwt-secret-key
JWT_EXPIRATION=900000
REFRESH_TOKEN_EXPIRATION=604800000

# Supabase Configurations
SUPABASE_URL=
SUPABASE_SERVICE_KEY=
# Python Microservices URLs (internal networking in production)
PYTHON_CALL_ANALYSIS_URL=http://localhost:5001
PYTHON_FACIAL_RECOGNITION_URL=http://localhost:5002

# Supabase Configuration
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=your-supabase-service-key
SUPABASE_BUCKET=criminal-photos

# CORS Configuration (comma-separated origins, use * for all)
CORS_ALLOWED_ORIGINS=*
59 changes: 59 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# =============================================
# CrimeLink Analyzer - Backend CI/CD
# Repo: github.com/arosha-w/CrimeLinkAnalyzer_backend
# Docker build → ECR → ECS Fargate
# =============================================
#
# Required GitHub Secrets:
# AWS_ACCESS_KEY_ID
# AWS_SECRET_ACCESS_KEY
# AWS_REGION (e.g., ap-south-1)
# AWS_ACCOUNT_ID (12-digit AWS account ID)
# ECS_CLUSTER_NAME (e.g., crimelink-cluster)
# ECS_SERVICE_NAME (e.g., crimelink-backend-service)

name: Deploy Backend

on:
push:
branches: [main]
workflow_dispatch:

env:
ECR_REPOSITORY: crimelink-backend
AWS_REGION: ${{ secrets.AWS_REGION }}

jobs:
build-and-deploy:
name: Build & Deploy to ECS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}

- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2

- name: Build, tag, and push Docker image
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest

- name: Force new ECS deployment
run: |
aws ecs update-service \
--cluster ${{ secrets.ECS_CLUSTER_NAME }} \
--service ${{ secrets.ECS_SERVICE_NAME }} \
--force-new-deployment
Comment on lines +54 to +59
55 changes: 55 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# =============================================
# CrimeLink Analyzer - Spring Boot Backend
# Multi-stage build: Maven → JRE 21
# =============================================

# Stage 1: Build with Maven
FROM eclipse-temurin:21-jdk-alpine AS builder

WORKDIR /app

# Copy Maven wrapper and POM first (better caching)
COPY mvnw ./
COPY .mvn .mvn
COPY pom.xml ./

# Make mvnw executable and fix Windows CRLF line endings
RUN chmod +x mvnw && sed -i 's/\r$//' mvnw

# Download dependencies (cached unless pom.xml changes)
RUN ./mvnw dependency:go-offline -B

# Copy source code
COPY src src

# Build the application (skip tests for faster builds)
RUN ./mvnw package -DskipTests -B

# Stage 2: Runtime with minimal JRE
FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Copy the built JAR from builder
COPY --from=builder /app/target/*.jar app.jar

# Create directories for backups and uploads
RUN mkdir -p /app/backups && chown -R appuser:appgroup /app

# Switch to non-root user
USER appuser

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1

# JVM tuning for containers
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
52 changes: 52 additions & 0 deletions aws/ecs-task-def.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"family": "crimelink-backend",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "backend",
"image": "YOUR_ACCOUNT_ID.dkr.ecr.YOUR_REGION.amazonaws.com/crimelink-backend:latest",
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{ "name": "SERVER_PORT", "value": "8080" },
{ "name": "SUPABASE_BUCKET", "value": "criminal-photos" },
{ "name": "CORS_ALLOWED_ORIGINS", "value": "https://your-cloudfront-domain.cloudfront.net" }
],
"secrets": [
{ "name": "DB_URL", "valueFrom": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/crimelink/db-url" },
{ "name": "DB_USERNAME", "valueFrom": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/crimelink/db-username" },
{ "name": "DB_PASSWORD", "valueFrom": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/crimelink/db-password" },
{ "name": "JWT_SECRET", "valueFrom": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/crimelink/jwt-secret" },
{ "name": "SUPABASE_URL", "valueFrom": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/crimelink/supabase-url" },
{ "name": "SUPABASE_SERVICE_KEY", "valueFrom": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/crimelink/supabase-service-key" },
{ "name": "PYTHON_CALL_ANALYSIS_URL", "valueFrom": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/crimelink/call-analysis-url" },
{ "name": "PYTHON_FACIAL_RECOGNITION_URL", "valueFrom": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/crimelink/facial-recognition-url" }
],
"healthCheck": {
"command": ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/crimelink-backend",
"awslogs-region": "YOUR_REGION",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
Binary file added backend_logs.txt
Binary file not shown.
21 changes: 12 additions & 9 deletions src/main/java/com/crimeLink/analyzer/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;

@org.springframework.beans.factory.annotation.Value("${cors.allowed-origins:*}")
private String corsAllowedOrigins;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
Expand Down Expand Up @@ -127,15 +130,15 @@ public PasswordEncoder passwordEncoder() {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Use allowedOriginPatterns for wildcard support with credentials
// For production, replace with specific origins
configuration.setAllowedOriginPatterns(List.of("*"));
// Or use specific origins (recommended for production):
// configuration.setAllowedOrigins(Arrays.asList(
// "http://localhost:5173",
// "http://localhost:3000",
// "https://yourdomain.com"
// ));
// Use env var CORS_ALLOWED_ORIGINS to configure origins
// Default: * (all origins) — restrict for production
Comment on lines 130 to +134
if ("*".equals(corsAllowedOrigins)) {
configuration.setAllowedOriginPatterns(List.of("*"));
} else {
configuration.setAllowedOrigins(
Arrays.asList(corsAllowedOrigins.split(","))
);
}
Comment on lines +135 to +141
configuration.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
configuration.setAllowedHeaders(List.of("*"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public UserController(UserService service) {
this.service = service;
}

@GetMapping("/field-officers")
public List<User> getFieldOfficers() {
return service.getFieldOfficers();
}
Comment on lines +19 to 22
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

@RestController
@RequestMapping("/api/vehicles")
@CrossOrigin(origins = {"http://localhost:5173", "http://localhost:3000"})
public class VehicleController {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class CallAnalysisService {
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;

@Value("${python.call-analysis.url:http://localhost:5001}")
@Value("${python.call-analysis.url}")
private String callAnalysisServiceUrl;

public CallAnalysisService(RestTemplate restTemplate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class FacialRecognitionService {
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;

@Value("${python.facial-recognition.url:http://localhost:5002}")
@Value("${python.facial-recognition.url}")
private String facialRecognitionServiceUrl;

public FacialRecognitionService(RestTemplate restTemplate) {
Expand Down
10 changes: 7 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ jwt.secret=${JWT_SECRET}
jwt.expiration=${JWT_EXPIRATION:900000}
jwt.refresh-expiration=${REFRESH_TOKEN_EXPIRATION:604800000}

# Python Microservices Configuration
python.call-analysis.url=${PYTHON_CALL_ANALYSIS_URL:http://localhost:5001}
python.facial-recognition.url=${PYTHON_FACIAL_RECOGNITION_URL:http://localhost:5002}
# Python Microservices Configuration (REQUIRED — no defaults)
python.call-analysis.url=${PYTHON_CALL_ANALYSIS_URL}
python.facial-recognition.url=${PYTHON_FACIAL_RECOGNITION_URL}

#Supabase Configuration
supabase.url=${SUPABASE_URL}
Expand All @@ -50,3 +50,7 @@ spring.servlet.multipart.max-request-size=50MB

# Backup Configuration
backup.directory=${BACKUP_DIR:./backups}

# CORS Configuration
cors.allowed-origins=${CORS_ALLOWED_ORIGINS:*}
spring.datasource.hikari.data-source-properties.prepareThreshold=0
Loading