A simple full-stack task management application inspired by Jira.
Built with Spring Boot + Angular, focusing on clean architecture, JWT authentication, and modern frontend practices.
Live Demo: https://82.165.51.255/
- π Authentication with JWT
- π Refresh token mechanism to renew access tokens
- π€ Role-based access control (Admin & User)
- π Task management (CRUD: Create, Read, Update, Delete)
- π€ Assign tasks to users
- π Status management: TODO, IN_PROGRESS, DONE
- π© Priority levels: CRITICAL, HIGH, MEDIUM, LOW
- π Advanced filtering, search & pagination
- π Dashboard with statistics, charts & recent tasks
- π Kanban Board with drag-and-drop (Angular CDK)
- π¬ Task comments with pagination
- π€ Collaboration: discussions per task
- π Task change history (audit log)
- π Activity feed (timeline of changes)
- π± Responsive UI with mobile-optimized layout (swipe / horizontal scroll)
- π Clean REST API design (Spring Boot best practices)
- π§± Layered architecture (Controller β Service β Repository)
- π DTO mapping & validation
- π Structured logging with correlationId for request tracing
- π Correlation ID propagation across requests
- π₯ Full HTTP request & response logging (method, URI, body, latency)
- π€ User-aware logging (logs include authenticated user context)
β οΈ Standardized error logging with stacktrace & context- π§Ύ Audit logging for business actions (create/update/delete tasks)
- π Activity tracking for debugging and system monitoring
- Unit tests for service layer (business logic validation)
- Integration tests for REST controllers (MockMvc)
- Repository tests with in-memory database (H2)
- Validation & error handling test coverage
- Unit tests for components, services, and pipes
- Mocked HTTP requests for isolated testing-
- Reactive forms & validation testing
- Observable-based async testing
- Full user flow testing (login β dashboard β task actions)
- Authentication handling (JWT / session reuse)
- UI interaction testing (filters, navigation, pagination)
- Stable async handling (auto-wait, non-flaky tests)
- Multi-browser support (Chromium, Firefox, WebKit)
- π³ Docker & Docker Compose setup for fullstack environment
- βοΈ Environment-based configuration (dev/prod ready)
- real time: sync kanban between users using websocket
- Offline-first: cache task and sync when it becomes online
- OIDC with keycloak (optional)
- File upload: save file local or in S3
- notification system :Notification {id, userId, message, isRead}. to trigger assign task, comment
- Java 17+
- Spring Boot 3
- Spring Security + JWT
- Spring Data JPA
- PostgreSQL / H2
- Lombok
- Angular 21
- RxJS (Observable, switchMap π₯)
- Angular Material / Tailwind CSS
- HttpClient
- Angular Routing
- Authentication guard and admin guard
backend/
βββ controller/ # REST controllers
βββ service/ # Business logic
βββ repository/ # Data access layer
βββ dto/ # Data Transfer Objects
βββ entity/ # JPA entities
βββ security/ # JWT + Spring Security
βββ config/ # App configurations
src/app/
βββ core/
β βββ auth.service.ts
β βββ auth.interceptor.ts
βββ features/
β βββ auth/
β β βββ login.component.ts
β βββ task/
β βββ task.component.ts
βββ models/
βββ app.routes.ts
npm install chart.js
npm install chartjs-plugin-datalabels
npm install @angular/material
ng add @angular/material
to use snackbar
style.css: @import '@angular/material/prebuilt-themes/indigo-pink.css';
npm install ngx-quill quill
style.css: @import 'quill/dist/quill.snow.css';
npm install @angular/cdk
npm install @ng-select/ng-select
Angular.jss
"styles": [
"src/material-theme.scss",
"node_modules/@ng-select/ng-select/themes/material.theme.css",
"src/styles.css"
]
npm install ngx-toastr
npm install @angular/animations
See full deployment guide here π DEPLOYMENT.md
[ Angular (build) ] β Nginx (serve frontend)
[ Spring Boot ] β backend API
[ H2 ] β database
FROM eclipse-temurin:17-jdk-jammy
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT [ "java", "-jar", "app.jar" ]
FROM node:20 as build
WORKDIR /app
# π₯ Copy dependency
COPY package*.json ./
# π₯ use ci to clean install matching platform
RUN npm ci
# copy source
COPY . .
# build
RUN npm run build
# -------- NGINX --------
FROM nginx:alpine
COPY --from=build /app/dist/TaskTrackerFrontend/browser/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
version: '3.8'
services:
backend:
build: ./TaskTracker
container_name: task-tracker-backend
ports:
- "8080:8080"
frontend:
build: ./TaskTrackerFrontend
container_name: task-tracker-frontend
ports:
- "80:80"
depends_on:
- backend
- start Docker Desktop
- run: docker-compose up --build
- start docker Image
- test: docker ps -> outpout: task-frontend ... 0.0.0.0:80->80/tcp task-backend ... 0.0.0.0:8080->8080/tcp
π Windows: delete node_modules localhost and install
cd frontend
rmdir /s /q node_modules
del package-lock.json
npm install
π Windows: build form sratch
docker-compose down -v
docker system prune -a
docker-compose up --build
TaskTracker.postman_collection.json
- Unit test: Vitest
- Component test: Angular TestBed
- E2E / System test: Playwright
Playwright installation
npm init playwright@latest
npx playwright install
playwright.config.ts
export default defineConfig({
use: {
baseURL: 'http://localhost:4200',
},
workers: 1, // π₯ trΓ‘nh race
webServer: {
command: 'npx ng serve --port 4200',
url: 'http://localhost:4200',
reuseExistingServer: false,
timeout: 120 * 1000,
},
});
/tests/dashboard.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Dashboard Page', () => {
test.beforeEach(async ({ page }) => {
// make sure in playwright.config.ts: baseURL: 'http://localhost:4200',
await page.goto('/login');
await page.fill('input[name="username"]', 'username');
await page.fill('input[name="password"]', 'password');
await Promise.all([page.waitForURL('**/dashboard'), page.click('button[type=submit]')]);
// await page.goto('http://localhost:4200'); // adjust nαΊΏu cαΊ§n
});
// β
1. Page loads
test('should load dashboard text', async ({ page }) => {
await page.waitForSelector('.title');
await expect(page.locator('.title')).toBeVisible();
await expect(page.locator('.title')).toContainText('Dashboard');
});
Minh Duc Ngo
This project is licensed under the MIT License - see the LICENSE file for details.