Streamline of rest api for tickets, users and organisations. Added pagination for search and index. Added auto lookup for id values (e. g. use group: 'some name', backend will automatically replace "group: 'some name'" by "group_id: 123").

This commit is contained in:
Martin Edenhofer 2016-06-06 17:26:37 +02:00
parent 65af185847
commit 54f6c456b7
12 changed files with 422 additions and 140 deletions

View file

@ -144,7 +144,7 @@ class App.TicketZoom extends App.Controller
@ajax(
id: "ticket_zoom_#{ticket_id}"
type: 'GET'
url: "#{@apiPath}/ticket_full/#{ticket_id}"
url: "#{@apiPath}/tickets/#{ticket_id}?full=true"
processData: true
success: (data, status, xhr) =>

View file

@ -381,8 +381,11 @@ class ApplicationController < ActionController::Base
# model helper
def model_create_render(object, params)
clean_params = object.param_association_lookup(params)
clean_params = object.param_cleanup(clean_params, true)
# create object
generic_object = object.new(object.param_cleanup(params[object.to_app_model_url], true ))
generic_object = object.new(clean_params)
# save object
generic_object.save!
@ -406,8 +409,11 @@ class ApplicationController < ActionController::Base
# find object
generic_object = object.find(params[:id])
clean_params = object.param_association_lookup(params)
clean_params = object.param_cleanup(clean_params, true)
# save object
generic_object.update_attributes!(object.param_cleanup(params[object.to_app_model_url]))
generic_object.update_attributes!(clean_params)
# set relations
generic_object.param_set_associations(params)
@ -458,11 +464,16 @@ class ApplicationController < ActionController::Base
end
def model_index_render(object, params)
offset = 0
per_page = 1000
if params[:page] && params[:per_page]
offset = (params[:page].to_i - 1) * params[:per_page].to_i
generic_objects = object.limit(params[:per_page]).offset(offset)
limit = params[:per_page].to_i
end
generic_objects = if offset > 0
object.limit(params[:per_page]).offset(offset).limit(limit)
else
generic_objects = object.all
object.all.offset(offset).limit(limit)
end
if params[:full]

View file

@ -47,16 +47,38 @@ curl http://localhost/api/v1/organizations -v -u #{login}:#{password}
=end
def index
offset = 0
per_page = 1000
if params[:page] && params[:per_page]
offset = (params[:page].to_i - 1) * params[:per_page].to_i
per_page = params[:per_page].to_i
end
# only allow customer to fetch his own organization
organizations = []
if role?(Z_ROLENAME_CUSTOMER) && !role?(Z_ROLENAME_ADMIN) && !role?(Z_ROLENAME_AGENT)
if current_user.organization_id
organizations = Organization.where( id: current_user.organization_id )
organizations = Organization.where(id: current_user.organization_id).offset(offset).limit(per_page)
end
else
organizations = Organization.all
organizations = Organization.all.offset(offset).limit(per_page)
end
if params[:full]
assets = {}
item_ids = []
organizations.each {|item|
item_ids.push item.id
assets = item.assets(assets)
}
render json: {
record_ids: item_ids,
assets: assets,
}, status: :ok
return
end
render json: organizations
end
@ -178,6 +200,68 @@ curl http://localhost/api/v1/organization/{id} -v -u #{login}:#{password} -H "Co
model_destory_render(Organization, params)
end
def search
if role?(Z_ROLENAME_CUSTOMER) && !role?(Z_ROLENAME_ADMIN) && !role?(Z_ROLENAME_AGENT)
response_access_deny
return
end
# set limit for pagination if needed
if params[:page] && params[:per_page]
params[:limit] = params[:page].to_i * params[:per_page].to_i
end
query_params = {
query: params[:term],
limit: params[:limit],
current_user: current_user,
}
if params[:role_ids] && !params[:role_ids].empty?
query_params[:role_ids] = params[:role_ids]
end
# do query
organization_all = Organization.search(query_params)
# do pagination if needed
if params[:page] && params[:per_page]
offset = (params[:page].to_i - 1) * params[:per_page].to_i
organization_all = organization_all.slice(offset, params[:per_page].to_i) || []
end
if params[:expand]
render json: organization_all
return
end
# build result list
if !params[:full]
organizations = []
organization_all.each { |organization|
a = { id: organization.id, label: organization.name }
organizations.push a
}
# return result
render json: organizations
return
end
organization_ids = []
assets = {}
organization_all.each { |organization|
assets = organization.assets(assets)
organization_ids.push organization.id
}
# return result
render json: {
assets: assets,
organization_ids: organization_ids.uniq,
}
end
# GET /api/v1/organizations/history/1
def history

View file

@ -3,23 +3,60 @@
class TicketsController < ApplicationController
before_action :authentication_check
# GET /api/v1/tickets
def index
offset = 0
per_page = 100
if params[:page] && params[:per_page]
offset = (params[:page].to_i - 1) * params[:per_page].to_i
per_page = params[:per_page].to_i
end
access_condition = Ticket.access_condition(current_user)
tickets = Ticket.where(access_condition).offset(offset).limit(per_page)
if params[:full]
assets = {}
item_ids = []
tickets.each {|item|
item_ids.push item.id
assets = item.assets(assets)
}
render json: {
record_ids: item_ids,
assets: assets,
}, status: :ok
return
end
render json: tickets
end
# GET /api/v1/tickets/1
def show
@ticket = Ticket.find( params[:id] )
# permission check
return if !ticket_permission(@ticket)
ticket = Ticket.find(params[:id])
return if !ticket_permission(ticket)
render json: @ticket
if params[:full]
render json: ticket_full(ticket)
return
end
render json: ticket
end
# POST /api/v1/tickets
def create
ticket = Ticket.new( Ticket.param_validation( params[:ticket] ) )
clean_params = Ticket.param_association_lookup(params)
clean_params = Ticket.param_cleanup(clean_params, true)
ticket = Ticket.new(clean_params)
# check if article is given
if !params[:article]
render json: 'article hash is missing', status: :unprocessable_entity
render json: { error: 'article hash is missing' }, status: :unprocessable_entity
return
end
@ -52,12 +89,15 @@ class TicketsController < ApplicationController
# PUT /api/v1/tickets/1
def update
ticket = Ticket.find(params[:id])
# permission check
ticket = Ticket.find(params[:id])
return if !ticket_permission(ticket)
if ticket.update_attributes(Ticket.param_validation(params[:ticket]))
clean_params = Ticket.param_association_lookup(params)
clean_params = Ticket.param_cleanup(clean_params, true)
if ticket.update_attributes(clean_params)
if params[:article]
article_create(ticket, params[:article])
@ -71,9 +111,9 @@ class TicketsController < ApplicationController
# DELETE /api/v1/tickets/1
def destroy
ticket = Ticket.find(params[:id])
# permission check
ticket = Ticket.find(params[:id])
return if !ticket_permission(ticket)
ticket.destroy
@ -204,68 +244,6 @@ class TicketsController < ApplicationController
}
end
# GET /api/v1/ticket_full/1
def ticket_full
# permission check
ticket = Ticket.find(params[:id])
return if !ticket_permission(ticket)
# get attributes to update
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(user: current_user, ticket: ticket)
# get related users
assets = attributes_to_change[:assets]
assets = ticket.assets(assets)
# get related articles
articles = Ticket::Article.where(ticket_id: params[:id]).order('created_at ASC, id ASC')
# get related users
article_ids = []
articles.each {|article|
# ignore internal article if customer is requesting
next if article.internal == true && role?(Z_ROLENAME_CUSTOMER)
# load article ids
article_ids.push article.id
# load assets
assets = article.assets(assets)
}
# get links
links = Link.list(
link_object: 'Ticket',
link_object_value: ticket.id,
)
link_list = []
links.each { |item|
link_list.push item
if item['link_object'] == 'Ticket'
linked_ticket = Ticket.lookup(id: item['link_object_value'])
assets = linked_ticket.assets(assets)
end
}
# get tags
tags = Tag.tag_list(
object: 'Ticket',
o_id: ticket.id,
)
# return result
render json: {
ticket_id: ticket.id,
ticket_article_ids: article_ids,
assets: assets,
links: link_list,
tags: tags,
form_meta: attributes_to_change[:form_meta],
}
end
# GET /api/v1/ticket_split
def ticket_split
@ -528,7 +506,10 @@ class TicketsController < ApplicationController
# create article if given
form_id = params[:form_id]
params.delete(:form_id)
article = Ticket::Article.new(Ticket::Article.param_validation( params ))
clean_params = Ticket::Article.param_association_lookup(params)
clean_params = Ticket::Article.param_cleanup(clean_params, true)
article = Ticket::Article.new(clean_params)
article.ticket_id = ticket.id
# store dataurl images to store
@ -573,4 +554,62 @@ class TicketsController < ApplicationController
o_id: form_id,
)
end
def ticket_full(ticket)
# get attributes to update
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(user: current_user, ticket: ticket)
# get related users
assets = attributes_to_change[:assets]
assets = ticket.assets(assets)
# get related articles
articles = Ticket::Article.where(ticket_id: ticket.id).order('created_at ASC, id ASC')
# get related users
article_ids = []
articles.each {|article|
# ignore internal article if customer is requesting
next if article.internal == true && role?(Z_ROLENAME_CUSTOMER)
# load article ids
article_ids.push article.id
# load assets
assets = article.assets(assets)
}
# get links
links = Link.list(
link_object: 'Ticket',
link_object_value: ticket.id,
)
link_list = []
links.each { |item|
link_list.push item
if item['link_object'] == 'Ticket'
linked_ticket = Ticket.lookup(id: item['link_object_value'])
assets = linked_ticket.assets(assets)
end
}
# get tags
tags = Tag.tag_list(
object: 'Ticket',
o_id: ticket.id,
)
# return result
{
ticket_id: ticket.id,
ticket_article_ids: article_ids,
assets: assets,
links: link_list,
tags: tags,
form_meta: attributes_to_change[:form_meta],
}
end
end

