node-cron v3 does not ship its own type declarations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TicketingSystem
Internal ticketing system with CTI-based routing, severity levels, and n8n/automation integration.
Features
- CTI routing — tickets are categorised by Category → Type → Item, reroutable at any time
- Severity 1–5 — 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
- Comments — threaded comments per ticket with author attribution
- Roles — Admin, Agent, Service (API key auth for automation accounts)
- Admin panel — manage users and the full CTI hierarchy via UI
- n8n ready — service accounts authenticate via
X-Api-Keyheader
Production Deployment
Prerequisites
- Docker + Docker Compose
- Traefik running with a
proxyDocker network - 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>
DOMAIN=tickets.thewrightserver.net
3. Create the initial database migration
Run this once on your local dev machine before first deploy, then commit the result:
cd server
npm install
# point at your local postgres or use DATABASE_URL from .env
npm run db:migrate # creates prisma/migrations/
Commit the generated server/prisma/migrations/ folder — prisma migrate deploy in the container will apply it on startup.
4. Deploy
docker compose pull
docker compose up -d
5. Seed (first deploy only)
docker compose exec server npm run db:seed
This creates:
adminuser (password:admin123) — change this immediatelygoddardservice 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 + migration files
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)
n8n Integration (Goddard)
The goddard service account authenticates via API key — no login flow needed.
Create a ticket from n8n:
POST https://tickets.thewrightserver.net/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/categoriesGET /api/cti/types?categoryId=<id>GET /api/cti/items?typeId=<id>
To regenerate the Goddard API key: Admin → Users → refresh icon next to Goddard.
CI/CD
Push to main triggers .gitea/workflows/build.yml, which builds and pushes two images in parallel:
| Image | Tag |
|---|---|
$REGISTRY/josh/ticketing-server |
latest, <git sha> |
$REGISTRY/josh/ticketing-client |
latest, <git sha> |
Gitea repository secrets/variables required:
| Name | Type | Value |
|---|---|---|
REGISTRY |
Variable | gitea.thewrightserver.net |
REGISTRY_TOKEN |
Secret | Gitea personal access token with write:packages |
Set these under Repository → Settings → Actions → Variables / Secrets.
To deploy a specific commit SHA instead of latest:
TAG=<sha> docker compose up -d
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 |
DOMAIN |
Deploy | Public domain for Traefik routing |
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. Paging by severity is planned for a future release.
Ticket Status Lifecycle
OPEN → IN_PROGRESS → RESOLVED ──(14 days)──→ CLOSED
↑
re-opens reset
the 14-day timer