Files
TicketingSystem/README.md
josh 725f91578d
All checks were successful
Build & Push / Build Server (push) Successful in 2m5s
Build & Push / Build Client (push) Successful in 41s
Dark theme, roles overhaul, modal New Ticket, My Tickets page, and more
- Dark UI across all pages and components (gray-950/900/800 palette)
- New Ticket is now a centered modal (triggered from sidebar), not a separate page
- Add USER role: view and comment only; AGENT and SERVICE can create/edit tickets
- Only admins can set ticket status to CLOSED (enforced server + UI)
- Add My Tickets page (/my-tickets) showing tickets assigned to current user
- Add queue (category) filter to Dashboard
- Audit log entries are clickable to expand detail; comment body shown as markdown
- Resolved date now includes time (HH:mm) in ticket sidebar
- Store comment body in audit log detail for COMMENT_ADDED and COMMENT_DELETED
- Clarify role descriptions in Admin Users modal
- Remove CI/CD section from README; add full API reference documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 23:17:14 -04:00

10 KiB
Raw Permalink Blame History

TicketingSystem

Internal ticketing system with CTI-based routing, severity levels, role-based access, and automation integration.

Features

  • CTI routing — tickets categorised by Category → Type → Item, reroutable at any time
  • Severity 15 — SEV 1 (critical) through SEV 5 (minimal); dashboard sorts by severity
  • Status lifecycle — Open → In Progress → Resolved → Closed; resolved tickets auto-close after 14 days
  • Queue filter — filter dashboard by category (queue)
  • My Tickets — dedicated view of tickets assigned to you
  • Comments — threaded markdown comments per ticket with author avatars
  • Roles — Admin, Agent, User, Service (API key auth for automation)
  • Audit log — every action tracked with actor, timestamp, and expandable detail
  • Admin panel — manage users and the full CTI hierarchy via UI
  • n8n ready — service accounts authenticate via X-Api-Key header

Roles

Role Access
Admin Full access — manage users, CTI config, close and delete tickets
Agent Manage tickets — create, update, assign, comment, change status (not Closed)
User Basic access — view tickets and add comments only
Service Automation account — authenticates via API key, no password login

Only Admins can manually set a ticket status to Closed.


Production Deployment

Prerequisites

  • Docker + Docker Compose
  • Nginx Proxy Manager pointed at the host port (default 3080)
  • Access to your Gitea container registry

1. Copy files to your server

scp docker-compose.yml .env.example user@your-server:~/ticketing/

2. Configure environment

cd ~/ticketing
cp .env.example .env

Edit .env:

REGISTRY=gitea.thewrightserver.net
TAG=latest
POSTGRES_PASSWORD=<strong password>
JWT_SECRET=<output of: openssl rand -hex 64>
CLIENT_URL=http://tickets.thewrightserver.net
PORT=3080

Point NPM at http://<host-ip>:3080 for the proxy host.

3. Deploy

docker compose pull
docker compose up -d

4. Seed (first deploy only)

docker compose exec server npm run db:seed

This creates:

  • admin user (password: admin123) — change this immediately
  • goddard service account — API key is printed to the console; copy it now

Development

Requirements

  • Node.js 22+
  • PostgreSQL (local or via Docker)

Start Postgres

docker run -d \
  --name ticketing-pg \
  -e POSTGRES_DB=ticketing \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -p 5432:5432 \
  postgres:16-alpine

Server

cd server
cp .env.example .env        # set DATABASE_URL and JWT_SECRET
npm install
npm run db:migrate          # creates tables
npm run db:seed             # seeds admin + Goddard + sample CTI
npm run dev                 # http://localhost:3000

Client

cd client
npm install
npm run dev                 # http://localhost:5173 (proxies /api to :3000)

API Reference

