Skip to content

freefair/issue-tracker

Repository files navigation

Issue Tracker

Modern, self-hosted issue tracker with Kotlin/Spring Boot backend and Next.js frontend. Features a powerful Kanban board, backlog management, advanced search, and drag & drop functionality.

Tech Stack

Backend

  • Kotlin 1.9+ - Modern JVM language
  • Spring Boot 3.x - Application framework
  • Spring WebFlux - Reactive web framework
  • Spring Data R2DBC - Reactive database access
  • Kotlin Coroutines - Suspend functions & Flow for async operations
  • PostgreSQL - Database (required)
  • Flyway - Database migrations
  • Gradle (Kotlin DSL) - Build tool
  • Java 21 - Runtime

Frontend

  • Next.js 16 - React framework (App Router)
  • React 19 - UI library
  • TypeScript 5.9 - Type safety (strict mode)
  • Tailwind CSS - Utility-first styling
  • @dnd-kit/react - Accessible drag & drop
  • Static Export - Served from backend /static

Features

🗂️ Multi-Board Organization

  • Create unlimited boards for projects, teams, or workflows
  • Quick board switching via sidebar
  • Filter boards by name (client-side search)
  • Board state persisted in URL (?board={uuid})

📊 Three-View Workflow

Board View (Kanban)

  • 4 status columns: To Do, In Progress, Ready for Deployment, Done
  • Drag & drop tasks between columns
  • Keyboard navigation support

Backlog View

  • Custom categories (e.g., "Critical", "Tech Debt", "Nice to have")
  • Drag & drop to reorder categories by priority
  • Drag & drop tasks within and between categories
  • Uncategorized section for unassigned tasks
  • Quick promotion from Backlog to To Do

Archive View

  • Review completed tasks
  • Search archived tasks
  • Restore tasks to active columns

🔍 Advanced Search

  • Structured Query Syntax:
    • Board:[Name] - Search specific board
    • Tag:[TagName] - Filter by tag
    • Status:[StatusName] - Filter by status
  • Query Chips - Structured filters become visual bubbles
  • Autocomplete - Field names and values suggested
  • Board/Global Scope - Search current board or all boards
  • Keyboard Shortcut - Cmd+K / Ctrl+K to focus search
  • Debounced - 300ms delay to reduce API load

🏷️ Flexible Tagging

  • Multiple tags per task
  • Tag autocomplete based on existing tags
  • Visual tag chips in UI
  • Search integration

📱 Responsive Design

  • Desktop: Full multi-column layout with persistent sidebar
  • Mobile: Collapsible sidebar, touch-optimized drag & drop
  • Touch support: Long-press to drag, swipe-friendly

⚡ Performance & UX

  • Optimistic UI updates (instant feedback)
  • Reactive backend (non-blocking I/O)
  • Indexed database queries
  • Client-side filtering where possible

Project Structure

issue-tracker/
├── backend/
│   └── src/main/
│       ├── kotlin/com/issuetracker/
│       │   ├── domain/          # Entities (Board, Task, BacklogCategory)
│       │   ├── dto/             # Request/Response DTOs
│       │   ├── repository/      # R2DBC repositories
│       │   ├── service/         # Business logic
│       │   ├── web/             # REST controllers
│       │   └── exception/       # Custom exceptions
│       └── resources/
│           ├── db/migration/    # Flyway SQL migrations (V1__, V2__, ...)
│           ├── application.properties  # Main config
│           └── static/          # Deployed frontend (auto-generated)
├── frontend/
│   ├── app/                     # Next.js App Router pages
│   ├── components/              # React components
│   ├── lib/                     # API client, utilities
│   ├── types/                   # TypeScript interfaces
│   ├── out/                     # Build output (→ backend/static)
│   └── next.config.mjs          # Static export config
├── gradlew                      # Gradle wrapper
├── build.gradle.kts             # Root build config
├── CLAUDE.md                    # Development guidelines
├── FEATURES.md                  # Detailed feature documentation
└── openapi.json                 # API specification

Installation

Option 1: Docker with PostgreSQL

The easiest way to run Issue Tracker is using Docker Compose with PostgreSQL:

docker-compose.yml:

version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    container_name: issue-tracker-db
    environment:
      POSTGRES_DB: issuetracker
      POSTGRES_USER: issuetracker
      POSTGRES_PASSWORD: changeme
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U issuetracker"]
      interval: 10s
      timeout: 5s
      retries: 5

  issue-tracker:
    image: ghcr.io/freefair/issue-tracker:latest
    container_name: issue-tracker
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "8080:8080"
    environment:
      # Spring Profile
      SPRING_PROFILES_ACTIVE: prod

      # Database Configuration
      DB_HOST: postgres
      DB_PORT: 5432
      DB_NAME: issuetracker
      DB_USER: issuetracker
      DB_PASSWORD: changeme

      # CORS Configuration (set to your domain in production)
      CORS_ALLOWED_ORIGINS: http://localhost:8080
    restart: unless-stopped

