Files
redmine/plugins/redmine_contacts_helpdesk/app/controllers/helpdesk_search_controller.rb
T
2026-04-24 22:01:18 +00:00

202 lines
6.4 KiB
Ruby

class HelpdeskSearchController < ApplicationController
unloadable
accept_api_auth :ticket_by_issue, :issues_by_contact, :messages_by_issue, :contact_timeline
before_filter :require_login
def usage
# Human/browser probes often stop at /helpdesk_search/issues. Return a
# machine-readable usage response instead of letting Rails raise a route
# exception that looks like an application failure in production.log.
render :json => {
:helpdesk_search => {
:ticket_by_issue => '/helpdesk_search/issues/:issue_id/ticket',
:ticket_by_issue_alias => '/helpdesk_search/issues/:issue_id',
:issues_by_contact => '/helpdesk_search/contacts/:contact_id/issues',
:messages_by_issue => '/helpdesk_search/issues/:issue_id/messages',
:contact_timeline => '/helpdesk_search/contacts/:contact_id/timeline'
}
}, :status => :bad_request
end
def ticket_by_issue
issue = Issue.find(params[:issue_id])
return unless authorize_helpdesk_project!(issue.project)
ticket = HelpdeskTicket.where(:issue_id => issue.id).first
unless ticket
render_404
return
end
render :json => {:helpdesk_ticket => serialize_ticket(ticket)}
rescue ActiveRecord::RecordNotFound
render_404
end
def issues_by_contact
contact = Contact.find(params[:contact_id])
tickets = HelpdeskTicket.
includes(:issue => [:project, :status, :tracker, :assigned_to]).
where(:contact_id => contact.id).
order("#{HelpdeskTicket.table_name}.ticket_date DESC").
limit(api_limit)
render :json => {
:contact_id => contact.id,
:issues => tickets.map { |ticket| serialize_ticket_issue(ticket) }.compact
}
rescue ActiveRecord::RecordNotFound
render_404
end
def messages_by_issue
issue = Issue.find(params[:issue_id])
return unless authorize_helpdesk_project!(issue.project)
messages = JournalMessage.
includes(:contact, :journal).
joins(:journal).
where(:journals => {:journalized_type => 'Issue', :journalized_id => issue.id}).
order("#{JournalMessage.table_name}.message_date ASC").
limit(api_limit)
render :json => {
:issue_id => issue.id,
:journal_messages => messages.map { |message| serialize_journal_message(message) }
}
rescue ActiveRecord::RecordNotFound
render_404
end
def contact_timeline
contact = Contact.find(params[:contact_id])
tickets = HelpdeskTicket.
includes(:issue => [:project, :status, :tracker]).
where(:contact_id => contact.id).
order("#{HelpdeskTicket.table_name}.ticket_date DESC").
limit(api_limit)
messages = JournalMessage.
includes(:contact, :journal).
where(:contact_id => contact.id).
order("#{JournalMessage.table_name}.message_date DESC").
limit(api_limit)
events = []
tickets.each do |ticket|
next unless visible_issue?(ticket.issue)
events << serialize_ticket(ticket).merge(:type => 'helpdesk_ticket', :date => iso8601(ticket.ticket_date))
end
messages.each do |message|
issue = message_issue(message)
next unless visible_issue?(issue)
events << serialize_journal_message(message).merge(:type => 'journal_message', :date => iso8601(message.message_date))
end
events.sort_by! { |event| event[:date].to_s }
events.reverse!
render :json => {
:contact_id => contact.id,
:timeline => events.first(api_limit)
}
rescue ActiveRecord::RecordNotFound
render_404
end
private
def authorize_helpdesk_project!(project)
# Search endpoints expose customer/email context, so they reuse the native
# per-project helpdesk visibility permission instead of adding a new role.
return true if project && User.current.allowed_to?(:view_helpdesk_tickets, project)
deny_access
false
end
def visible_issue?(issue)
issue && issue.project && User.current.allowed_to?(:view_helpdesk_tickets, issue.project)
end
def api_limit
requested_limit = params[:limit].present? ? params[:limit].to_i : 100
[[requested_limit, 1].max, 200].min
end
def serialize_ticket_issue(ticket)
issue = ticket.issue
return nil unless visible_issue?(issue)
serialize_ticket(ticket).merge(
:issue => {
:id => issue.id,
:project_id => issue.project_id,
:tracker_id => issue.tracker_id,
:tracker_name => issue.tracker.try(:name),
:status_id => issue.status_id,
:status_name => issue.status.try(:name),
:assigned_to_id => issue.assigned_to_id,
:assigned_to_name => issue.assigned_to.try(:name),
:subject => issue.subject,
:created_on => iso8601(issue.created_on),
:updated_on => iso8601(issue.updated_on)
}
)
end
def serialize_ticket(ticket)
{
:id => ticket.id,
:issue_id => ticket.issue_id,
:contact_id => ticket.contact_id,
:message_id => ticket.message_id,
:source => ticket.source,
:is_incoming => ticket.is_incoming?,
:from_address => ticket.from_address,
:to_address => ticket.to_address,
:cc_address => ticket.cc_address,
:ticket_date => iso8601(ticket.ticket_date)
}
end
def serialize_journal_message(message)
journal = message.journal
issue = message_issue(message)
# Keep this API metadata-only. The external indexer can fetch journal.notes
# with its own read policy; this endpoint should not leak message bodies,
# private notes, attachments, or BCC addresses by accident.
{
:id => message.id,
:journal_id => message.journal_id,
:issue_id => issue.try(:id),
:project_id => issue.try(:project_id),
:contact_id => message.contact_id,
:message_id => message.message_id,
:source => message.source,
:is_incoming => message.is_incoming?,
:from_address => message.from_address,
:to_address => message.to_address,
:cc_address => message.cc_address,
:has_bcc_address => message.bcc_address.present?,
:message_date => iso8601(message.message_date),
:journal_user_id => journal.try(:user_id),
:journal_private_notes => journal.try(:private_notes?),
:journal_has_notes => journal.try(:notes).present?
}
end
def message_issue(message)
journal = message.journal
return nil unless journal
journal.try(:issue) || journal.try(:journalized)
end
def iso8601(value)
value.try(:utc).try(:iso8601)
end
end