English · Türkçe
A full-stack, multi-role IT Service Management (ticketing) platform — Keycloak-secured, jBPM-orchestrated, AI-assisted, and fully observable.
Customers report technical problems, support agents resolve them under SLA rules, and managers monitor the operation through live dashboards. The system is built as a containerised, polyglot monorepo that demonstrates a production-shaped full-stack architecture: identity federation, workflow orchestration, asynchronous processing, observability, and an AI summarisation service.
- Key Features
- Architecture at a Glance
- Technology Stack
- Getting Started
- Local Development
- Project Structure
- Testing & Quality
- Deployment
- Screenshots
- Documentation
- License
- Full ticket lifecycle as a state machine:
NEW → IN_PROGRESS → WAITING_FOR_CUSTOMER → RESOLVED → CLOSED - Each ticket runs as a jBPM process instance — every status transition is driven through the workflow engine, including the side-effect transitions caused by claim / unclaim / assignment
- Claim/pool model — agents pull tickets from a product-scoped pool; multiple agents can collaborate on one ticket
- Manual assignment with agent capacity checks, resolution notes, and a full audit trail
- Per-priority SLA policies (
CRITICAL/HIGH/MEDIUM/LOW) with configurable resolution and warning thresholds - SLA clock pauses while a ticket is
WAITING_FOR_CUSTOMERorRESOLVED, resumes onIN_PROGRESSwith the paused time preserved (only active time counts down), and stops permanently once the ticket isCLOSED - A scheduler flags approaching and breached SLAs; the UI shows colour-coded countdown badges
- Chat-style ticket conversation with internal (agent-only) and external (customer-visible) comments
- Attachment upload with content validation — file type/size checks, keyword scan, and sensitive-data detection (tokens, private keys)
- Worklog time tracking per agent
- PDF export of ticket detail — configurable sections (comments, worklogs, attachments, audit log), language and orientation; preferences persisted per user so the modal reopens with last-used selections
- Multi-channel notifications (in-app feed + email simulation via Mailpit) for ticket, status, comment and SLA events
- Per-user notification preferences — scoped to the user's role union so only relevant event rows are shown — and fully localised notification content (rendered in the recipient's current language)
- Light / dark theme and English / Turkish UI, both shared with the Keycloak login screens
- User-selectable date format applied to every date across the UI (
31/12/2026,12/31/2026,2026-12-31,31.12.2026, or a localized month name) — persisted per user and synced across devices - Theme, language, date format and notification preferences are all set from the profile page
- Command palette (
Ctrl/Cmd+K) for quick navigation and ticket search, a role-aware guided onboarding tour for first-time users, and non-blocking toast notifications for action feedback
- Dedicated
llm-servicegenerates AI ticket summaries (Groq / Llama 3.1) in Turkish or English
- Role-scoped dashboards — a personal Overview for customers and a My Performance dashboard for agents, plus the global manager dashboard; admins, managers and leads can drill into any user's, agent's or product's dashboard (leads stay product-scoped)
- KPI summary, status distribution, ticket timeline, priority-SLA breakdown, agent performance leaderboard, product metrics, CSAT analytics (distribution / trend), daily-worklog charts and configurable stuck-ticket alerts — KPIs scope to a selected date range, all Caffeine-cached
- Keycloak SSO with users federated from OpenLDAP, OAuth2/OIDC, JWT, 2FA (TOTP), "remember me" and Keycloak-native password reset (e-mail relayed through Mailpit)
- Additive multi-role access control: a user holds a set of roles and their effective permissions are the union. Five roles —
customer,agent,lead_agent,admin,manager— span the operational, configuration and oversight axes;lead_agentis a Keycloak composite ofagent.customeris a singleton role — mutually exclusive with the staff roles — while the staff roles combine freely. Roles live in Keycloak and are cached in theuser_rolestable (Flyway V37), synced on/users/sync. - Distributed rate limiting (Bucket4j + Redis), method-level authorization (
@PreAuthorize+AuthRoleshelpers), internal service-to-service token auth
- Web (React) and mobile (React Native / Expo) clients with functional parity
- Internationalisation (English / Turkish) and light/dark theming across the SPA and the Keycloak login screens
- End-to-end observability: structured logs, distributed traces and metrics in OpenSearch
All external traffic enters through a single nginx reverse proxy on port 80.
flowchart TB
web[Web SPA<br/>React 19] --> nginx
mobile[Mobile App<br/>React Native] --> nginx
nginx[nginx-proxy · :80<br/>single entry point]
nginx --> be[it-service-backend<br/>Spring Boot 4 · :8081]
nginx --> llm[llm-service<br/>Spring Boot 3 · :8082]
nginx --> kc[Keycloak 24<br/>/auth]
be --> pg[(PostgreSQL<br/>ticketdb)]
be --> redis[(Redis)]
be --> kie[KIE Server<br/>jBPM workflow]
be --> mail[Mailpit / SMTP]
llm --> pg
llm --> groq[Groq API · LLM]
kc --> ldap[(OpenLDAP)]
kc --> kcdb[(PostgreSQL<br/>keycloakdb)]
kie --> jbpmh2[(Embedded H2<br/>jbpm_data volume)]
kie -. workflow callback .-> be
be --> obs[Observability pipeline<br/>Kafka · OTEL Collector · Logstash<br/>Data Prepper → OpenSearch]
llm --> obs
A detailed breakdown — container diagram, request flows, security model and design decisions — lives in docs/ARCHITECTURE.md.
| Layer | Technologies |
|---|---|
| Backend API | Java 21, Spring Boot 4, Spring Security (OAuth2 Resource Server), Spring Data JPA, Flyway, WebSocket/STOMP, Caffeine, Bucket4j, Resilience4j |
| AI Service | Java 21, Spring Boot 3, Groq API (Llama 3.1) |
| Web Frontend | React 19, Vite, Tailwind CSS 4, React Router 7, i18next, keycloak-js, Recharts |
| Mobile | React Native, Expo |
| Workflow | jBPM / KIE Server 7.61, BPMN 2.0 |
| Data | PostgreSQL 15, Redis 7 |
| Identity | Keycloak 24, OpenLDAP |
| Messaging / Logs | Apache Kafka, Logstash |
| Observability | OpenTelemetry, OpenSearch + Dashboards, Data Prepper, Log4j2 |
| Infrastructure | Docker Compose, Kubernetes (kind + Kustomize), nginx |
| Quality / CI | JUnit 5, Mockito, Testcontainers, Vitest, JaCoCo, SonarQube, GitHub Actions |
Running on Kubernetes instead? This guide covers the Docker Compose path (the default for local development & demos). To deploy on Kubernetes (kind + Kustomize —
make k8s-up, in-cluster Keycloak seeding withmake k8s-seed-roles, etc.), follow k8s/README.md.
- Docker & Docker Compose
- Make
- Windows: install via GnuWin32 Make or
choco install make. The Makefile uses Windows-friendly commands (mvnw.cmd,cmd /k,rmdir). - macOS / Linux: pre-installed or
brew install make/apt install make. Replacemvnw.cmdwith./mvnwin the targets you invoke directly.
- Windows: install via GnuWin32 Make or
- For local (non-Docker) development: JDK 21, Node.js 22+
- A Groq API key (console.groq.com/keys) — only needed for the AI summary feature
cp .env.example .envThat's it for development — the example ships with working dev defaults (shared dev password 321654, matching internal tokens, the KIE server image's built-in credentials). The only optional blank is GROQ_API_KEY, needed solely for the AI summary feature. Every variable is documented inline in .env.example; change all passwords/secrets before any production use.
make up # start everything in Docker
make ps # list running containers
make logs s=it-service-backend # tail one serviceThe first start pulls images, runs Flyway migrations and imports the Keycloak realm — give it a couple of minutes. Use make rebuild after changing application code, and make down to stop.
Dev vs. prod compose. Compose is split into three files following the standard override pattern:
docker-compose.yaml— environment-agnostic base (pinned images, core config).docker-compose.override.yaml— dev layer, auto-loaded bymake up/docker compose up: host-published ports, build-from-source, the dev tools (mailpit, phpldapadmin, OpenSearch Dashboards, SonarQube) and dev settings (Swagger, SQL logging, mailpit SMTP). Somake upstays pure dev — nothing changes.docker-compose.prod.yaml— prod layer, applied explicitly withmake up-prod(-f docker-compose.yaml -f docker-compose.prod.yaml): pulls pinned images, exposes only nginx (80 + 443/TLS), drops every dev tool, runs Keycloak in production mode and addsrestartpolicies. Images must be in a registry first (the CD pipeline pushes them to Docker Hub onmain; selected viaDOCKERHUB_USERNAME/IMAGE_TAG) and a prod.envprepared (KC hostname, real SMTP, strong secrets). For full production (autoscaling, secrets, cert-manager) use the k8s prod overlay.
No Keycloak admin-console setup required. The realm import injects the LDAP bind credential and the
ticket-clientservice-account secret straight from your.env(LDAP_ADMIN_PASSWORD,KEYCLOAK_ADMIN_CLIENT_SECRET) — just make sure both are set before the firstmake up.
The only remaining one-time step: LDAP users land in Keycloak without realm roles — roles are not stored in LDAP. Run the idempotent seeder job, which pulls the users in from LDAP and assigns their roles in one shot:
make seed-roles # one-shot keycloak-seeder (tools profile); imports LDAP users + assigns roles; safe to re-runThis maps every seed user to the role(s) below. Roles are additive — a user may hold several, and effective permissions are their union.
| Username | Realm role(s) |
|---|---|
customer |
customer |
agent |
agent |
lead |
lead_agent (composite of agent) |
manager |
manager |
admin |
admin |
adminmanager |
admin + manager |
leadmanager |
lead_agent + manager |
superadmin |
admin + lead_agent + manager |
You can now log in at http://localhost.
| Service | URL |
|---|---|
| Web application | http://localhost |
| API (Swagger UI) | http://localhost/swagger-ui/index.html |
| Keycloak (OIDC, via nginx) | http://localhost/auth/realms/TicketSystemRealm — nginx only proxies /auth/realms and /auth/resources; everything else under /auth falls through to the SPA |
| Keycloak Admin Console | http://localhost:8080/auth/admin — direct Keycloak port (dev only; never served through nginx). Login: admin / KEYCLOAK_ADMIN_PASSWORD (from .env). In prod the port is bound to 127.0.0.1 — reach it via SSH tunnel |
| Mailpit (captured e-mails) | http://localhost:8025 |
| OpenSearch Dashboards | http://localhost:5601 |
| phpLDAPadmin | http://localhost:8085 — login: cn=admin,dc=ticketsystem,dc=com / LDAP_ADMIN_PASSWORD |
| KIE Server (jBPM workflow API) | http://localhost:8180/kie-server/docs |
| SonarQube (opt-in) | http://localhost:9000 |
make gen # build + run the data generator (products, tickets, history)Tip: the generator runs fastest with the backend global rate limit disabled (it fires requests with no pacing). Before
make gen, setRATE_LIMIT_GLOBAL_MAX_REQUESTS=10000000(orRATE_LIMIT_GLOBAL_ENABLED=false) in.envand restart the backend. It still works with the limit on — 429s are auto-retried — just slower. See data-generator/README.md.
Users are seeded into OpenLDAP and federated into Keycloak; their realm roles are assigned by make seed-roles (see step 3) — roles are not stored in LDAP. All seed users share the password 321654. Roles are additive — a user holds a set of roles, and their effective permissions are the union of them.
| Username | Password | Role(s) | Lands on | Capabilities |
|---|---|---|---|---|
customer |
321654 |
customer |
Overview | Personal Overview dashboard; raise & track own tickets (product-scoped), comment, attach files, submit CSAT |
agent |
321654 |
agent |
My Performance | Personal My Performance dashboard; claim tickets and act only on claimed tickets (product-scoped); change status, worklog, internal notes, AI summary |
lead |
321654 |
lead_agent |
My Performance + Team | Composite of agent + assign tickets to agents, act on tickets without claiming, manage product content (topics / known-issues / shared canned-responses) and a product-scoped team dashboard |
manager |
321654 |
manager |
Dashboard | Oversight (global, read-only): all dashboards, reports and full read visibility — no operational actions, no system config |
admin |
321654 |
admin |
Workspace + Admin | System configuration (global): create users, assign roles, create/manage products, grant product access, agent limits, SLA / cache |
adminmanager |
321654 |
admin + manager |
Workspace + Admin | Admin configuration + manager oversight (union) |
leadmanager |
321654 |
lead_agent + manager |
My Performance + Team + Dashboard | Lead-agent operations + manager oversight (union) |
superadmin* |
321654 |
admin + lead_agent + manager |
Workspace + Admin | Full super-admin: union of admin config, lead-agent operations and manager oversight |
*The data-generator bootstrap account is
superadmin, which holdsadmin+lead_agent+managerand therefore behaves as a super-admin. A "super-admin" is simply a user that holds all three of these roles — there is no dedicated super-admin role.
For hot-reload development, run the infrastructure in Docker and the apps on the host:
make infra # start only infra containers (DB, Keycloak, Redis, jBPM, OpenSearch...)
make dev-backend # run the backend on the host (Spring Boot :8081)
make dev-frontend # run the web frontend on the host (Vite :3000)
make dev-mobile # start the Expo dev server for the mobile appThe Vite dev server proxies /api/v1 → localhost:8081 and the WebSocket endpoint accordingly.
TicketSystemProject/
├── it-service-backend/ # Spring Boot 4 — main REST API (:8081)
├── llm-service/ # Spring Boot 3 — AI summarisation service (:8082)
├── it-service-frontend/ # React 19 + Vite — web SPA
├── it-service-mobile/ # React Native + Expo — mobile app
├── ticket-workflow-kjar/ # jBPM BPMN process definition (ticket-lifecycle)
├── data-generator/ # Standalone demo-data seeder
├── keycloak-init/ # Keycloak realm import
├── keycloak-themes/ # Custom Keycloak login theme
├── ldap-init/ # OpenLDAP bootstrap (seed users)
├── nginx/ # Reverse proxy configuration
├── observability/ # OpenSearch dashboards / saved objects
├── data-prepper/ # Metrics pipeline configuration
├── k8s/ # Kubernetes manifests (Kustomize base + overlays)
├── dev_plans/ # Design & planning documents
├── docs/ # Architecture & technical documentation
├── docker-compose.yaml # Full-stack orchestration (base)
├── docker-compose.override.yaml # Dev overlay (auto-loaded: ports, build, dev tools)
├── docker-compose.prod.yaml # Prod overlay (make up-prod: pinned images, nginx-only)
├── Makefile # Canonical command entry point
└── RUNBOOK.md # Operations & incident playbooks
make test # backend unit tests + frontend tests
make verify # backend unit + integration tests (Testcontainers) + JaCoCo report
make lint # frontend ESLint
make ci # full local CI gate: verify + frontend tests + lint
make sonar-up # start SonarQube, then: make sonar- Backend — JUnit 5 + Mockito unit tests;
*IT.javaintegration tests run against a real PostgreSQL via Testcontainers. Coverage report:it-service-backend/target/site/jacoco/index.html. - Frontend — Vitest + Testing Library (jsdom).
- CI — GitHub Actions runs backend
verify, llm-service build, and frontend lint/test/build on every pull request.
| Path | Command | Notes |
|---|---|---|
| Docker Compose (dev) | make up / make rebuild |
Single-host dev/demo; auto-loads docker-compose.override.yaml (ports, build, dev tools) |
| Docker Compose (prod) | make up-prod |
base + docker-compose.prod.yaml: pinned images, only nginx exposed (80/443), no dev tools, Keycloak prod mode, restart policies |
| Kubernetes | make k8s-up |
kind cluster + Kustomize (k8s/overlays/local); a prod overlay adds HPA, cert-manager and SealedSecrets |
| CI/CD | GitHub Actions | CI on every PR; CD builds and pushes Docker Hub images on main |
See docs/ARCHITECTURE.md for deployment topology and RUNBOOK.md for operational procedures.
Login — language and light/dark theme switcher.
TOTP 2FA prompt during sign-in.
My Tickets — the customer's own tickets with status / priority filters.
Create-ticket modal — product, topic and priority selection.
Ticket detail — conversation timeline, SLA countdown badge and attachments.
CSAT survey shown after a ticket is RESOLVED — 1-5 rating + comment.
Workspace — tickets the agent has claimed.
Pool — unclaimed tickets ready to be picked up.
Ticket detail from the agent's perspective — internal note tab and worklog panel.
AI summary modal — Groq-generated ticket summary.
Canned responses — reusable reply templates (personal + shared, product-scoped).
Admin panel — entry point to user / product / SLA / rate-limit management.
User management — multi-role assignment, agent capacity and product authorizations.
Product & topic configuration — CRUD on the categories customers can file tickets against.
Products — the catalogue of products customers can raise tickets against.
Known issues editor — the knowledge base attached to every product/topic.
Dashboard — KPI cards and completion meters (date-range scoped).
Dashboard — status distribution, CSAT distribution/trend and ticket-timeline charts.
Profile — language / theme preferences, password change and 2FA management.
In-app notification feed (bell icon) — live STOMP updates with unread badge.
Captured ticket / SLA e-mail in Mailpit (the dev SMTP catcher).
| Document | Purpose |
|---|---|
| docs/ARCHITECTURE.md | System architecture, request flows, security model, design decisions |
| docs/API.md | REST API reference — endpoints, parameters and example responses |
| docs/WORKFLOW.md | jBPM / BPMN ticket-lifecycle workflow design |
| docs/CICD.md | CI/CD pipeline design (GitHub Actions) |
| RUNBOOK.md | Operations & incident playbooks (DB backup, Keycloak re-import, Flyway repair...) |
| API reference | Interactive OpenAPI / Swagger UI at http://localhost/swagger-ui/index.html |
© 2026 Ukbe Taha ŞAHİNKAYA. All rights reserved.
This project was developed as an educational and portfolio project. It is shared solely for evaluation and learning purposes. It may not be used, copied, modified, distributed, or deployed — in whole or in part — for any commercial or for-profit purpose without the prior written permission of the author.
See the LICENSE file for the full terms.