volumes:
  postgres_data:

Start:

docker-compose up -d

View logs:

docker-compose logs -f issue-tracker

Stop:

docker-compose down

Access the application at http://localhost:8080

Option 2: Docker with External Database

If you already have a PostgreSQL database:

docker run -d \
  --name issue-tracker \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DB_HOST=your-db-host \
  -e DB_PORT=5432 \
  -e DB_NAME=issuetracker \
  -e DB_USER=your-db-user \
  -e DB_PASSWORD=your-db-password \
  -e CORS_ALLOWED_ORIGINS=https://yourdomain.com \
  ghcr.io/freefair/issue-tracker:latest

Option 3: From Source

For development or custom builds, see the Getting Started section below.


Getting Started

Prerequisites

  • Java 21 (via jenv, sdkman, or manual install)
  • Node.js 18+ and npm (for frontend development)
  • PostgreSQL 12+ (for local development)

Quick Start (Development)

  1. Setup PostgreSQL database:

    # Create database and user
    psql -U postgres -c "CREATE DATABASE issuetracker;"
    psql -U postgres -c "CREATE USER issuetracker WITH PASSWORD 'postgres';"
    psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE issuetracker TO issuetracker;"
  2. Build and deploy frontend:

    cd frontend
    npm install
    npm run build         # Builds to out/
    npm run deploy        # Copies out/ → backend/src/main/resources/static/
  3. Start backend:

    cd ..
    ./gradlew bootRun     # Uses dev profile with localhost PostgreSQL
  4. Access application:

Development Workflow

Frontend hot reload:

cd frontend
npm run dev     # Starts Next.js dev server on http://localhost:3000

Backend auto-restart:

./gradlew bootRun --continuous

Full deployment (after changes):

# Kill existing backend
pkill -f "gradle.*bootRun"

# Build and deploy frontend
cd frontend && npm run deploy

# Restart backend
cd ..
./gradlew bootRun > /tmp/backend.log 2>&1 &

# Verify
sleep 5 && curl -s http://localhost:8080/api/boards | jq

API Endpoints

Boards

GET    /api/boards           # List all boards
GET    /api/boards/{id}      # Get board by ID
POST   /api/boards           # Create board
PUT    /api/boards/{id}      # Update board
DELETE /api/boards/{id}      # Delete board (cascades to tasks)

Tasks

GET    /api/boards/{boardId}/tasks              # List tasks for board
GET    /api/boards/{boardId}/tasks?status=TODO  # Filter by status
GET    /api/tasks/{id}                          # Get task by ID
POST   /api/boards/{boardId}/tasks              # Create task
PATCH  /api/tasks/{id}                          # Update task (partial)
PATCH  /api/tasks/{id}/move                     # Move task (status + position)
DELETE /api/tasks/{id}                          # Delete task

Search

GET    /api/tasks/search?boardId={id}&q={query} # Search in board
GET    /api/tasks/search/global?q={query}       # Search across all boards

Tags

GET    /api/boards/{boardId}/tags               # Get all tags for board
GET    /api/boards/{boardId}/tags?q={query}     # Filter tags

Backlog Categories

GET    /api/boards/{boardId}/backlog-categories      # List categories
POST   /api/boards/{boardId}/backlog-categories      # Create category
GET    /api/backlog-categories/{id}                  # Get category
PATCH  /api/backlog-categories/{id}                  # Update category
DELETE /api/backlog-categories/{id}                  # Delete category

Example Requests

Create Board:

curl -X POST http://localhost:8080/api/boards \
  -H "Content-Type: application/json" \
  -d '{"name": "My Project", "description": "Main development board"}'

Create Task:

curl -X POST http://localhost:8080/api/boards/{boardId}/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Implement user authentication",
    "description": "Add JWT-based auth",
    "status": "TODO",
    "position": 0,
    "tags": ["backend", "security"]
  }'

Update Task:

curl -X PATCH http://localhost:8080/api/tasks/{taskId} \
  -H "Content-Type: application/json" \
  -d '{"status": "IN_PROGRESS", "position": 1}'

Search Tasks:

curl "http://localhost:8080/api/tasks/search?boardId={id}&q=authentication"
curl "http://localhost:8080/api/tasks/search/global?q=Tag:backend"

Database

Schema Overview

Tables:

  • boards - Board definitions
  • tasks - All tasks (linked to boards)
  • backlog_categories - Custom backlog categories per board
  • flyway_schema_history - Migration tracking

Key Relationships:

  • tasks.board_idboards.id (CASCADE DELETE)
  • tasks.backlog_category_idbacklog_categories.id (SET NULL)
  • backlog_categories.board_idboards.id (CASCADE DELETE)

Migrations

Database schema managed with Flyway. Migrations located in:

backend/src/main/resources/db/migration/
├── V1__initial_schema.sql
├── V2__add_backlog_categories.sql
└── ...

