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
185 changes: 165 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ A simple task management application with a Java Spring Boot backend and React f

## Features

- Create, read, update, and delete tasks
- Mark tasks as completed
- Filter tasks by status (all, active, completed)
- Set task priority (low, medium, high)
- Set due dates for tasks
- **Create tasks** - Add new tasks with title, description, priority, and due date
- **View tasks** - See all tasks with detailed information
- **Edit tasks** - Modify existing tasks including all fields and completion status
- **Delete tasks** - Remove tasks from the list
- **Toggle completion** - Quickly mark tasks as complete or incomplete
- **Filter tasks** - Filter by status (all, active, completed)
- **Priority levels** - Set task priority (low, medium, high) with visual indicators
- **Due dates** - Set and track task due dates
- **Form validation** - Client-side validation for required fields and data integrity
- **Error handling** - Comprehensive error messages for better user experience


## Tech Stack
Expand All @@ -33,22 +38,34 @@ task-manager/
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ β”œβ”€β”€ main/
β”‚ β”‚ β”‚ β”œβ”€β”€ java/com/example/taskmanager/
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ config/ # Configuration classes (CORS, Web)
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ controller/ # REST API controllers
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ exception/ # Global exception handlers
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ model/ # Entity classes
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ repository/ # Data access layer
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ service/ # Business logic
β”‚ β”‚ β”‚ β”‚ └── TaskManagerApplication.java
β”‚ β”‚ β”‚ └── resources/
β”‚ β”‚ β”‚ └── application.properties
β”‚ β”œβ”€β”€ Dockerfile # Backend Docker configuration
β”‚ └── pom.xml # Maven configuration
└── frontend/ # React frontend
β”œβ”€β”€ public/ # Static files
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ components/ # React components
β”‚ β”œβ”€β”€ services/ # API service layer
β”‚ β”œβ”€β”€ App.js # Main application component
β”‚ └── index.js # Entry point
└── package.json # npm configuration
β”œβ”€β”€ frontend/ # React frontend
β”‚ β”œβ”€β”€ public/ # Static files
β”‚ β”œβ”€β”€ src/
β”‚ β”‚ β”œβ”€β”€ components/ # React components
β”‚ β”‚ β”‚ β”œβ”€β”€ TaskForm.js # Form for creating/editing tasks
β”‚ β”‚ β”‚ β”œβ”€β”€ TaskItem.js # Individual task component
β”‚ β”‚ β”‚ β”œβ”€β”€ TaskList.js # Task list container
β”‚ β”‚ β”‚ └── TaskFilter.js # Filter component
β”‚ β”‚ β”œβ”€β”€ services/ # API service layer
β”‚ β”‚ β”‚ └── taskService.js # API calls with error handling
β”‚ β”‚ β”œβ”€β”€ App.js # Main application component
β”‚ β”‚ β”œβ”€β”€ index.js # Entry point
β”‚ β”‚ └── index.css # Global styles
β”‚ β”œβ”€β”€ nginx.conf # Nginx configuration for production
β”‚ β”œβ”€β”€ Dockerfile # Frontend Docker configuration
β”‚ └── package.json # npm configuration
└── docker-compose.yml # Multi-container Docker setup
```

## Getting Started
Expand All @@ -58,35 +75,54 @@ task-manager/
- Java 17 or higher
- Node.js and npm
- Maven
- Docker (optional, for containerized deployment)

### Running the Backend
### Running with Docker (Recommended)

1. Navigate to the backend directory:
1. Build and run the entire stack:
```bash
docker build -t taskmanager-backend:dev -f backend/Dockerfile ./backend
docker build -t taskmanager-frontend:dev -f frontend/Dockerfile ./frontend
```

2. Run the containers:
```bash
docker run -d --name taskmanager-backend -p 8080:8080 taskmanager-backend:dev
docker run -d --name taskmanager-frontend -p 3000:80 taskmanager-frontend:dev
```

3. Access the application at http://localhost:3000

### Running Locally

#### Backend

1. Navigate to the backend directory:
```bash
cd backend
```

2. Build and run the Spring Boot application:
```
```bash
mvn spring-boot:run
```

3. The backend will start on http://localhost:8080

### Running the Frontend
#### Frontend

1. Navigate to the frontend directory:
```
```bash
cd frontend
```

2. Install dependencies:
```
```bash
npm install
```

3. Start the React development server:
```
```bash
npm start
```

Expand All @@ -101,3 +137,112 @@ task-manager/
- `PUT /api/tasks/{id}` - Update a task
- `PATCH /api/tasks/{id}/toggle` - Toggle task completion status
- `DELETE /api/tasks/{id}` - Delete a task

### Request/Response Examples

#### Create Task (POST /api/tasks)
```json
{
"title": "Complete project documentation",
"description": "Write comprehensive README and API docs",
"priority": "HIGH",
"dueDate": "2026-01-30T17:00:00.000Z",
"completed": false
}
```

#### Update Task (PUT /api/tasks/{id})
```json
{
"title": "Updated task title",
"description": "Updated description",
"priority": "MEDIUM",
"dueDate": "2026-02-01T17:00:00.000Z",
"completed": true
}
```

#### Response
```json
{
"id": 1,
"title": "Complete project documentation",
"description": "Write comprehensive README and API docs",
"priority": "HIGH",
"dueDate": "2026-01-30T17:00:00.000",
"completed": false,
"createdAt": "2026-01-22T00:00:00.000",
"updatedAt": "2026-01-22T00:00:00.000"
}
```

## Edit Task Functionality

The application includes comprehensive edit functionality:

### Frontend Edit Features

1. **Edit Button**: Each task item has an "Edit" button that triggers edit mode
2. **Form Population**: When editing, the form automatically populates with existing task data:
- Title
- Description
- Priority (LOW, MEDIUM, HIGH)
- Due Date
- Completion status (checkbox, only shown in edit mode)
3. **Visual Feedback**: The form title changes to "Edit Task" when in edit mode
4. **Cancel Option**: A "Cancel" button appears in edit mode to discard changes
5. **Form Validation**:
- Required field validation (title)
- Due date validation (prevents past dates for new tasks)
- Real-time error messages
6. **Auto-scroll**: Page automatically scrolls to the form when edit button is clicked
7. **Error Handling**: Clear error messages if update fails, keeping edit mode active for retry

### Backend Edit Support

1. **PUT Endpoint**: `/api/tasks/{id}` accepts all task fields for update
2. **Timestamp Management**: Automatically updates `updatedAt` timestamp
3. **Data Integrity**: Validates required fields (title)
4. **Error Responses**: Returns appropriate HTTP status codes:
- 200 OK - Successful update
- 400 Bad Request - Validation errors
- 404 Not Found - Task doesn't exist
5. **CORS Support**: Configured to accept requests from frontend origin

### Edit Workflow

1. User clicks "Edit" button on a task
2. Page scrolls to the top form
3. Form populates with existing task data
4. Form title changes to "Edit Task"
5. "Cancel" button appears next to "Update Task" button
6. Completion status checkbox is displayed
7. User modifies desired fields
8. On submit:
- Frontend validates input
- Sends PUT request to backend with all fields
- Backend validates and updates task
- Backend updates `updatedAt` timestamp
- Returns updated task
- Frontend updates task list with new data
- Form exits edit mode
9. If error occurs:
- Error message is displayed
- Form remains in edit mode
- User can retry or cancel

### Data Integrity Features

- Title is required for all tasks
- Timestamps are managed automatically by the backend
- Completion status is properly synchronized between toggle and edit operations
- Failed updates don't clear the form, allowing users to retry
- Task list updates reflect changes immediately upon successful save

## Development Notes

- H2 in-memory database is used for development (data is lost on restart)
- CORS is configured to allow requests from `http://localhost:3000`
- Backend runs on port 8080, frontend on port 3000
- All API endpoints are prefixed with `/api`
- Docker support available for containerized deployment
17 changes: 17 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Build artifacts
target/
*.jar
*.war
*.ear

# IDE
.idea/
.vscode/
*.iml

# Logs
*.log

