Commit Graph

21 Commits

Author SHA1 Message Date
josh 28274bf7bd Add missing database indexes for FK lookups and audit queries
Type(categoryId), Item(typeId), Attachment(uploadedById), and
AuditLog(ticketId, createdAt) were missing indexes for their
primary query patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 20:25:40 -04:00
josh 5acc252921 Add requireAgent guard to analytics and export routes
Both endpoints were authenticated but had no role check, allowing any
logged-in USER to view company-wide analytics and export all tickets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 20:25:19 -04:00
josh f98930b54f Unblock prod deploy from Prisma data-loss guard
Build & Push / Test (client) (push) Successful in 32s
Build & Push / Test (server) (push) Successful in 31s
Build & Push / Build Client (push) Successful in 1m9s
Build & Push / Build Server (push) Successful in 2m17s
db push now runs with --accept-data-loss so the SERVICE enum-value
removal (rows already migrated by pre-push.sql) doesn't halt the boot.
Both Ticket and Comment also declare searchVector as
Unsupported("tsvector") so Prisma stops proposing to drop the columns
that post-push.sql manages — after this deploy, --accept-data-loss
becomes belt-and-suspenders rather than routinely required.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 21:56:00 -04:00
josh d8785a964d Merge SERVICE role into AGENT
Build & Push / Test (client) (push) Successful in 31s
Build & Push / Test (server) (push) Successful in 38s
Build & Push / Build Client (push) Successful in 1m17s
Build & Push / Build Server (push) Successful in 1m18s
Every AGENT now gets an auto-generated API key on creation, shown once
in a modal. AGENTs log in with password and authenticate to the API
with X-Api-Key. pre-push.sql defensively migrates any residual SERVICE
rows to AGENT before Prisma rewrites the enum. Goddard is no longer
baked into the seed — create agents via Admin → Users.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 22:44:32 -04:00
josh a9ba74f1af Remove login rate limiter (internal service only)
Build & Push / Test (client) (push) Successful in 22s
Build & Push / Test (server) (push) Successful in 28s
Build & Push / Build Client (push) Successful in 1m18s
Build & Push / Build Server (push) Successful in 1m18s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:43:10 -04:00
josh b341c64b02 Install root deps in Docker build so shared schemas resolve zod
Build & Push / Test (client) (push) Successful in 21s
Build & Push / Test (server) (push) Successful in 35s
Build & Push / Build Client (push) Successful in 1m12s
Build & Push / Build Server (push) Successful in 1m8s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:19:45 -04:00
josh 2c11d19f76 Pin vitest-mock-extended to 2.x to match vitest 2.x peer
Build & Push / Test (client) (push) Successful in 23s
Build & Push / Test (server) (push) Successful in 29s
Build & Push / Build Client (push) Failing after 1m9s
Build & Push / Build Server (push) Failing after 35s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:14:40 -04:00
josh 7253068fee Phase 5: ship (healthz, CI test gates, v1.0 README)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 16:42:47 -04:00
josh edf4c5eb3c Phase 2b: backend services, routes, and notification triggers
Attachments: multer-backed uploads with random-hex filenames,
streaming downloads with Content-Disposition, 25MB limit,
mimetype allowlist, audit entries, orphan cleanup on DB failure.

Full-text search: searchTicketIds + searchComments via raw SQL
ranked with ts_rank, composable filters via Prisma.sql/join,
hydrated with findMany and reordered via Map to preserve rank.

Pagination: listTicketsPaged returns {data,total,page,pageSize}
only when page/pageSize present (array response stays default,
so the Goddard n8n flow is unchanged).

Bulk actions: reassign/close/setSeverity/setStatus on POST /bulk,
writes one audit entry per ticket via createMany.

Analytics: summarize(window) runs 5 parallel groupBy + raw-SQL
queries for open-by-severity, status counts, queue load,
age buckets, percentile_cont median resolution hours.

CSV export streams matching tickets via res.write; saved views
CRUD with per-user ownership checks (403 cross-user, 404 missing).

Notifications: in-app Notification rows gated by prefs, email via
nodemailer (SMTP_HOST-gated, no-op when unset), outgoing webhooks
with HMAC-SHA256 signed POST and 3-retry exponential backoff.
Triggers wired into createTicket/updateTicket/addComment; mention
detection via parseMentions skips self-notify.

Infra: docker-compose uploads volume + SMTP env passthrough;
.env.example SMTP section.

43 server tests passing (attachment/webhook/notification/savedView
services covered; bulkAction covered in ticketService).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 15:56:33 -04:00
josh 0806aec4a4 Phase 2a: Prisma schema + shared schemas for v1.0 features
- New models: Attachment, Webhook, Notification, SavedView
- New fields: User.notificationPrefs (Json), indexes on Ticket
- post-push.sql manages the tsvector columns + GIN indexes + triggers for
  FTS on Ticket (title/overview/displayId) and Comment (body); Prisma can't
  express these
- package.json scripts: db:push and start:prod now chain `prisma db execute`
  against post-push.sql after `prisma db push`
- db:migrate script removed — project uses push workflow, not migrations
- Shared Zod schemas: attachment (25MB limit + mimetype allowlist), savedView,
  notification (prefs, mark-read, webhook CRUD)
