feat(tickets): finish PMS-11 story (JOINed DTOs, note email, automation dispatch) #21

Closed
David wants to merge 3 commits from feat/tickets-story-pms-11 into feat/auth-story-pms-4
Owner

Implements YouTrack story PMS-11 (tickets). One commit per sub-task.

Stacked on top of #20 (PMS-4 auth story). Targets that branch as base so the utils::email::Mailer infra appears once. Retarget to main after #20 merges.

Sub-tasks

  • PMS-12 (F3) fill ticket DTOs from JOINed query: every ticket-returning handler used to send String::new() for status.name, priority.name, queue/company/contact/assigned/created-by names. New TicketService::get_ticket_response / list_ticket_responses issue one JOINed SELECT against the seven lookup tables and produce wire-shaped TicketResponse directly. Routes for list, get, create, update, assign all read from these methods.
  • PMS-13 on_create rules: already shipped earlier in tickets/service.rs::create_ticket; closed administratively.
  • PMS-14 on_update rules: already shipped in tickets/service.rs::update_ticket; closed administratively.
  • PMS-15 add_note email: TicketService::with_mailer injects the host-crate Mailer. When add_note has send_email = true on a public note, the service sends a plain-text update to the ticket's contact and flips ticket_notes.is_email_sent. Internal notes never leave the building.
  • PMS-16 automation notifications + webhooks: AutomationEngine::with_deps takes the mailer + a reqwest client (10s timeout). The send_notification action reads to/subject/body from params and sends via Mailer::send_text. The webhook action POSTs (or GET/PUT/PATCH/DELETE) to params.url with params.payload (default a JSON envelope naming tenant + ticket + rule).

Behaviour-visible changes

  • GET /api/v1/tickets, GET /api/v1/tickets/:id, POST /api/v1/tickets, PUT /api/v1/tickets/:id, POST /api/v1/tickets/:id/assign now return complete names instead of empty strings.
  • POST /api/v1/tickets/:id/notes with send_email: true on a public note delivers an email to the ticket contact via the configured mailer (LogMailer dev, SmtpMailer prod) and stamps the note's email metadata.
  • Existing automation rules with the send_notification or webhook action now actually act.

Test plan

  • cargo check --bin mokosh-server clean (verified locally).
  • Smoke: GET /api/v1/tickets/:id returns populated status.name, priority.name, company_name, etc.
  • Smoke: POST /api/v1/tickets/:id/notes with { "note_type": "public", "content": "...", "send_email": true } for a ticket with a contact email triggers an SMTP send (mailpit at http://localhost:8025 in dev) and flips is_email_sent.
  • Smoke: create an automation rule with a webhook action pointing at a request-bin URL; observe the POST body.

Closes #PMS-11

Implements YouTrack story PMS-11 (tickets). One commit per sub-task. **Stacked on top of #20 (PMS-4 auth story).** Targets that branch as base so the `utils::email::Mailer` infra appears once. Retarget to `main` after #20 merges. ## Sub-tasks - PMS-12 (F3) fill ticket DTOs from JOINed query: every ticket-returning handler used to send `String::new()` for `status.name`, `priority.name`, queue/company/contact/assigned/created-by names. New `TicketService::get_ticket_response` / `list_ticket_responses` issue one JOINed SELECT against the seven lookup tables and produce wire-shaped `TicketResponse` directly. Routes for list, get, create, update, assign all read from these methods. - PMS-13 on_create rules: already shipped earlier in `tickets/service.rs::create_ticket`; closed administratively. - PMS-14 on_update rules: already shipped in `tickets/service.rs::update_ticket`; closed administratively. - PMS-15 add_note email: `TicketService::with_mailer` injects the host-crate `Mailer`. When `add_note` has `send_email = true` on a public note, the service sends a plain-text update to the ticket's contact and flips `ticket_notes.is_email_sent`. Internal notes never leave the building. - PMS-16 automation notifications + webhooks: `AutomationEngine::with_deps` takes the mailer + a reqwest client (10s timeout). The `send_notification` action reads `to`/`subject`/`body` from params and sends via `Mailer::send_text`. The `webhook` action POSTs (or GET/PUT/PATCH/DELETE) to `params.url` with `params.payload` (default a JSON envelope naming tenant + ticket + rule). ## Behaviour-visible changes - `GET /api/v1/tickets`, `GET /api/v1/tickets/:id`, `POST /api/v1/tickets`, `PUT /api/v1/tickets/:id`, `POST /api/v1/tickets/:id/assign` now return complete names instead of empty strings. - `POST /api/v1/tickets/:id/notes` with `send_email: true` on a public note delivers an email to the ticket contact via the configured mailer (LogMailer dev, SmtpMailer prod) and stamps the note's email metadata. - Existing automation rules with the `send_notification` or `webhook` action now actually act. ## Test plan - [ ] `cargo check --bin mokosh-server` clean (verified locally). - [ ] Smoke: `GET /api/v1/tickets/:id` returns populated `status.name`, `priority.name`, `company_name`, etc. - [ ] Smoke: `POST /api/v1/tickets/:id/notes` with `{ "note_type": "public", "content": "...", "send_email": true }` for a ticket with a contact email triggers an SMTP send (mailpit at http://localhost:8025 in dev) and flips `is_email_sent`. - [ ] Smoke: create an automation rule with a `webhook` action pointing at a request-bin URL; observe the POST body. Closes #PMS-11
Every ticket-returning handler previously constructed `TicketResponse` with `String::new()` for the nine joined string fields (status.name, status.color, priority.name, priority.color, type_name, category_name, queue_name, company_name, contact_name, assigned_to_name, created_by_name). Clients that render names rather than UUIDs were getting blanks.

Move the join into the service: `get_ticket_response` / `list_ticket_responses` issue one SELECT against `tickets` joined to `ticket_statuses`, `ticket_priorities`, `ticket_queues`, `ticket_types`, `ticket_categories`, `companies`, `contacts`, and `users` (assigned + created_by). The route handlers for list, get, create, update, and assign all read from these new methods, so `200 OK` now means complete data on the wire. The shared SELECT clause is centralised in `TICKET_RESPONSE_SELECT` so the column list and join graph cannot drift between get and list.

#PMS-12 State Done
`TicketService` now takes an `Arc<dyn Mailer>` (via `with_mailer`, wired from `create_api_router`) and a new `Mailer::send_text` escape hatch covers ad-hoc bodies. When `add_note` is called with `send_email = true` on a public note, the service fetches the ticket's contact email in one round-trip and sends a plain-text update. Success flips `ticket_notes.is_email_sent` and stamps `email_sent_at`; failures are logged but never 5xx the note add, since the note itself is already persisted.

Internal notes never trigger email regardless of the flag, since they aren't meant to leave the agent surface.

#PMS-15 State Done
`AutomationEngine` gains `with_deps(db, mailer)` and an internal reqwest client (10s timeout, named user-agent). The two action branches that previously logged-and-skipped now do real work:

- `send_notification` reads `to`, `subject`, `body` from action params and sends via `Mailer::send_text`. When the notifications module (PMS-85) lands, this branch will hand off to its dispatcher for channels + templates + watcher fan-out; until then the rule author supplies the recipient inline.
- `webhook` reads `url` (required), `method` (default POST), and `payload` (default JSON envelope naming tenant + ticket + rule). Non-2xx responses and transport failures are logged but never abort the rest of the rule chain.

#PMS-16 State Done
vas2000-work closed this pull request 2026-05-21 02:42:54 +02:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!21
No description provided.