# OS files
.DS_Store
Thumbs.db
28 changes: 28 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Use Maven with JDK 17 for building
FROM maven:3.9-eclipse-temurin-17 AS build

WORKDIR /app

# Copy pom.xml and download dependencies (for layer caching)
COPY pom.xml .
RUN mvn dependency:go-offline -B

# Copy source code
COPY src ./src

# Build the application
RUN mvn clean package -DskipTests

# Use JDK 17 for runtime
FROM eclipse-temurin:17-jre

WORKDIR /app

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

# Expose the default Spring Boot port
EXPOSE 8080

# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]
Comment on lines +16 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docker container runs as root user, which creates a security risk. Running applications as root violates the principle of least privilege and increases the attack surface if the container is compromised.

Suggested change
# Use JDK 17 for runtime
FROM eclipse-temurin:17-jre
WORKDIR /app
# Copy the built JAR from build stage
COPY --from=build /app/target/*.jar app.jar
# Expose the default Spring Boot port
EXPOSE 8080
# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]
# Use JDK 17 for runtime
FROM eclipse-temurin:17-jre
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
# Copy the built JAR from build stage
COPY --from=build /app/target/*.jar app.jar
# Change ownership to non-root user
RUN chown appuser:appuser app.jar
# Switch to non-root user
USER appuser
# Expose the default Spring Boot port
EXPOSE 8080
# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.taskmanager.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000", "http://localhost:80")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
Comment on lines +13 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ›‘ Security Vulnerability: CORS configuration allows overly permissive access that could enable cross-site attacks1. The wildcard for allowedHeaders combined with allowCredentials creates a security risk, and localhost:80 is an unusual configuration that may not work as expected.

Suggested change
.allowedOrigins("http://localhost:3000", "http://localhost:80")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.allowedOrigins("")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("Content-Type", "Authorization")
.allowCredentials(true)

Footnotes

  1. CWE-942: Permissive Cross-domain Policy with Untrusted Domains - https://cwe.mitre.org/data/definitions/942.html ↩

.maxAge(3600);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.example.taskmanager.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});

Map<String, Object> response = new HashMap<>();
response.put("timestamp", LocalDateTime.now());
response.put("status", HttpStatus.BAD_REQUEST.value());
response.put("message", "Validation failed");
response.put("errors", errors);

return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
Map<String, Object> response = new HashMap<>();
response.put("timestamp", LocalDateTime.now());
response.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
response.put("message", "An error occurred: " + ex.getMessage());

return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
Comment on lines +36 to +44

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ›‘ Security Vulnerability: Generic exception handler exposes internal error details that could leak sensitive information to attackers1. The error message from ex.getMessage() may contain stack traces, database details, or other internal system information.

Suggested change
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
Map<String, Object> response = new HashMap<>();
response.put("timestamp", LocalDateTime.now());
response.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
response.put("message", "An error occurred: " + ex.getMessage());
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
Map<String, Object> response = new HashMap<>();
response.put("timestamp", LocalDateTime.now());
response.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
response.put("message", "An internal server error occurred");
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}

Footnotes

  1. CWE-209: Information Exposure Through Error Messages - https://cwe.mitre.org/data/definitions/209.html ↩

}
2 changes: 1 addition & 1 deletion backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ server.port=8080

# CORS Configuration
spring.web.cors.allowed-origins=http://localhost:3000
spring.web.cors.allowed-methods=GET,POST,PUT,DELETE
spring.web.cors.allowed-methods=GET,POST,PUT,PATCH,DELETE
spring.web.cors.allowed-headers=*
36 changes: 36 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: '3.8'

services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: taskmanager-backend
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
networks:
- taskmanager-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/tasks"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Healthcheck command will fail because curl is not installed in the eclipse-temurin:17-jre base image. This will cause the container to be marked as unhealthy.

Suggested change
test: ["CMD", "curl", "-f", "http://localhost:8080/api/tasks"]
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", ""]

interval: 30s
timeout: 10s
retries: 3
start_period: 40s

frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: taskmanager-frontend
ports:
- "3000:80"
depends_on:
- backend
networks:
- taskmanager-network

networks:
taskmanager-network:
driver: bridge
Loading