JOB-EASY

Support Tickets

The built-in help desk, privacy guards, and internal notes.

Job-Easy has a native support ticket system allowing users to converse with admins without leaving the platform.

The Feature Flag

The entire ticket system is governed by a single feature flag: SystemSettings.ticketSystemEnabled (off by default). This flag is enforced in server/guards.ts, which every route calls first. Hiding the sidebar entry and 404-ing the pages is just visual tidiness; the backend guard is the true control.

Privacy Invariants

Query-Level Ownership

A user must only ever see their own tickets. To guarantee this, user-facing queries take a userId and place it directly into the Postgres WHERE clause. The system deliberately avoids the "fetch-then-compare" anti-pattern, ensuring that a missing authorization check cannot accidentally leak another account's correspondence.

Internal Notes

Admins can leave internal: true notes on a user's ticket.

Query-level isolation

Internal notes are NOT just visually hidden from the user on the frontend. The server employs two entirely different database queries for the two audiences (getUserTicket vs getAdminTicket). The user query explicitly filters out internal notes (internal: false) at the database level, meaning the note is physically absent from the returned payload. No downstream serialization mistake can expose it.

Workflow

  • Reopening: If a user replies to a resolved ticket, the ticket is instantly reopened. Requiring a new ticket for a follow-up discards the history of the problem, and users will naturally reply to the last message they see regardless.
  • Transactional Counters: Denormalized counters (like messageCount, unreadForAdmin, and lastMessageAt) are written only by specific service functions inside a database transaction. A message written without its counter bump would cause the sidebar badge to disagree with the thread.