View file

@ -13,13 +13,34 @@ class UsersController < ApplicationController
# @response_message 200 [Array<User>] List of matching User records.
# @response_message 401 Invalid session.
def index
offset = 0
per_page = 1000
if params[:page] && params[:per_page]
offset = (params[:page].to_i - 1) * params[:per_page].to_i
per_page = params[:per_page].to_i
end
# only allow customer to fetch him self
users = if role?(Z_ROLENAME_CUSTOMER) && !role?(Z_ROLENAME_ADMIN) && !role?('Agent')
User.where(id: current_user.id)
User.where(id: current_user.id).offset(offset).limit(per_page)
else
User.all
User.all.offset(offset).limit(per_page)
end
if params[:full]
assets = {}
item_ids = []
users.each {|item|
item_ids.push item.id
assets = item.assets(assets)
}
render json: {
record_ids: item_ids,
assets: assets,
}, status: :ok
return
end
users_all = []
users.each {|user|
users_all.push User.lookup(id: user.id).attributes_with_associations
@ -69,7 +90,10 @@ class UsersController < ApplicationController
# in case of authentication, set current_user to access later
authentication_check_only({})
user = User.new(User.param_cleanup(params, true))
clean_params = User.param_association_lookup(params)
clean_params = User.param_cleanup(clean_params, true)
user = User.new(clean_params)
user.param_set_associations(params)
begin
# check if it's first user
@ -194,27 +218,30 @@ class UsersController < ApplicationController
return if !permission_check
user = User.find(params[:id])
clean_params = User.param_association_lookup(params)
clean_params = User.param_cleanup(clean_params, true)
begin
# permission check by role
return if !permission_check_by_role(params)
user.update_attributes( User.param_cleanup(params) )
user.update_attributes(clean_params)
# only allow Admin's and Agent's
if role?(Z_ROLENAME_ADMIN) && role?('Agent') && params[:role_ids]
if role?(Z_ROLENAME_ADMIN) && role?('Agent') && (params[:role_ids] || params[:roles])
user.role_ids = params[:role_ids]
user.param_set_associations({ role_ids: params[:role_ids], roles: params[:roles] })
end
# only allow Admin's
if role?(Z_ROLENAME_ADMIN) && params[:group_ids]
if role?(Z_ROLENAME_ADMIN) && (params[:group_ids] || params[:groups])
user.group_ids = params[:group_ids]
user.param_set_associations({ group_ids: params[:group_ids], groups: params[:groups] })
end
# only allow Admin's and Agent's
if role?(Z_ROLENAME_ADMIN) && role?('Agent') && params[:organization_ids]
user.organization_ids = params[:organization_ids]
if role?(Z_ROLENAME_ADMIN) && role?('Agent') && (params[:organization_ids] || params[:organizations])
user.param_set_associations({ organization_ids: params[:organization_ids], organizations: params[:organizations] })
end
# get new data
@ -262,11 +289,16 @@ class UsersController < ApplicationController
# @response_message 401 Invalid session.
def search
if role?(Z_ROLENAME_CUSTOMER) && !role?(Z_ROLENAME_ADMIN) && !role?('Agent')
if role?(Z_ROLENAME_CUSTOMER) && !role?(Z_ROLENAME_ADMIN) && !role?(Z_ROLENAME_AGENT)
response_access_deny
return
end
# set limit for pagination if needed
if params[:page] && params[:per_page]
params[:limit] = params[:page].to_i * params[:per_page].to_i
end
query_params = {
query: params[:term],
limit: params[:limit],
@ -279,6 +311,17 @@ class UsersController < ApplicationController
# do query
user_all = User.search(query_params)
# do pagination if needed
if params[:page] && params[:per_page]
offset = (params[:page].to_i - 1) * params[:per_page].to_i
user_all = user_all.slice(offset, params[:per_page].to_i) || []
end
if params[:expand]
render json: user_all
return
end
# build result list
if !params[:full]
users = []

View file

@ -68,26 +68,35 @@ returns
=end
def self.param_cleanup(params, newObject = false)
def self.param_cleanup(params, new_object = false)
if params.respond_to?('permit!')
params.permit!
end
if params.nil?
raise "No params for #{self}!"
end
data = {}
params.each {|key, value|
data[key.to_sym] = value
}
# ignore id for new objects
if newObject && params[:id]
params[:id] = nil
if new_object && params[:id]
data.delete(:id)
end
# only use object attributes
data = {}
new.attributes.each {|item|
next if !params.key?(item[0])
data[item[0].to_sym] = params[item[0]]
clean_params = {}
new.attributes.each {|attribute, _value|
next if !data.key?(attribute.to_sym)
clean_params[attribute.to_sym] = data[attribute.to_sym]
}
# we do want to set this via database
param_validation(data)
param_validation(clean_params)
end
=begin
@ -105,22 +114,62 @@ returns
def param_set_associations(params)
# set relations
# set relations by id/verify if ref exists
self.class.reflect_on_all_associations.map { |assoc|
real_key = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
next if !params.key?(real_key.to_sym)
list_of_items = params[ real_key.to_sym ]
if params[ real_key.to_sym ].class != Array
list_of_items = [ params[ real_key.to_sym ] ]
real_ids = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
real_ids = real_ids.to_sym
next if !params.key?(real_ids)
list_of_items = params[real_ids]
if params[real_ids].class != Array
list_of_items = [ params[real_ids] ]
end
list = []
list_of_items.each {|item|
next if !item
list.push(assoc.klass.find(item))
list_of_items.each {|item_id|
next if !item_id
lookup = assoc.klass.lookup(id: item_id)
# complain if we found no reference
if !lookup
raise "No value found for '#{assoc.name}' with id #{item_id.inspect}"
end
list.push item_id
}
send(assoc.name.to_s + '=', list)
#p "SEND #{real_ids} = #{list.inspect}"
send("#{real_ids}=", list)
}
# set relations by name/lookup
self.class.reflect_on_all_associations.map { |assoc|
real_ids = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
next if !respond_to?(real_ids)
real_values = assoc.name.to_s[0, assoc.name.to_s.length - 1] + 's'
real_values = real_values.to_sym
next if !respond_to?(real_values)
next if !params[real_values]
next if params[real_values].class != Array
list = []
class_object = assoc.klass
params[real_values].each {|value|
lookup = nil
if class_object == User
if !lookup
lookup = class_object.lookup(login: value)
end
if !lookup
lookup = class_object.lookup(email: value)
end
else
lookup = class_object.lookup(name: value)
end
# complain if we found no reference
if !lookup
raise "No lookup value found for '#{assoc.name}': #{value.inspect}"
end
list.push lookup.id
}
#p "SEND #{real_ids} = #{list.inspect}"
send("#{real_ids}=", list)
}
end
@ -139,13 +188,12 @@ returns
def attributes_with_associations
# set relations
# get relations
attributes = self.attributes
self.class.reflect_on_all_associations.map { |assoc|
real_key = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
if respond_to?(real_key)
attributes[ real_key ] = send(real_key)
end
real_ids = assoc.name.to_s[0, assoc.name.to_s.length - 1] + '_ids'
next if !respond_to?(real_ids)
attributes[real_ids] = send(real_ids)
}
attributes
end
@ -165,13 +213,67 @@ returns
def self.param_validation(data)
# we do want to set this via database
data.delete(:updated_at)
data.delete(:created_at)
data.delete(:updated_by_id)
data.delete(:created_by_id)
if data.respond_to?('permit!')
data.permit!
[:action, :controller, :updated_at, :created_at, :updated_by_id, :created_by_id, :updated_by, :created_by].each {|key|
data.delete(key)
}
data
end
=begin
do name/login/email based lookup for associations
attributes = Model.param_association_lookup(params)
returns
attributes = params # params with possible lookups
=end
def self.param_association_lookup(params)
data = {}
params.each {|key, value|
data[key.to_sym] = value
}
data.symbolize_keys!
available_attributes = attribute_names
reflect_on_all_associations.map { |assoc|
value = data[assoc.name.to_sym]
next if !value # next if we do not have a value
ref_name = "#{assoc.name}_id"
next if !available_attributes.include?(ref_name) # next if we do not have an _id attribute
next if data[ref_name.to_sym] # next if we have already the id filled
# get association class and do lookup
class_object = assoc.klass
lookup = nil
if class_object == User
if !lookup
lookup = class_object.lookup(login: value)
end
if !lookup
lookup = class_object.lookup(email: value)
end
else
lookup = class_object.lookup(name: value)
end
# complain if we found no reference
if !lookup
raise "No lookup value found for '#{assoc.name}': #{value.inspect}"
end
# release data value
data.delete(assoc.name.to_sym)
# remember id reference
data[ref_name.to_sym] = lookup.id
}
data
end

View file

@ -88,7 +88,7 @@ returns
# get model with full data
if !organization_exists
organizations.push Organization.find(organization_by_user)
organizations.push Organization.find(organization_by_user.id)
end
}
end

View file

@ -55,9 +55,9 @@ class Ticket < ApplicationModel
search_index_support
belongs_to :group
belongs_to :group, class_name: 'Group'
has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update
belongs_to :organization
belongs_to :organization, class_name: 'Organization'
belongs_to :state, class_name: 'Ticket::State'
belongs_to :priority, class_name: 'Ticket::Priority'
belongs_to :owner, class_name: 'User'

View file

@ -1,6 +1,7 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Ticket::State < ApplicationModel
belongs_to :state_type, class_name: 'Ticket::StateType'
belongs_to :next_state, class_name: 'Ticket::State'
validates :name, presence: true
latest_change_support

View file

@ -36,9 +36,9 @@ class User < ApplicationModel
after_destroy :avatar_destroy
notify_clients_support
has_and_belongs_to_many :groups, after_add: :cache_update, after_remove: :cache_update
has_and_belongs_to_many :roles, after_add: [:cache_update, :check_notifications], after_remove: :cache_update
has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update
has_and_belongs_to_many :groups, after_add: :cache_update, after_remove: :cache_update, class_name: 'Group'
has_and_belongs_to_many :roles, after_add: [:cache_update, :check_notifications], after_remove: :cache_update, class_name: 'Role'
has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization'
has_many :tokens, after_add: :cache_update, after_remove: :cache_update
has_many :authorizations, after_add: :cache_update, after_remove: :cache_update
belongs_to :organization, class_name: 'Organization'

View file

@ -2,6 +2,7 @@ Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
# organizations
match api_path + '/organizations/search', to: 'organizations#search', via: [:get, :post]
match api_path + '/organizations', to: 'organizations#index', via: :get
match api_path + '/organizations/:id', to: 'organizations#show', via: :get
match api_path + '/organizations', to: 'organizations#create', via: :post

View file

@ -4,12 +4,13 @@ Zammad::Application.routes.draw do
# tickets
match api_path + '/tickets/search', to: 'tickets#search', via: [:get, :post]
match api_path + '/tickets/selector', to: 'tickets#selector', via: :post
match api_path + '/tickets', to: 'tickets#index', via: :get
match api_path + '/tickets/:id', to: 'tickets#show', via: :get
match api_path + '/tickets', to: 'tickets#create', via: :post
match api_path + '/tickets/:id', to: 'tickets#update', via: :put
match api_path + '/tickets/:id', to: 'tickets#destroy', via: :delete
match api_path + '/ticket_create', to: 'tickets#ticket_create', via: :get
match api_path + '/ticket_split', to: 'tickets#ticket_split', via: :get
match api_path + '/ticket_full/:id', to: 'tickets#ticket_full', via: :get
match api_path + '/ticket_history/:id', to: 'tickets#ticket_history', via: :get
match api_path + '/ticket_customer', to: 'tickets#ticket_customer', via: :get
match api_path + '/ticket_related/:ticket_id', to: 'tickets#ticket_related', via: :get