Skip to content

feat: implement ticket visibility controls (#44) - #57

Merged
gabemoreno merged 4 commits into
mainfrom
feat/ticket-visibility
Oct 26, 2026
Merged

gabemoreno merged 4 commits into
mainfrom
feat/ticket-visibility

Conversation

@gabemoreno

Copy link
Copy Markdown
Contributor

Summary

Implements ticket visibility options, allowing users to control who can view and reply to their support tickets.

Closes #44

Changes

  • Added three ticket visibility levels:
    • Public: Anyone can view and reply, including anonymous users.
    • Logged-in Only: Only authenticated users can view and reply.
    • Private: Only the ticket creator and IT technicians can view and reply.
  • Added a visibility selector to the ticket creation form.
  • Enforced visibility permissions through backend authorization checks.
  • Restricted anonymous users to creating public tickets.
  • Preserved ticket ownership when posting anonymously.
  • Updated the database schema and migrations to support visibility.
  • Updated relevant tests and documentation.

Testing

  • Manually tested the following scenarios:
    • Anonymous visitors can create, view, and reply to PUBLIC tickets.
    • Anonymous visitors cannot create or view restricted tickets.
    • Logged-in users can create AUTHENTICATED and PRIVATE tickets.
    • Other logged-in users can view and reply to AUTHENTICATED tickets.
    • Regular users cannot access someone else's PRIVATE ticket, even through a direct URL.
    • The original creator can view and reply to their PRIVATE ticket.
    • IT technicians can view and reply to PRIVATE tickets.
    • Posting anonymously preserves the creator's access without exposing their identity.
  • 116 automated tests passed, including PostgreSQL integration tests.
  • Database migrations completed successfully.
  • Formatting, linting, type checks, coverage, and production build passed.
  • Infrastructure smoke test.

Notes

  • Existing tickets default to Public to maintain backward compatibility.
  • Unauthorized requests to restricted tickets return 404 to avoid revealing their existence.
  • Ticket visibility is selected during creation and cannot currently be changed afterward.

async listRecent(limit: number): Promise<TicketRecord[]> {
async listRecent(limit: number, viewer: TicketViewer | null = null): Promise<TicketRecord[]> {
const visibilityFilter =
viewer?.role === "TECHNICIAN"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test gap (please add): none of the tests exercise this where filter. The route tests use a fake repository whose listRecent ignores the viewer, and the route filters the list again with canAccessTicket. If this SQL filter broke, every test would still pass, but hidden tickets would use up the 50-ticket list limit and visible tickets would quietly drop off the list. Could you add a case to postgres-ticket-repository.integration.test.ts? Create one ticket of each visibility, then check what listRecent returns for null, the owner, another user and a technician.

Simplification (nit): the technician case matches all three values, which is the same as no filter. viewer?.role === "TECHNICIAN" ? undefined : … says that more directly, and Drizzle's .where(undefined) is a no-op.

additionalInformation: ticket.additionalInformation,
status: ticket.status,
visibility: ticket.visibility,
canReply: canAccessTicket(ticket, viewer),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canReply uses the same check as viewing, so it's true for every ticket a user can see. That makes the "you can view but cannot reply" message in TicketReplies.tsx unreachable. If view and reply permissions are expected to differ later, a short comment saying so would help. Otherwise we could drop the field and the dead branch for now.

Comment thread apps/web/src/tickets/TicketReplies.tsx Outdated
</form>
</form>
) : (
<p className="field-hint">You can view this conversation, but you cannot reply.</p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to the canReply comment: this branch can't be reached at the moment.

Comment thread apps/web/src/tickets/ticket-format.ts Outdated
return {
PUBLIC: "Public",
AUTHENTICATED: "Logged-in users",
PRIVATE: "You and IT only"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: technicians also see this badge on other people's tickets, where "You and IT only" reads oddly. Maybe just "Private"? The form's dropdown can keep its longer explanation.

Comment thread apps/api/src/tickets/ticket-route.ts Outdated
} from "./ticket-contract.js";
import type { ReplyRecord, TicketRecord, TicketRepository } from "./ticket-repository.js";
import { canAccessTicket } from "./ticket-access.js";
import type { TicketViewer } from "./ticket-repository.js";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this can be folded into the existing import type { … } from "./ticket-repository.js" above (same in postgres-ticket-repository.ts line 13).

};
}

async function currentUser(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the assignment route (PATCH /api/tickets/:ticketId/assignment) still looks up the session inline instead of using this helper. After merging main, that route should use #56's requireTechnician guard anyway, so it may sort itself out during the conflict fix.

Preserve ticket visibility controls while incorporating PR #56 IT account support, shared technician authorization, PostgreSQL visibility filtering tests, and ADR numbering updates.
Preserve ticket visibility and IT authorization while incorporating PR #58 technician status transitions, API contracts, UI controls, and documentation.
@gabemoreno
gabemoreno merged commit a44a5e9 into main Oct 26, 2026
3 checks passed
systemicdream added a commit that referenced this pull request Oct 26, 2026
Resolve conflicts with ticket visibility (#57):
- Renumber the notifications migration to 0006 after main's
  0005_ticket_visibility; the regenerated SQL is unchanged.
- Keep both canTechnicianChangeStatus and the visibility types in
  ticket-contract.ts, and pass notificationRepository to main's new
  route tests.
- Assert the status PATCH with toHaveBeenCalledWith in TicketDetail:
  the replies panel can fetch after the click, which made the
  last-call assertion flaky in CI.
@gabemoreno
gabemoreno deleted the feat/ticket-visibility branch October 26, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

As a ticket creator, I want visibility options for who can see my ticket (everyone, only logged-in users, or only IT).

4 participants