Skip to content

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

Open
gabemoreno wants to merge 1 commit into
mainfrom
feat/ticket-visibility
Open

gabemoreno wants to merge 1 commit 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.

</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.

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.

} 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.

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).

2 participants