- Shared type additions: Attachment, Notification, SavedView, Webhook,
  PaginatedResponse<T>
- Test fixtures updated for the new User.notificationPrefs column

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 15:52:16 -04:00
josh aff52e5672 Phase 1a: shared schemas, service layer, server tooling
- shared/schemas/: move Zod schemas out of routes so client + server share them
- shared/types.ts: inferred types and enums for cross-package use
- server tsconfig rootDir raised to ".." so shared/ compiles in-tree
- server/src/services/: ticket, comment, cti, user, auth, notification (stub), search (stub)
- Routes thinned to validate-delegate-return; business logic now testable in isolation
- server/src/lib/httpError.ts: typed HttpError replaces ad-hoc throw shapes
- server/src/lib/logger.ts: pino structured logging replaces console.log
- autoClose job delegates to ticketService.closeStale()
- express-rate-limit on /api/auth/login (10 / 15min / IP)
- vitest + vitest-mock-extended; 20 service-level tests cover auth, ticket, comment, user flows
- CI: lint + test jobs before docker builds

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 15:34:57 -04:00
josh 27d2ab0f0d Add ESLint + Prettier + EditorConfig tooling at repo root
v1.0 Phase 1.1 — repo-wide lint/format baseline.

- eslint.config.mjs (flat config) lints server, client, shared
- .prettierrc.json, .prettierignore, .editorconfig, .nvmrc
- Root package.json holds shared devDeps; per-package scripts keep
  their typecheck + test runners
- Fix 7 lint issues surfaced by the baseline run:
  - TicketDetail.tsx: replace ternary-with-side-effects with if/else
  - admin/Users.tsx: escape apostrophe in JSX
  - errorHandler.ts: typed err as unknown with ErrorLike refinement
  - users.ts: Prisma.UserUpdateInput instead of Record<string, any>
  - seed.ts: drop unused goddard binding
- Run prettier across tracked sources for a clean formatting baseline
2026-04-18 14:47:34 -04:00
josh d751e36ae8 Fix My Tickets and queue filter
Build & Push / Build Server (push) Successful in 58s
Build & Push / Build Client (push) Successful in 41s
- My Tickets: exclude RESOLVED and CLOSED, show active tickets only
- Queue filter: cascading Category > Type > Item picker — each leaf is
  a distinct queue (e.g. TheWrightServer > Automation > Backup vs Sync)
- Server: support typeId and itemId as ticket list filter params

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 23:35:33 -04:00
josh 725f91578d Dark theme, roles overhaul, modal New Ticket, My Tickets page, and more
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>
2026-03-30 23:17:14 -04:00
josh 64477529ea Add dbgenerated default for displayId
Build & Push / Build Server (push) Successful in 57s
Build & Push / Build Client (push) Successful in 41s
prisma db push cannot add a non-nullable column to an existing table
without a database-level default. Using a PostgreSQL expression to
generate V + 9 random digits as the fallback default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:58:44 -04:00
josh f65c259a71 Ticket IDs, audit log, markdown comments, tabbed detail page
- Tickets get a random display ID (V + 9 digits, e.g. V325813929)
- Ticket detail page has Overview / Comments / Audit Log tabs
- Audit log records every action (create, status, assignee, severity,
  reroute, title/overview edit, comment add/delete) with who and when
- Comments redesigned: avatar (initials + color), markdown rendering
  via react-markdown + remark-gfm, Write/Preview toggle
- Dashboard shows displayId and assignee avatar
- URLs now use displayId (/tickets/V325813929)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:53:37 -04:00
josh 429a530fc8 Use prisma db push instead of migrate deploy on startup
Build & Push / Build Server (push) Successful in 1m35s
Build & Push / Build Client (push) Successful in 40s
Eliminates the need to generate and commit migration files locally
before first deploy. Schema is synced directly from schema.prisma
on container start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:59:55 -04:00
josh 3dce53f1dc Install openssl in server runtime image
Build & Push / Build Server (push) Successful in 50s
Build & Push / Build Client (push) Has been cancelled
Prisma's schema engine requires libssl on Alpine.
Without it, migrate deploy fails with a JSON parse error
wrapping an ELF load failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:58:41 -04:00
josh ecaf17bf9f Add @types/node-cron to fix TypeScript build
Build & Push / Build Server (push) Successful in 58s
Build & Push / Build Client (push) Successful in 42s
node-cron v3 does not ship its own type declarations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:45:32 -04:00
josh e7ecf210e0 Add package-lock.json for reproducible Docker builds
Build & Push / Build Server (push) Failing after 24s
Build & Push / Build Client (push) Successful in 44s
npm ci in the Dockerfiles requires committed lockfiles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:43:43 -04:00
josh 21894fad7a Initial commit: TicketingSystem
Build & Push / Build Client (push) Failing after 9s
Build & Push / Build Server (push) Failing after 28s
Internal ticketing app with CTI routing, severity levels, and n8n integration.

Stack: Express + TypeScript + Prisma + PostgreSQL / React + Vite + Tailwind
- JWT auth for users, API key auth for service accounts (Goddard/n8n)
- CTI hierarchy (Category > Type > Item) for ticket routing
- Severity 1-5, auto-close resolved tickets after 14 days
- Gitea Actions CI/CD building separate server/client images
- Production docker-compose.yml with Traefik integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:38:32 -04:00