Dark theme, roles overhaul, modal New Ticket, My Tickets page, and more
All checks were successful
Build & Push / Build Server (push) Successful in 2m5s
Build & Push / Build Client (push) Successful in 41s

- 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>
This commit is contained in:
2026-03-30 23:17:14 -04:00
parent d8dc5b3ded
commit 725f91578d
21 changed files with 821 additions and 388 deletions

306
README.md
View File

@@ -1,19 +1,35 @@
# TicketingSystem
Internal ticketing system with CTI-based routing, severity levels, and automation integration.
Internal ticketing system with CTI-based routing, severity levels, role-based access, and automation integration.
## Features
- **CTI routing** — tickets are categorised by Category → Type → Item, reroutable at any time
- **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
- **Comments** — threaded comments per ticket with author attribution
- **Roles** — Admin, Agent, Service (API key auth for automation accounts)
- **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
@@ -55,7 +71,7 @@ docker compose pull
docker compose up -d
```
### 5. Seed (first deploy only)
### 4. Seed (first deploy only)
```bash
docker compose exec server npm run db:seed
@@ -92,7 +108,7 @@ docker run -d \
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:migrate # creates tables
npm run db:seed # seeds admin + Goddard + sample CTI
npm run dev # http://localhost:3000
```
@@ -107,6 +123,251 @@ 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:**
```json
{ "username": "string", "password": "string" }
```
**Response:**
```json
{
"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: `1``5` |
| `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:**
```json
{
"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):**
```json
{
"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:**
```json
{ "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:
```json
[
{
"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.
```json
{
"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.
```json
{
"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.
@@ -114,7 +375,7 @@ The `goddard` service account authenticates via API key — no login flow needed
**Create a ticket from n8n:**
```
POST https://tickets.thewrightserver.net/api/tickets
POST /api/tickets
X-Api-Key: sk_<goddard api key>
Content-Type: application/json
@@ -138,32 +399,6 @@ To regenerate the Goddard API key: Admin → Users → refresh icon next to Godd
---
## 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:
```bash
TAG=<sha> docker compose up -d
```
---
## Environment Variables
| Variable | Required | Description |
@@ -174,7 +409,6 @@ TAG=<sha> docker compose up -d
| `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`) |
---
@@ -189,7 +423,7 @@ TAG=<sha> docker compose up -d
| 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.
Tickets are sorted SEV 1 → SEV 5 on the dashboard.
---
@@ -201,3 +435,5 @@ 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.