All endpoints (except /api/auth/*) require authentication via one of:

  • JWT: Authorization: Bearer <token> (obtained from POST /api/auth/login)
  • API Key: X-Api-Key: sk_<key> (Service accounts only)

Base URL: https://tickets.thewrightserver.net/api


Authentication

POST /api/auth/login

Authenticate and receive a JWT.

Body:

{ "username": "string", "password": "string" }

Response:

{
  "token": "eyJ...",
  "user": { "id": "...", "username": "admin", "displayName": "Admin", "email": "...", "role": "ADMIN" }
}

GET /api/auth/me

Returns the currently authenticated user.


Tickets

GET /api/tickets

List all tickets, sorted by severity (ASC) then created date (DESC).

Query parameters:

Parameter Type Description
status string Filter by status: OPEN, IN_PROGRESS, RESOLVED, CLOSED
severity number Filter by severity: 15
categoryId string Filter by category (queue)
assigneeId string Filter by assignee user ID
search string Full-text search on title, overview, and display ID

Response: Array of ticket objects with nested category, type, item, assignee, createdBy, and _count.comments.


GET /api/tickets/:id

Fetch a single ticket by internal ID or display ID (e.g. V325813929). Includes full comments array.


POST /api/tickets

Create a new ticket. Requires Agent, Admin, or Service role.

Body:

{
  "title": "string",
  "overview": "string (markdown)",
  "severity": 1,
  "categoryId": "string",
  "typeId": "string",
  "itemId": "string",
  "assigneeId": "string (optional)"
}

Response: Created ticket object (201).


PATCH /api/tickets/:id

Update a ticket. Accepts any combination of fields. Requires Agent, Admin, or Service role.

Setting status to CLOSED requires Admin role.

Body (all fields optional):

{
  "title": "string",
  "overview": "string (markdown)",
  "severity": 3,
  "status": "IN_PROGRESS",
  "assigneeId": "string | null",
  "categoryId": "string",
  "typeId": "string",
  "itemId": "string"
}

All changes are recorded in the audit log automatically.

Response: Updated ticket object.


DELETE /api/tickets/:id

Delete a ticket and all associated comments and audit logs. Admin only.

Response: 204 No Content.


Comments

POST /api/tickets/:id/comments

Add a comment to a ticket. Supports markdown. All authenticated roles may comment.

Body:

{ "body": "string (markdown)" }

Response: Created comment object (201).


DELETE /api/tickets/:id/comments/:commentId

Delete a comment. Authors may delete their own comments; Admins may delete any.

Response: 204 No Content.


Audit Log

GET /api/tickets/:id/audit

Retrieve the full audit log for a ticket, ordered newest first.

Response: Array of audit log entries:

[
  {
    "id": "...",
    "action": "COMMENT_ADDED",
    "detail": "Comment body text here",
    "createdAt": "2026-03-30T10:00:00Z",
    "user": { "id": "...", "username": "admin", "displayName": "Admin" }
  }
]

Action types:

Action Detail
CREATED
STATUS_CHANGED e.g. Open → In Progress
SEVERITY_CHANGED e.g. SEV 3 → SEV 1
ASSIGNEE_CHANGED e.g. Unassigned → Josh
REROUTED e.g. OldCat OldType OldItem → NewCat NewType NewItem
TITLE_CHANGED New title
OVERVIEW_CHANGED
COMMENT_ADDED Comment body
COMMENT_DELETED Deleted comment body

CTI (Category / Type / Item)

GET /api/cti/categories

GET /api/cti/types?categoryId=<id>

GET /api/cti/items?typeId=<id>

Read the CTI hierarchy. Used to resolve IDs when creating/rerouting tickets.

Admin-only write operations:

Method Endpoint Body
POST /api/cti/categories { "name": "string" }
PUT /api/cti/categories/:id { "name": "string" }
DELETE /api/cti/categories/:id
POST /api/cti/types { "name": "string", "categoryId": "string" }
PUT /api/cti/types/:id { "name": "string" }
DELETE /api/cti/types/:id
POST /api/cti/items { "name": "string", "typeId": "string" }
PUT /api/cti/items/:id { "name": "string" }
DELETE /api/cti/items/:id

Deleting a category cascades to all child types and items.


Users

GET /api/users

List all users (id, username, displayName, email, role). Used to populate assignee dropdowns.

Admin-only operations:

POST /api/users

Create a user.

{
  "username": "string",
  "email": "string",
  "displayName": "string",
  "password": "string (not required for SERVICE role)",
  "role": "ADMIN | AGENT | USER | SERVICE"
}

Service accounts receive an auto-generated API key returned in the response. Copy it immediately — it is not shown again.

PATCH /api/users/:id

Update a user.

{
  "displayName": "string",
  "email": "string",
  "password": "string",
  "role": "ADMIN | AGENT | USER | SERVICE",
  "regenerateApiKey": true
}

DELETE /api/users/:id

Delete a user. Cannot delete your own account.


n8n Integration (Goddard)

The goddard service account authenticates via API key — no login flow needed.

Create a ticket from n8n:

POST /api/tickets
X-Api-Key: sk_<goddard api key>
Content-Type: application/json

{
  "title": "[Plex] Backup - 2026-03-30T02:00:00",
  "overview": "Automated nightly Plex backup completed.",
  "severity": 5,
  "categoryId": "<TheWrightServer category ID>",
  "typeId": "<Automation type ID>",
  "itemId": "<Backup item ID>",
  "assigneeId": "<Goddard user ID>"
}

CTI IDs can be fetched from:

  • GET /api/cti/categories
  • GET /api/cti/types?categoryId=<id>
  • GET /api/cti/items?typeId=<id>

To regenerate the Goddard API key: Admin → Users → refresh icon next to Goddard.


Environment Variables

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string
JWT_SECRET Yes Secret for signing JWTs — use openssl rand -hex 64
CLIENT_URL Yes Allowed CORS origin (your domain)
PORT No Server port (default: 3000)
REGISTRY Deploy Container registry hostname
POSTGRES_PASSWORD Deploy Postgres password
TAG Deploy Image tag to deploy (default: latest)

Ticket Severity

Level Label Meaning
1 SEV 1 Critical — immediate action required
2 SEV 2 High — significant impact
3 SEV 3 Medium — standard priority
4 SEV 4 Low — minor issue
5 SEV 5 Minimal — informational / automated

Tickets are sorted SEV 1 → SEV 5 on the dashboard.


Ticket Status Lifecycle

OPEN → IN_PROGRESS → RESOLVED ──(14 days)──→ CLOSED
                         ↑
                    re-opens reset
                    the 14-day timer

CLOSED status can only be set manually by an Admin. The auto-close job runs hourly.