Creating new migrations:

  1. Create file: V{N}__{description}.sql
  2. Use sequential version numbers (V1, V2, V3, ...)
  3. Flyway auto-applies on next backend start
  4. Never modify existing migrations - always create new ones

Reset database:

# Drop and recreate database
psql -U postgres -c "DROP DATABASE IF EXISTS issuetracker;"
psql -U postgres -c "CREATE DATABASE issuetracker;"
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE issuetracker TO issuetracker;"

# Restart application - Flyway will run migrations
pkill -f gradle
./gradlew bootRun

Configuration

Environment Variables

Frontend:

  • NEXT_PUBLIC_API_URL - API base URL (default: http://localhost:8080/api)

Backend:

Database Configuration:

  • DB_HOST - Database host (default: localhost)
  • DB_PORT - Database port (default: 5432)
  • DB_NAME - Database name (default: issuetracker)
  • DB_USER - Database user (default: issuetracker)
  • DB_PASSWORD - Database password (default: changeme)

Application Configuration:

  • SPRING_PROFILES_ACTIVE - Spring profile (dev or prod)
    • dev - Development settings (hardcoded localhost PostgreSQL)
    • prod - Production settings (uses environment variables)
  • CORS_ALLOWED_ORIGINS - Required for production - Comma-separated allowed origins (e.g., https://yourdomain.com,https://www.yourdomain.com)
  • SERVER_PORT - Server port (default: 8080)
  • LOGGING_LEVEL_ROOT - Log level (default: INFO)

Java Version:

# Using jenv
jenv local 21

# Or set JAVA_HOME
export JAVA_HOME=/path/to/java-21

Production Deployment

Build for Production

# 1. Build frontend (static export)
cd frontend
npm run build
npm run deploy

# 2. Build backend JAR (includes frontend)
cd ..
./gradlew build

# 3. JAR location
# backend/build/libs/backend-*.jar

Run Production JAR

java -jar backend/build/libs/backend-*.jar

Environment Configuration

Required:

  • SPRING_PROFILES_ACTIVE=prod - Enable production profile
  • Database connection (see below)
  • CORS_ALLOWED_ORIGINS - Your production domain(s)

PostgreSQL Database Setup:

First, create the database:

CREATE DATABASE issuetracker;
CREATE USER issuetracker WITH PASSWORD 'your_secure_password';
GRANT ALL PRIVILEGES ON DATABASE issuetracker TO issuetracker;

Run with PostgreSQL:

export SPRING_PROFILES_ACTIVE=prod
export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=issuetracker
export DB_USER=issuetracker
export DB_PASSWORD=your_secure_password
export CORS_ALLOWED_ORIGINS=https://tracker.example.com

java -jar backend/build/libs/backend-*.jar

Or use a .env file and docker-compose (see Installation)

Architecture Highlights

Backend Patterns

R2DBC Persistable Pattern:

  • Entities implement Persistable<UUID> interface
  • Manual UUID generation via UUID.randomUUID()
  • Call .withPersistedFlag() after loading from DB

Reactive Streams:

  • Kotlin Flow for streaming responses
  • Suspend functions for single-value responses
  • Non-blocking I/O throughout

Frontend Patterns

Optimistic UI Updates:

  • Immediate local state update on user action
  • Backend update in background
  • Revert only on error

URL State Management:

  • Board selection: ?board={uuid}
  • View selection: ?view=board|backlog|archive
  • Both required for full state restoration

Drag & Drop (@dnd-kit/react):

  • Unified API across all views
  • Position recalculation from 0 on every move
  • Update ALL affected tasks, never use relative positions

Development Tips

View backend logs:

tail -f /tmp/backend.log

Check health:

curl http://localhost:8080/health
curl http://localhost:8080/api/boards

Frontend dev server:

cd frontend
npm run dev
# Access at http://localhost:3000

Clean build:

rm -rf frontend/out frontend/.next backend/src/main/resources/static

Documentation

  • CLAUDE.md - Development guidelines, architecture patterns, gotchas
  • FEATURES.md - Detailed feature descriptions and workflows
  • openapi.json - Complete API specification (OpenAPI 3.0)

Code Quality

  • Clean Architecture - Clear separation: Domain, Repository, Service, Controller
  • Type Safety - TypeScript (strict) + Kotlin type system
  • Reactive - Non-blocking I/O with coroutines and Flow
  • Validation - Jakarta Bean Validation on all inputs
  • Error Handling - Consistent exception handling with meaningful messages

Security Notes

Current State (Development):

  • No authentication (single-user mode)
  • CORS disabled by default
  • Input validation via Bean Validation
  • XSS prevention via React auto-escaping

Production Requirements:

  • ⚠️ Set CORS_ALLOWED_ORIGINS environment variable
  • Add authentication (JWT, OAuth, etc.)
  • Enable HTTPS
  • Add rate limiting
  • Review and sanitize all inputs

License

MIT

Support

For detailed development guidelines, see CLAUDE.md. For feature documentation, see FEATURES.md. For API details, see openapi.json.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages