Skip to content

Commit

Permalink
refactor(admin api) support CRUD on all core entities
Browse files Browse the repository at this point in the history
Former-commit-id: 450f55d90c2247fab8bdf654525377b9cc4dc0c8
  • Loading branch information
thibaultcha committed May 28, 2015
1 parent 6f9c97c commit 7db809e
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 119 deletions.
131 changes: 97 additions & 34 deletions kong/api/app.lua
Original file line number Diff line number Diff line change
@@ -1,46 +1,90 @@
local lapis = require "lapis"
local utils = require "kong.tools.utils"
local stringy = require "stringy"
local responses = require "kong.tools.responses"
local constants = require "kong.constants"

local Apis = require "kong.api.routes.apis"
--local Consumers = require "kong.api.routes.consumers"
--local PluginsConfigurations = require "kong.api.routes.plugins_configurations"

local app_helpers = require "lapis.application"
local app = lapis.Application()

local BaseController = require "kong.api.routes.base_controller"
-- Put nested keys in objects:
-- Normalize dotted keys in objects.
-- Example: {["key.value.sub"]=1234} becomes {key = {value = {sub=1234}}
-- @param `obj` Object to normalize
-- @return `normalized_object`
local function normalize_nested_params(obj)
local normalized_obj = {} -- create a copy to not modify obj while it is in a loop.

for k, v in pairs(obj) do
if type(v) == "table" then
-- normalize arrays since Lapis parses ?key[1]=foo as {["1"]="foo"} instead of {"foo"}
if utils.is_array(v) then
local arr = {}
for _, arr_v in pairs(v) do table.insert(arr, arr_v) end
v = arr
else
v = normalize_nested_params(v) -- recursive call on other table values
end
end

local function get_hostname()
local f = io.popen ("/bin/hostname")
local hostname = f:read("*a") or ""
f:close()
hostname = string.gsub(hostname, "\n$", "")
return hostname
-- normalize sub-keys with dot notation
local keys = stringy.split(k, ".")
if #keys > 1 then -- we have a key containing a dot
local current_level = keys[1] -- let's create an empty object for the first level
if normalized_obj[current_level] == nil then
normalized_obj[current_level] = {}
end
table.remove(keys, 1) -- remove the first level
normalized_obj[k] = nil -- remove it from the object
if #keys > 0 then -- if we still have some keys, then there are more levels of nestinf
normalized_obj[current_level][table.concat(keys, ".")] = v
normalized_obj[current_level] = normalize_nested_params(normalized_obj[current_level])
else
normalized_obj[current_level] = v -- latest level of nesting, attaching the value
end
else
normalized_obj[k] = v -- nothing special with that key, simply attaching the value
end
end

return normalized_obj
end

app:get("/", function(self)
local db_plugins, err = dao.plugins_configurations:find_distinct()
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
local function default_on_error(self)
local err = self.errors[1]
if type(err) == "table" then
if err.database then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err.message)
elseif err.unique then
return responses.send_HTTP_CONFLICT(err.message)
elseif err.foreign then
return responses.send_HTTP_NOT_FOUND(err.message)
elseif err.invalid_type and err.message.id then
return responses.send_HTTP_BAD_REQUEST(err.message)
else
return responses.send_HTTP_BAD_REQUEST(err.message)
end
end
end

return responses.send_HTTP_OK({
tagline = "Welcome to Kong",
version = constants.VERSION,
hostname = get_hostname(),
plugins = {
available_on_server = configuration.plugins_available,
enabled_in_cluster = db_plugins
},
lua_version = jit and jit.version or _VERSION
})
end)
local function parse_params(fn)
return app_helpers.json_params(function(self, ...)
local content_type = self.req.headers["content-type"]
if content_type and string.find(content_type:lower(), "application/json", nil, true) then
if not self.json then
return responses.send_HTTP_BAD_REQUEST("Cannot parse JSON body")
end
end
self.params = normalize_nested_params(self.params)
return fn(self, ...)
end)
end

app.default_route = function(self)
local path = self.req.parsed_url.path:match("^(.*)/$")

if path and self.app.router:resolve(path, self) then
return
elseif self.app.router:resolve(self.req.parsed_url.path.."/", self) then
return
end

return self.app.handle_404(self)
Expand Down Expand Up @@ -70,13 +114,32 @@ app.handle_error = function(self, err, trace)
end
end

-- Load controllers
--Apis(app, dao)
--Consumers()
--PluginsConfigurations()
local handler_helpers = {
responses = responses,
yield_error = app_helpers.yield_error
}

local kong_app = BaseController(app, dao)
kong_app:attach(Apis)
local function attach_routes(routes)
for route_path, methods in pairs(routes) do
if not methods.on_error then
methods.on_error = default_on_error
end

for k, v in pairs(methods) do
local method = function(self)
return v(self, dao, handler_helpers)
end
methods[k] = parse_params(method)
end

app:match(route_path, route_path, app_helpers.respond_to(methods))
end
end

for _, v in ipairs({"kong", "apis", "consumers", "plugins_configurations"}) do
local routes = require("kong.api.routes."..v)
attach_routes(routes)
end

-- Loading plugins routes
--[[if configuration and configuration.plugins_available then
Expand Down
88 changes: 88 additions & 0 deletions kong/api/crud_helpers.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
local responses = require "kong.tools.responses"
local app_helpers = require "lapis.application"

local _M = {}

function _M.paginated_set(self, dao_collection)
local size = self.params.size and tonumber(self.params.size) or 100
local offset = self.params.offset and ngx.decode_base64(self.params.offset) or nil

self.params.size = nil
self.params.offset = nil

local data, err = dao_collection:find_by_keys(self.params, size, offset)
if err then
return app_helpers.yield_error(err)
end

local next_url
if data.next_page then
next_url = self:build_url(self.req.parsed_url.path, {
port = self.req.parsed_url.port,
query = ngx.encode_args({
offset = ngx.encode_base64(data.next_page),
size = size
})
})
data.next_page = nil
end

-- This check is required otherwise the response is going to be a
-- JSON Object and not a JSON array. The reason is because an empty Lua array `{}`
-- will not be translated as an empty array by cjson, but as an empty object.
local result = #data == 0 and "{\"data\":[]}" or {data=data, ["next"]=next_url}

return responses.send_HTTP_OK(result, type(result) ~= "table")
end

function _M.put(self, dao_collection)
local new_entity, err
if self.params.id then
new_entity, err = dao_collection:update(self.params)
if not err then
return responses.send_HTTP_OK(new_entity)
end
else
new_entity, err = dao_collection:insert(self.params)
if not err then
return responses.send_HTTP_CREATED(new_entity)
end
end

if err then
return app_helpers.yield_error(err)
end
end

function _M.post(self, dao_collection)
local data, err = dao_collection:insert(self.params)
if err then
return app_helpers.yield_error(err)
else
return responses.send_HTTP_CREATED(data)
end
end

function _M.patch(params, dao_collection)
local new_entity, err = dao_collection:update(params)
if err then
return app_helpers.yield_error(err)
else
return responses.send_HTTP_OK(new_entity)
end
end

function _M.delete(entity_id, dao_collection)
local ok, err = dao_collection:delete(entity_id)
if not ok then
if err then
return app_helpers.yield_error(err)
else
return responses.send_HTTP_NOT_FOUND()
end
else
return responses.send_HTTP_NO_CONTENT()
end
end

return _M
105 changes: 52 additions & 53 deletions kong/api/routes/apis.lua
Original file line number Diff line number Diff line change
@@ -1,74 +1,73 @@
local validations = require("kong.dao.schemas")
local validations = require "kong.dao.schemas"
local crud = require "kong.api.crud_helpers"

local function find_by_name_or_id(self, dao_factory, helpers)
local fetch_keys = {
[validations.is_valid_uuid(self.params.name_or_id) and "id" or "name"] = self.params.name_or_id
}
self.params.name_or_id = nil

-- TODO: make the base_dao more flexible so we can query find_one with key/values
-- https://github.com/Mashape/kong/issues/103
local data, err = dao_factory.apis:find_by_keys(fetch_keys)
if err then
return helpers.yield_error(err)
end

self.api = data[1]
if not self.api then
return helpers.responses.send_HTTP_NOT_FOUND()
end
end

return {
["/apis/"] = {
GET = function(self, dao_factory, helpers)
helpers.return_paginated_set(self, dao_factory.apis)
GET = function(self, dao_factory)
crud.paginated_set(self, dao_factory.apis)
end,

PUT = function(self, dao_factory, helpers)
local new_api, err
if self.params.id then
new_api, err = dao_factory.apis:update(self.params)
if not err then
return helpers.responses.send_HTTP_OK(new_api)
end
else
new_api, err = dao_factory.apis:insert(self.params)
if not err then
return helpers.responses.send_HTTP_CREATED(new_api)
end
end

if err then
return helpers.yield_error(err)
end
PUT = function(self, dao_factory)
crud.put(self, dao_factory.apis)
end,

POST = function(self, dao_factory, helpers)
local data, err = dao_factory.apis:insert(self.params)
if err then
return helpers.yield_error(err)
else
return helpers.responses.send_HTTP_CREATED(data)
end
POST = function(self, dao_factory)
crud.post(self, dao_factory.apis)
end
},

["/apis/:name_or_id"] = {
before = function(self, dao_factory, helpers)
local fetch_keys = {
[validations.is_valid_uuid(self.params.name_or_id) and "id" or "name"] = self.params.name_or_id
}
self.params.name_or_id = nil

-- TODO: make the base_dao more flexible so we can query find_one with key/values
-- https://github.com/Mashape/kong/issues/103
local data, err = dao_factory.apis:find_by_keys(fetch_keys)
if err then
return helpers.yield_error(err)
end

self.api = data[1]
if not self.api then
return helpers.responses.send_HTTP_NOT_FOUND()
end
end,
before = find_by_name_or_id,

GET = function(self, dao_factory, helpers)
return helpers.responses.send_HTTP_OK(self.api)
end,

PATCH = function(self, dao_factory, helpers)
PATCH = function(self, dao_factory)
self.params.id = self.api.id
crud.patch(self.params, dao_factory.apis)
end,

local new_api, err = dao_factory.apis:update(self.params)
if err then
return helpers.yield_error(err)
else
return helpers.responses.send_HTTP_OK(new_api)
end
DELETE = function(self, dao_factory)
crud.delete(self.api.id, dao_factory.apis)
end
},

["/apis/:name_or_id/plugins/"] = {
before = function(self, dao_factory, helpers)
find_by_name_or_id(self, dao_factory, helpers)
self.params.api_id = self.api.id
end,

GET = function(self, dao_factory)
crud.paginated_set(self, dao_factory.plugins_configurations)
end,

POST = function(self, dao_factory, helpers)
crud.post(self, dao_factory.plugins_configurations)
end,

PUT = function(self, dao_factory, helpers)
crud.put(self, dao_factory.plugins_configurations)
end
}
}

Loading

0 comments on commit 7db809e

Please sign in to comment.