From 559c1c85d09a48272ad0e2cdecbedfbc108f2268 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 17:24:24 +0800 Subject: [PATCH 001/165] change: pdk.const add http content type --- apioak/pdk/const.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apioak/pdk/const.lua b/apioak/pdk/const.lua index dae129d..8bff543 100644 --- a/apioak/pdk/const.lua +++ b/apioak/pdk/const.lua @@ -8,6 +8,12 @@ _M.BALANCER_CHASH = "chash" _M.BALANCER_ROUNDROBIN = "roundrobin" +_M.CONTENT_TYPE_JSON = "application/json" + +_M.CONTENT_TYPE_HTML = "text/html" + +_M.CONTENT_TYPE_XML = "text/xml" + _M.BALANCER_COMMECT_TIMEOUT = 60 _M.BALANCER_SEND_TIMEOUT = 60 -- Gitee From 67ada87dd9bccf7dddc57b47f8e9a72386e69ea9 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 17:26:12 +0800 Subject: [PATCH 002/165] feature: add pdk.mysql module. --- apioak/pdk/mysql.lua | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 apioak/pdk/mysql.lua diff --git a/apioak/pdk/mysql.lua b/apioak/pdk/mysql.lua new file mode 100644 index 0000000..d0ffdd6 --- /dev/null +++ b/apioak/pdk/mysql.lua @@ -0,0 +1,56 @@ +local config = require("apioak.pdk.config") +local mysql = require("resty.mysql") + +local oak_conf = config.all() +local my_conf = oak_conf.mysql + +local _M = {} + +function _M.new() + local db + local ok + local err + + db, err = mysql:new() + if not db then + return nil, err + end + + db:set_timeout(my_conf.timeout or 1000) + + ok, err = db:connect({ + host = my_conf.host or "127.0.0.1", + port = my_conf.port or 3306, + database = my_conf.database or "apioak", + user = my_conf.user or "apioak", + password = my_conf.password or "" + }) + + if not ok then + return nil, err + end + + db.close = close + return db, nil +end + +function close(self) + local sock = self.sock + if not sock then + return nil, "not initialized" + end + if self.subscribed then + return nil, "subscribed state" + end + return sock:setkeepalive(my_conf.max_idle_timeout, my_conf.pool_size) +end + + +function _M.execute(sql) + local my_cli = _M.new() + local res, err = my_cli:query(sql) + my_cli:close() + return res, err +end + +return _M -- Gitee From 8eef01362c21eb615499b19510144a41b7ea69e4 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 17:31:39 +0800 Subject: [PATCH 003/165] change: add mysql default config and library. --- conf/apioak.yaml | 10 ++++++++++ rockspec/apioak-master-0.rockspec | 1 + 2 files changed, 11 insertions(+) diff --git a/conf/apioak.yaml b/conf/apioak.yaml index 113ef83..fc174be 100644 --- a/conf/apioak.yaml +++ b/conf/apioak.yaml @@ -3,6 +3,16 @@ etcd: prefix: "/apioak" # apioak config prefix timeout: 3 # 3 seconds +mysql: + host: 127.0.0.1 + port: 3306, + database: apioak, + user: root + password: 123456 + timeout: 1000 # millisecond + pool_size: 100 + max_idle_timeout: 10000 # millisecond + plugins: - limit-req - limit-count diff --git a/rockspec/apioak-master-0.rockspec b/rockspec/apioak-master-0.rockspec index b606d38..7284bdd 100644 --- a/rockspec/apioak-master-0.rockspec +++ b/rockspec/apioak-master-0.rockspec @@ -23,6 +23,7 @@ dependencies = { "lua-resty-jwt = 0.2.0", "lua-resty-libr3 = 1.2-0", "lua-resty-http = 0.15-0", + "lua-resty-mysql = 0.15-0", "jsonschema = 0.4", "luasocket = 3.0rc1-2", "luafilesystem = 1.7.0-2", -- Gitee From 28869af50562b278677ade4fa3dce0e56e1f0438 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 17:34:54 +0800 Subject: [PATCH 004/165] feature: pdk.string add md5 and null functions. --- apioak/pdk/string.lua | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apioak/pdk/string.lua b/apioak/pdk/string.lua index ba3fa31..be916d3 100644 --- a/apioak/pdk/string.lua +++ b/apioak/pdk/string.lua @@ -2,16 +2,22 @@ local plstring = require("pl.stringx") local _M = {} -_M.format = string.format +_M.format = string.format -_M.lower = string.lower +_M.lower = string.lower -_M.upper = string.upper +_M.upper = string.upper -_M.find = string.find +_M.find = string.find -_M.split = plstring.split +_M.split = plstring.split -_M.replace = plstring.replace +_M.replace = plstring.replace + +_M.md5 = ngx.md5 + +_M.null = ngx.null + +_M.tonumber = tonumber return _M -- Gitee From e2d0d35e0ba1293d23121ad97da3ab9b8bbb18f2 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 17:37:14 +0800 Subject: [PATCH 005/165] feature: add pdk.time module. --- apioak/pdk/time.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 apioak/pdk/time.lua diff --git a/apioak/pdk/time.lua b/apioak/pdk/time.lua new file mode 100644 index 0000000..3cabeb1 --- /dev/null +++ b/apioak/pdk/time.lua @@ -0,0 +1,16 @@ +local _M = {} + +_M.time = os.time + +_M.date = os.date + +_M.strtotime = function(date_str) + local _, _, year, month, day, hour, min, sec = string.find(date_str, + "(%d+)-(%d+)-(%d+)%s*(%d+):(%d+):(%d+)") + if not year or not month or not day or not hour or not min or not sec then + return nil, "params \"date_str\" invalid" + end + return os.time({ year = year, month = month, day = day, hour = hour, min = min, sec = sec }) +end + +return _M -- Gitee From 735a63614df17ad9eab04a697b36daa512ea046d Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 17:59:32 +0800 Subject: [PATCH 006/165] change: remove service and plugin and router schemas from pdk.schema. --- apioak/pdk/schema.lua | 284 ------------------------------------------ 1 file changed, 284 deletions(-) diff --git a/apioak/pdk/schema.lua b/apioak/pdk/schema.lua index 5153e8b..7623665 100644 --- a/apioak/pdk/schema.lua +++ b/apioak/pdk/schema.lua @@ -2,290 +2,6 @@ local jsonschema = require 'jsonschema' local _M = {} -local ipv4_pattern = "^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$" - -local host_pattern = "^\\*?[0-9a-zA-Z-.]+$" - -local id_define = { - anyOf = { - { - type = "string", minLength = 1, maxLength = 32, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } -} - -local router = { - type = 'object', - properties = { - name = { - type = 'string', - minLength = 1, - maxLength = 50 - }, - path = { - type = 'string', - minLength = 1, - maxLength = 100 - }, - method = { - type = "string", - enum = { "GET", "POST", "PUT", "DELETE", "HEAD" } - }, - enable_cors = { - type = "boolean", - enum = { true, false } - }, - desc = { - type = "string" - }, - request_params = { - type = "array", - uniqueItems = true, - items = { - type = "object", - properties = { - name = { - type = 'string', - minLength = 1, - maxLength = 50 - }, - position = { - type = "string", - enum = { "Header", "Path", "Query" } - }, - type = { - type = "string", - enum = { "string", "int", "long", "float", "double", "boolean" } - }, - default_val = { - type = "string" - }, - require = { - type = "boolean", - enum = { true, false } - }, - desc = { - type = "string", - minLength = 0, - maxLength = 90 - } - } - } - }, - service_path = { - type = 'string', - minLength = 1, - maxLength = 100 - }, - service_method = { - type = "string", - enum = { "GET", "POST", "PUT", "DELETE", "HEAD" } - }, - timeout = { - type = "number", - minimum = 1, - exclusiveMaximum = 181 - }, - service_params = { - type = "array", - uniqueItems = true, - items = { - type = "object", - properties = { - service_name = { - type = "string", - minLength = 1, - maxLength = 50 - }, - service_position = { - type = "string", - enum = { "Header", "Path", "Query" } - }, - name = { - type = 'string', - minLength = 1, - maxLength = 50 - }, - position = { - type = "string", - enum = { "Header", "Path", "Query" } - }, - type = { - type = "string", - enum = { "string", "int", "long", "float", "double", "boolean" } - }, - desc = { - type = "string" - } - } - } - }, - constant_params = { - type = "array", - uniqueItems = true, - items = { - type = "object", - properties = { - name = { - type = 'string', - minLength = 1, - maxLength = 50 - }, - position = { - type = "string", - enum = { "Header", "Path", "Query" } - }, - value = { - anyOf = { - { - type = "string", - }, - { - type = "number", - }, - { - type = "boolean", - } - } - }, - desc = { - type = "string", - } - } - } - }, - response_type = { - type = "string", - enum = { "JSON", "HTML", "TEXT", "XML", "BINARY" } - }, - response_success = { - type = "string", - }, - response_fail = { - type = "string", - }, - response_error_codes = { - type = "array", - uniqueItems = true, - items = { - type = "object", - properties = { - code = { - type = "number", - }, - msg = { - type = "string", - minLength = 0, - maxLength = 60 - }, - desc = { - type = "string", - minLength = 0, - maxLength = 90 - } - } - } - } - }, - required = { "name", "path", "method", "enable_cors", "service_path", "service_method", "response_type", "response_success", "response_fail"}, -} - -local service_upstreams = { - type = "object", - properties = { - host = { - type = "string", - pattern = host_pattern - }, - type = { - type = "string", - enum = { "chash", "roundrobin" } - }, - nodes = { - type = "array", - minItems = 1, - uniqueItems = true, - items = { - type = "object", - properties = { - ip = { - type = "string", - pattern = ipv4_pattern - }, - port = { - type = "number", - minimum = 1, - maximum = 65535, - }, - weight = { - type = "number", - minimum = 0, - maximum = 100, - }, - }, - required = { "ip", "port", "weight" } - } - }, - required = { "host", "type", "nodes" } - } -} - -local service = { - type = "object", - properties = { - id = id_define, - name = { - type = 'string', - minLength = 1, - maxLength = 50 - }, - prefix = { - type = 'string', - minLength = 1, - maxLength = 20 - }, - desc = { - type = 'string', - minLength = 0, - maxLength = 90 - }, - upstreams = { - type = "object", - minItems = 1, - properties = { - prod = service_upstreams, - beta = service_upstreams, - dev = service_upstreams - }, - } - }, - required = { "name", "prefix", "upstreams" }, -} - -local plugin = { - type = "object", - properties = { - key = { - type = "string", - }, - config = { - type = "object" - }, - }, - required = { "key" } -} - -_M.plugin = plugin - -_M.service = service - -_M.router = router - function _M.check(schema, json) local validator = jsonschema.generate_validator(schema) return validator(json) -- Gitee From 269278de4faf14151e2a22cae2252dce66cc860f Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:08:47 +0800 Subject: [PATCH 007/165] feature: add admin base controller. --- apioak/admin/controller.lua | 144 ++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 apioak/admin/controller.lua diff --git a/apioak/admin/controller.lua b/apioak/admin/controller.lua new file mode 100644 index 0000000..63b01a1 --- /dev/null +++ b/apioak/admin/controller.lua @@ -0,0 +1,144 @@ +local db = require("apioak.db") +local pdk = require("apioak.pdk") + +local controller = function(name) + + local cls = {} + + cls.__class = name + + cls.header_token_key = "APIOAK-ADMIN-TOKEN" + + cls.uid = nil + + cls.token = nil + + cls.is_owner = nil + + cls.get_body = function(key) + local body, err = pdk.request.body() + if err then + pdk.response.exit(500, { err_message = err }) + end + if key then + return body[key] + end + return body + end + + + cls.get_header = function(key) + return pdk.request.header(key) + end + + + cls.check_schema = function(schema, body) + local _, err = pdk.schema.check(schema, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + end + + + cls.get_header_token = function() + local token = cls.get_header(cls.header_token_key) + if not token then + pdk.response.exit(401, { err_message = pdk.string.format( + "property header \"%s\" is required", cls.header_token_key) }) + end + return token + end + + cls.get_account_token = function(token) + local res, err = db.token.query_by_token(token) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + pdk.response.exit(401, { err_message = "login account token invalid" }) + end + + return res[1] + end + + cls.get_account_info = function(uid) + local res, err = db.user.query_by_id(uid) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + pdk.response.exit(401, { err_message = "login account not exists" }) + end + + return res[1] + end + + cls.user_authenticate = function() + local header_token = cls.get_header_token() + local account_token = cls.get_account_token(header_token) + + local expired = pdk.time.strtotime(account_token.expired_at) + local now_time = pdk.time.time() + if expired < now_time then + pdk.response.exit(401, { err_message = "login status expired" }) + end + + local diff_time = expired - now_time + if diff_time < 3600 then + db.token.continue_by_token(cls.token) + end + + local account_info = cls.get_account_info(account_token.user_id) + cls.uid = account_info.id + cls.token = header_token + + if account_info.is_owner == 1 then + cls.is_owner = true + end + end + + cls.group_authenticate = function(group_id, user_id) + local res, err = db.role.query(group_id, user_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + if #res == 0 then + pdk.response.exit(401, { err_message = "no permission to operate on this group" }) + end + return res[1] + end + + cls.project_authenticate = function(project_id, user_id) + local res, err = db.project.query(project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + pdk.response.exit(500, { err_message = "project: " .. project_id .. " not exists" }) + end + + return cls.group_authenticate(res[1].group_id, user_id) + end + + cls.router_authenticate = function(router_id, user_id) + local res, err = db.router.query(router_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + pdk.response.exit(500, { err_message = "router: " .. router_id .. " not exists" }) + end + + return cls.project_authenticate(res[1].project_id, user_id) + end + + return cls +end + +return { + new = controller +} -- Gitee From dcd5a05651039c017d4be636bfdb86769bec7a32 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:10:13 +0800 Subject: [PATCH 008/165] feature: add admin.group controller. --- apioak/admin/group.lua | 209 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 apioak/admin/group.lua diff --git a/apioak/admin/group.lua b/apioak/admin/group.lua new file mode 100644 index 0000000..07ecb34 --- /dev/null +++ b/apioak/admin/group.lua @@ -0,0 +1,209 @@ +local db = require("apioak.db") +local pdk = require("apioak.pdk") +local schema = require("apioak.schema") +local controller = require("apioak.admin.controller") + +local group_controller = controller.new("group") + +function group_controller.list() + + group_controller.user_authenticate() + + local res, err + if group_controller.is_owner then + res, err = db.group.all(true) + else + res, err = db.group.query_by_uid(group_controller.uid) + end + + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", groups = res }) +end + +function group_controller.query(params) + + group_controller.check_schema(schema.group.query, params) + + group_controller.user_authenticate() + + local res, err = db.group.query(params.group_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res > 0 then + res = res[1] + end + + pdk.response.exit(200, { err_message = "OK", group = res }) +end + +function group_controller.created() + + group_controller.user_authenticate() + + if not group_controller.is_owner then + pdk.response.exit(401, { err_message = "no permission to create group" }) + end + + local body = group_controller.get_body() + group_controller.check_schema(schema.group.created, body) + + local _, err = db.group.create(body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + + +function group_controller.updated(params) + + group_controller.user_authenticate() + + if not group_controller.is_owner then + pdk.response.exit(401, { err_message = "no permission to update group" }) + end + + local body = group_controller.get_body() + body.group_id = params.group_id + + group_controller.check_schema(schema.group.updated, body) + + local _, err = db.group.update(body.group_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function group_controller.deleted(params) + + group_controller.user_authenticate() + + if not group_controller.is_owner then + pdk.response.exit(401, { err_message = "no permission to update group" }) + end + + group_controller.check_schema(schema.group.deleted, params) + + local res, err = db.project.query_by_gid(params.group_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res > 0 then + pdk.response.exit(500, { err_message = "projects in group were not deleted" }) + end + + res, err = db.role.delete_by_gid(params.group_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + res, err = db.group.delete(params.group_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function group_controller.user_list(params) + + group_controller.user_authenticate() + + if not group_controller.is_owner then + group_controller.group_authenticate(params.group_id, group_controller.uid) + end + + group_controller.check_schema(schema.group.user_list, params) + + local res, err = db.user.query_by_gid(params.group_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", users = res }) +end + +function group_controller.user_create(params) + + group_controller.user_authenticate() + + local body = group_controller.get_body() + body.group_id = params.group_id + + group_controller.check_schema(schema.group.user_created, body) + + if not group_controller.is_owner then + local user_group = group_controller.group_authenticate(params.group_id, group_controller.uid) + if user_group.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create group member" }) + end + end + + local _, err = db.role.create(body.group_id, body.user_id, body.is_admin) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function group_controller.user_update(params) + + group_controller.user_authenticate() + + local body = group_controller.get_body() + body.user_id = params.user_id + body.group_id = params.group_id + + group_controller.check_schema(schema.group.user_updated, body) + + if not group_controller.is_owner then + local user_group = group_controller.group_authenticate(params.group_id, group_controller.uid) + if user_group.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to update group member" }) + end + end + + local _, err = db.role.update(body.group_id, body.user_id, body.is_admin) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function group_controller.user_delete(params) + + group_controller.user_authenticate() + + group_controller.check_schema(schema.group.user_deleted, params) + + if not group_controller.is_owner then + local user_group = group_controller.group_authenticate(params.group_id, group_controller.uid) + if user_group.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to delete group member" }) + end + end + + if params.user_id == group_controller.uid then + pdk.response.exit(401, { err_message = "no permission to delete this member" }) + end + + local _, err = db.role.delete(params.group_id, params.user_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +return group_controller -- Gitee From 01bb7646f5ef3b844661eb84ade735ced29fb527 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:11:57 +0800 Subject: [PATCH 009/165] feature: add admin.plugin controller --- apioak/admin/plugin.lua | 205 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 189 insertions(+), 16 deletions(-) diff --git a/apioak/admin/plugin.lua b/apioak/admin/plugin.lua index 98dc532..d1592e0 100644 --- a/apioak/admin/plugin.lua +++ b/apioak/admin/plugin.lua @@ -1,24 +1,197 @@ -local ipairs = ipairs -local pdk = require("apioak.pdk") +local db = require("apioak.db") +local pdk = require("apioak.pdk") +local schema = require("apioak.schema") +local controller = require("apioak.admin.controller") -local _M = {} +local plugin_controller = controller.new("plugin") -_M.cached_key = "/plugins" +function plugin_controller.plugin_list() + + plugin_controller.user_authenticate() -function _M.list() - local responses = {} local plugins = pdk.plugin.loading() - for _, plugin in ipairs(plugins) do - pdk.table.insert(responses, { - name = plugin.name, - type = plugin.type, - desc = plugin.desc, - key = plugin.key, - order = plugin.order, - parameter = plugin.parameter, + + local res = {} + for i = 1, #plugins do + pdk.table.insert(res, { + name = plugins[i].name, + type = plugins[i].type, + description = plugins[i].description, + config = plugins[i].config }) end - pdk.response.exit(200, responses) + + pdk.response.exit(200, { err_message = "OK", plugins = res }) +end + +function plugin_controller.project_list(params) + + plugin_controller.check_schema(schema.plugin.project_list, params) + + plugin_controller.user_authenticate() + + if not plugin_controller.is_owner then + local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create group member" }) + end + end + + local res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", plugins = res }) +end + +function plugin_controller.project_created(params) + + local body = plugin_controller.get_body() + body.project_id = params.project_id + + plugin_controller.check_schema(schema.plugin.project_created, body) + + plugin_controller.user_authenticate() + + if not plugin_controller.is_owner then + local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create group member" }) + end + end + + local _, err = db.plugin.create_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function plugin_controller.project_updated(params) + + local body = plugin_controller.get_body() + body.project_id = params.project_id + body.plugin_id = params.plugin_id + + plugin_controller.check_schema(schema.plugin.project_updated, body) + + plugin_controller.user_authenticate() + + if not plugin_controller.is_owner then + local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create group member" }) + end + end + + local _, err = db.plugin.update(params.plugin_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function plugin_controller.project_deleted(params) + + plugin_controller.check_schema(schema.plugin.project_deleted, params) + + plugin_controller.user_authenticate() + + if not plugin_controller.is_owner then + local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create group member" }) + end + end + + local _, err = db.plugin.delete(params.plugin_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function plugin_controller.router_list(params) + + plugin_controller.check_schema(schema.plugin.router_list, params) + + plugin_controller.user_authenticate() + + if not plugin_controller.is_owner then + plugin_controller.router_authenticate(params.router_id, plugin_controller.uid) + end + + local res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", plugins = res }) +end + +function plugin_controller.router_created(params) + + local body = plugin_controller.get_body() + body.router_id = params.router_id + + plugin_controller.check_schema(schema.plugin.router_created, body) + + plugin_controller.user_authenticate() + + if not plugin_controller.is_owner then + plugin_controller.router_authenticate(params.router_id, plugin_controller.uid) + end + + local _, err = db.plugin.create_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function plugin_controller.router_updated(params) + + local body = plugin_controller.get_body() + body.router_id = params.router_id + body.plugin_id = params.plugin_id + + plugin_controller.check_schema(schema.plugin.router_updated, body) + + plugin_controller.user_authenticate() + + if not plugin_controller.is_owner then + plugin_controller.router_authenticate(params.router_id, plugin_controller.uid) + end + + local _, err = db.plugin.update(params.plugin_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function plugin_controller.router_deleted(params) + + plugin_controller.check_schema(schema.plugin.router_deleted, params) + + plugin_controller.user_authenticate() + + if not plugin_controller.is_owner then + plugin_controller.router_authenticate(params.router_id, plugin_controller.uid) + end + + local _, err = db.plugin.delete(params.plugin_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) end -return _M +return plugin_controller -- Gitee From 7327a77ecb72c597570b131c0c51d73b15d1c148 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:13:18 +0800 Subject: [PATCH 010/165] feature: add admin.project controller. --- apioak/admin/project.lua | 162 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 apioak/admin/project.lua diff --git a/apioak/admin/project.lua b/apioak/admin/project.lua new file mode 100644 index 0000000..6ddc7b6 --- /dev/null +++ b/apioak/admin/project.lua @@ -0,0 +1,162 @@ +local db = require("apioak.db") +local pdk = require("apioak.pdk") +local schema = require("apioak.schema") +local controller = require("apioak.admin.controller") + +local project_controller = controller.new("project") + +function project_controller.list(params) + + project_controller.check_schema(schema.project.list, params) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + project_controller.group_authenticate(params.group_id, project_controller.uid) + end + + local res, err = db.project.query_by_gid(params.group_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", projects = res }) +end + +function project_controller.create(params) + + local body = project_controller.get_body() + body.group_id = params.group_id + + project_controller.check_schema(schema.project.created, body) + + project_controller.user_authenticate() + body.user_id = project_controller.uid + + if not project_controller.is_owner then + local user_group = project_controller.group_authenticate(params.group_id, project_controller.uid) + if user_group.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create group member" }) + end + end + + local res, err = db.project.create(body) + if err then + pdk.response.exit(500, { err_message = err }) + end + local project_id = res.insert_id + + for i = 1, #body.upstreams do + local upstream = body.upstreams[i] + upstream.project_id = project_id + res, err = db.upstream.create(upstream) + if err then + pdk.response.exit(500, { err_message = err }) + end + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function project_controller.update(params) + + local body = project_controller.get_body() + body.id = params.project_id + + project_controller.check_schema(schema.project.updated, body) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + local user_group = project_controller.group_authenticate(params.group_id, project_controller.uid) + if user_group.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create group member" }) + end + end + + local res, err = db.project.update(params.project_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + for i = 1, #body.upstreams do + local upstream = body.upstreams[i] + res, err = db.upstream.update(upstream.id, upstream) + if err then + pdk.response.exit(500, { err_message = err }) + end + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function project_controller.query(params) + + project_controller.check_schema(schema.project.query, params) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + project_controller.project_authenticate(params.project_id, project_controller.uid) + end + + local res, err = db.project.query(params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + pdk.response.exit(500, { err_message = "project: " .. params.project_id .. "not exists" }) + end + local project = res[1] + + res, err = db.upstream.query_by_pid(params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + project.upstreams = res + + pdk.response.exit(200, { err_message = "OK", project = project }) +end + +function project_controller.delete(params) + + project_controller.check_schema(schema.project.deleted, params) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + local user_group = project_controller.group_authenticate(params.group_id, project_controller.uid) + if user_group.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create group member" }) + end + end + + local res, err = db.router.query_by_pid(params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res > 0 then + pdk.response.exit(500, { err_message = "routers in project were not deleted" }) + end + + res, err = db.plugin.delete_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + res, err = db.upstream.delete_by_pid(params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + res, err = db.project.delete(params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +return project_controller -- Gitee From bb63fb1523eede227d186c2591f466e7eeb42769 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:13:59 +0800 Subject: [PATCH 011/165] feature: add admin.router controller. --- apioak/admin/router.lua | 301 ++++++++++++---------------------------- 1 file changed, 92 insertions(+), 209 deletions(-) diff --git a/apioak/admin/router.lua b/apioak/admin/router.lua index 0affdad..4f01f2b 100644 --- a/apioak/admin/router.lua +++ b/apioak/admin/router.lua @@ -1,284 +1,167 @@ -local pdk = require("apioak.pdk") -local ipairs = ipairs -local etcd_key -local uri_params +local db = require("apioak.db") +local pdk = require("apioak.pdk") +local schema = require("apioak.schema") +local controller = require("apioak.admin.controller") -local ENV_DEV = pdk.admin.ENV_DEV -local ENV_BETA = pdk.admin.ENV_BETA -local ENV_PROD = pdk.admin.ENV_PROD -local ENV_MASTER = pdk.admin.ENV_MASTER -local env_names = { ENV_PROD, ENV_BETA, ENV_DEV } +local router_controller = controller.new("router") -local _M = {} +function router_controller.list(params) -local function create_etcd_key(env, service_id, router_id) - etcd_key = pdk.admin.get_router_etcd_key(env, service_id, router_id) -end + router_controller.check_schema(schema.router.list, params) -local function get_service_id() - local service_id = pdk.request.header('APIOAK-SERVICE-ID') - if not service_id then - pdk.response.exit(500, { err_message = "property \"HEADER: APIOAK-SERVICE-ID\" is required" }) - end - return service_id -end + router_controller.user_authenticate() -local function get_body() - local body, err = pdk.request.body() - if err then - pdk.response.exit(500, { err_message = err }) + if not router_controller.is_owner then + router_controller.project_authenticate(params.project_id, router_controller.uid) end - return body -end -local function check_schema(schema, body) - local _, err = pdk.schema.check(schema, body) + local res, err = db.router.query_by_pid(params.project_id) if err then pdk.response.exit(500, { err_message = err }) end -end -local function get_uri_param(key) - local val = uri_params[key] or nil - if not val then - if key == "env" then - return ENV_MASTER - else - pdk.response.exit(500, - { err_message = pdk.string.format("property \"URI: %s\" is required", key) }) - end - end - return val + pdk.response.exit(200, { err_message = "OK", routers = res }) end -function _M.list(params) - uri_params = params - local env = get_uri_param('env') - local service_id = get_service_id() - - create_etcd_key(env, service_id) +function router_controller.created(params) - local data, code, err = pdk.etcd.query(etcd_key) - if err then - return pdk.response.exit(code, { err_message = err }) - end + local body = router_controller.get_body() + body.project_id = params.project_id - return pdk.response.exit(code, data) -end + router_controller.check_schema(schema.router.created, body) -function _M.query(params) - uri_params = params - local env = get_uri_param('env') - local router_id = get_uri_param('router_id') - local service_id = get_service_id() + router_controller.user_authenticate() - create_etcd_key(env, service_id, router_id) + if not router_controller.is_owner then + router_controller.project_authenticate(params.project_id, router_controller.uid) + end + body.user_id = router_controller.uid - local data, code, etcd_err = pdk.etcd.query(etcd_key) - if data then - pdk.response.exit(code, data) - else - pdk.response.exit(code, { err_message = etcd_err }) + local _, err = db.router.created(body) + if err then + pdk.response.exit(500, { err_message = err }) end + + pdk.response.exit(200, { err_message = "OK" }) end -function _M.create(params) - uri_params = params - local env = get_uri_param('env') - local body = get_body() - local service_id = get_service_id() +function router_controller.query(params) - check_schema(pdk.schema.router, body) + router_controller.check_schema(schema.router.query, params) - create_etcd_key(env, service_id) + router_controller.user_authenticate() - if not body.push_env then - body.push_env = {} - for _, env_name in ipairs(env_names) do - body.push_env[env_name] = false - end + if not router_controller.is_owner then + router_controller.router_authenticate(params.router_id, router_controller.uid) end - local data, code, etcd_err = pdk.etcd.create(etcd_key, body) - if data then - pdk.response.exit(code, data) - else - pdk.response.exit(code, etcd_err) + local res, err = db.router.query(params.router_id) + if err then + pdk.response.exit(500, { err_message = err }) end -end -function _M.update(params) - uri_params = params - local env = get_uri_param('env') - local router_id = get_uri_param('router_id') - local body = get_body() - local service_id = get_service_id() - - check_schema(pdk.schema.router, body) - - create_etcd_key(env, service_id, router_id) - - local res, code, err = pdk.etcd.query(etcd_key) - if res and res.value.push_env then - body.push_env = res.value.push_env - else - body.push_env = {} - for _, env_name in ipairs(env_names) do - body.push_env[env_name] = false - end + if #res == 0 then + pdk.response.exit(500, { err_message = "router: " .. params.router_id .. "not exists" }) end - res, code, err = pdk.etcd.update(etcd_key, body) - if res then - pdk.response.exit(code, res) - else - pdk.response.exit(code, { err_message = err }) - end + pdk.response.exit(200, { err_message = "OK", router = res[1] }) end -function _M.delete(params) - uri_params = params - local env = get_uri_param('env') - local router_id = get_uri_param('router_id') - local service_id = get_service_id() +function router_controller.updated(params) - create_etcd_key(env, service_id, router_id) + local body = router_controller.get_body() + body.project_id = params.project_id + body.router_id = params.router_id - local data, code, etcd_err = pdk.etcd.delete(etcd_key) + router_controller.check_schema(schema.router.updated, body) - if env == ENV_MASTER then - for _, env_name in ipairs(env_names) do - create_etcd_key(env_name, service_id, router_id) - pdk.etcd.delete(etcd_key) - end - end + router_controller.user_authenticate() - if data then - pdk.response.exit(code, data) - else - pdk.response.exit(code, { err_message = etcd_err }) + if not router_controller.is_owner then + router_controller.project_authenticate(params.project_id, router_controller.uid) end -end - -function _M.plugin_create(params) - uri_params = params - local env = get_uri_param('env') - local router_id = get_uri_param('router_id') - local body = get_body() - local service_id = get_service_id() - - check_schema(pdk.schema.plugin, body) - - create_etcd_key(env, service_id, router_id) - local res, code, err = pdk.etcd.query(etcd_key) + local _, err = db.router.updated(params.router_id, body) if err then - pdk.response.exit(code, { err_message = err }) - end - - if res.value.plugins then - res.value.plugins[body.key] = body.config or {} - else - local plugins = {} - plugins[body.key] = body.config or {} - res.value.plugins = plugins + pdk.response.exit(500, { err_message = err }) end - res, code, err = pdk.etcd.update(etcd_key, res.value) - if err then - pdk.response.exit(code, { err_message = err }) - end - pdk.response.exit(code, res) + pdk.response.exit(200, { err_message = "OK" }) end -function _M.plugin_delete(params) - uri_params = params - local env = get_uri_param('env') - local router_id = get_uri_param('router_id') - local plugin_key = get_uri_param('plugin_key') - local service_id = get_service_id() +function router_controller.deleted(params) - create_etcd_key(env, service_id, router_id) + router_controller.check_schema(schema.router.deleted, params) - local res, code, err = pdk.etcd.query(etcd_key) - if err then - pdk.response.exit(code, { err_message = err }) - end + router_controller.user_authenticate() - if not res.value.plugins or not res.value.plugins[plugin_key] then - pdk.response.exit(500, - { err_message = pdk.string.format("property \"PLUGIN: %s\" not found", plugin_key) }) + if not router_controller.is_owner then + router_controller.project_authenticate(params.project_id, router_controller.uid) end - res.value.plugins[plugin_key] = nil - - res, code, err = pdk.etcd.update(etcd_key, res.value) + local _, err = db.router.deleted(params.router_id) if err then - pdk.response.exit(code, { err_message = err }) + pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(code, res) + + pdk.response.exit(200, { err_message = "OK" }) end -function _M.env_create(params) - uri_params = params - local env = get_uri_param('env') - local router_id = get_uri_param('router_id') - local service_id = get_service_id() +function router_controller.online(params) - create_etcd_key(ENV_MASTER, service_id, router_id) + router_controller.check_schema(schema.router.online, params) - local router, code, err = pdk.etcd.query(etcd_key) + router_controller.user_authenticate() + if not router_controller.is_owner then + router_controller.router_authenticate(params.router_id, router_controller.uid) + end + + local res, err = db.router.query(params.router_id) if err then - pdk.response.exit(code, { err_message = err }) + pdk.response.exit(500, { err_message = err }) end - router.value.push_env[env] = true + local router = res[1] + if router.response_type == pdk.const.CONTENT_TYPE_JSON then + router.response_success = pdk.json.decode(router.response_success) + router.response_failure = pdk.json.decode(router.response_failure) + end - router, code, err = pdk.etcd.update(etcd_key, router.value) + res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id) if err then - pdk.response.exit(code, { err_message = err }) + pdk.response.exit(500, { err_message = err }) end - create_etcd_key(env, service_id, router_id) - - router.value.push_env = nil + local plugins = {} + for i = 1, #res do + local plugin = res[i] + plugins[plugin.name] = plugin + end + router.plugins = plugins - router, code, err = pdk.etcd.update(etcd_key, router.value) + res, err = db.router.online(params.router_id, params.env, router) if err then - pdk.response.exit(code, { err_message = err }) + pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(code, router) + + pdk.response.exit(200, { err_message = "OK" }) end -function _M.env_delete(params) - uri_params = params - local env = get_uri_param('env') - local router_id = get_uri_param('router_id') - local service_id = get_service_id() +function router_controller.offline(params) - create_etcd_key(ENV_MASTER, service_id, router_id) + router_controller.check_schema(schema.router.offline, params) - local res, code, err = pdk.etcd.query(etcd_key) - if err then - pdk.response.exit(code, { err_message = err }) + router_controller.user_authenticate() + if not router_controller.is_owner then + router_controller.router_authenticate(params.router_id, router_controller.uid) end - res.value.push_env[env] = false - - res, code, err = pdk.etcd.update(etcd_key, res.value) + local _, err = db.router.offline(params.router_id, params.env) if err then - pdk.response.exit(code, { err_message = err }) + pdk.response.exit(500, { err_message = err }) end - create_etcd_key(env, service_id, router_id) - - res, code, err = pdk.etcd.delete(etcd_key) - - if res then - pdk.response.exit(code, res) - else - pdk.response.exit(code, { err_message = err }) - end + pdk.response.exit(200, { err_message = "OK" }) end -return _M +return router_controller -- Gitee From 08aaa00ae77eaadf72174cbde530deef04ddff77 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:15:31 +0800 Subject: [PATCH 012/165] feature: add admin.user controller. --- apioak/admin/service.lua | 162 -------------------------- apioak/admin/user.lua | 237 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+), 162 deletions(-) delete mode 100644 apioak/admin/service.lua create mode 100644 apioak/admin/user.lua diff --git a/apioak/admin/service.lua b/apioak/admin/service.lua deleted file mode 100644 index e1c31d1..0000000 --- a/apioak/admin/service.lua +++ /dev/null @@ -1,162 +0,0 @@ -local pdk = require("apioak.pdk") - -local etcd_key -local uri_params - -local function create_etcd_key(service_id) - etcd_key = pdk.admin.get_service_etcd_key(service_id) -end - -local function get_uri_param(key) - local val = uri_params[key] or nil - if not val then - pdk.response.exit(500, - { err_message = pdk.string.format("property \"URI: %s\" is required", key) }) - end - return val -end - -local function get_body() - local body, err = pdk.request.body() - if err then - pdk.response.exit(500, { err_message = err }) - end - return body -end - -local function check_schema(schema, body) - local _, err = pdk.schema.check(schema, body) - if err then - pdk.response.exit(500, { err_message = err }) - end -end - -local _M = {} - -function _M.list() - create_etcd_key() - - local data, code, etcd_err = pdk.etcd.query(etcd_key) - if data then - pdk.response.exit(code, data) - else - pdk.response.exit(code, etcd_err) - end -end - -function _M.query(params) - uri_params = params - local service_id = get_uri_param('service_id') - - create_etcd_key(service_id) - - local data, code, etcd_err = pdk.etcd.query(etcd_key) - if data then - pdk.response.exit(code, data) - else - pdk.response.exit(code, etcd_err) - end -end - -function _M.create() - local body = get_body() - - check_schema(pdk.schema.service, body) - - create_etcd_key() - - local data, code, etcd_err = pdk.etcd.create(etcd_key, body) - if data then - pdk.response.exit(code, data) - else - pdk.response.exit(code, etcd_err) - end -end - -function _M.update(params) - uri_params = params - local service_id = get_uri_param('service_id') - local body = get_body() - - check_schema(pdk.schema.service, body) - - create_etcd_key(service_id) - - local data, code, etcd_err = pdk.etcd.update(etcd_key, body) - if data then - pdk.response.exit(code, data) - else - pdk.response.exit(code, etcd_err) - end -end - -function _M.delete(params) - uri_params = params - local service_id = get_uri_param('service_id') - - create_etcd_key(service_id) - - local data, code, etcd_err = pdk.etcd.delete(etcd_key) - if data then - pdk.response.exit(code, data) - else - pdk.response.exit(code, etcd_err) - end -end - -function _M.plugin_create(params) - uri_params = params - local service_id = get_uri_param('service_id') - local body = get_body() - - check_schema(pdk.schema.plugin, body) - - create_etcd_key(service_id) - - local res, code, err = pdk.etcd.query(etcd_key) - if err then - pdk.response.exit(code, { err_message = err }) - end - - if res.value.plugins then - res.value.plugins[body.key] = body.config or {} - else - local plugins = {} - plugins[body.key] = body.config or {} - res.value.plugins = plugins - end - - res, code, err = pdk.etcd.update(etcd_key, res.value) - if err then - pdk.response.exit(code, { err_message = err }) - end - pdk.response.exit(code, res) -end - -function _M.plugin_delete(params) - uri_params = params - local service_id = get_uri_param('service_id') - local plugin_key = get_uri_param('plugin_key') - - create_etcd_key(service_id) - - local res, code, err = pdk.etcd.query(etcd_key) - if err then - pdk.response.exit(code, { err_message = err }) - end - - if not res.value.plugins or not res.value.plugins[plugin_key] then - pdk.response.exit(500, - { err_message = pdk.string.format("property \"PLUGIN: %s\" not found", plugin_key) }) - end - - res.value.plugins[plugin_key] = nil - - res, code, err = pdk.etcd.update(etcd_key, res.value) - if err then - pdk.response.exit(code, { err_message = err }) - end - pdk.response.exit(code, res) -end - -return _M diff --git a/apioak/admin/user.lua b/apioak/admin/user.lua new file mode 100644 index 0000000..d9408ca --- /dev/null +++ b/apioak/admin/user.lua @@ -0,0 +1,237 @@ +local db = require("apioak.db") +local pdk = require("apioak.pdk") +local schema = require("apioak.schema") +local controller = require("apioak.admin.controller") + +local user_controller = controller.new("user") + +function user_controller.register() + + local body = user_controller.get_body() + + user_controller.check_schema(schema.user.register, body) + + local res, err = db.user.all() + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + body.is_owner = 1 + body.is_enable = 1 + end + + if body.password ~= body.valid_password then + pdk.response.exit(401, { err_message = "inconsistent password entry" }) + end + + res, err = db.user.create(body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if body.is_owner == 1 then + local user_id = res.insert_id + res, err = db.group.create({ + name = "Default Group", + description = "default project group", + user_id = user_id + }) + if err then + pdk.response.exit(500, { err_message = err }) + end + + local group_id = res.insert_id + res, err = db.role.create(group_id, user_id, true) + if err then + pdk.response.exit(500, { err_message = err }) + end + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function user_controller.login() + + local body = user_controller.get_body() + + user_controller.check_schema(schema.user.login, body) + + local res, err = db.user.query_by_email(body.email) + if err then + pdk.response.exit(401, { err_message = err }) + end + + if #res == 0 then + pdk.response.exit(401, { err_message = pdk.string.format( + "request login user \"%s\" not exists", body.email) }) + end + + local user = res[1] + if pdk.string.md5(body.password) ~= user.password then + pdk.response.exit(401, { err_message = pdk.string.format( + "request login user \"%s\" password error", body.email) }) + end + + if user.is_enable == 0 then + pdk.response.exit(401, { err_message = + "user account is not enabled, please contact the administrator" }) + end + + res, err = db.token.query_by_uid(user.id) + if err then + pdk.response.exit(401, { err_message = err }) + end + + if #res == 0 then + res, err = db.token.create_by_uid(user.id) + else + res, err = db.token.update_by_uid(user.id) + end + + if err then + pdk.response.exit(401, { err_message = err }) + end + user.token = res.token + + if user.is_owner then + res, err = db.group.all(true) + else + res, err = db.group.query_by_uid(user.id) + end + + if err then + pdk.response.exit(401, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" , user = { + id = user.id, + name = user.name, + token = user.token, + is_owner = user.is_owner, + groups = res + }}) +end + +function user_controller.logout() + + user_controller.user_authenticate() + + local _, err = db.token.expire_by_token(user_controller.token) + + if err then + pdk.response.exit(500, { err_message = err }) + end + pdk.response.exit(200, { err_message = "OK" }) +end + +function user_controller.list() + + user_controller.user_authenticate() + + local res, err = db.user.all() + + if err then + pdk.response.exit(500, { err_message = err }) + end + pdk.response.exit(200, { err_message = "OK", users = res }) +end + +function user_controller.created() + + user_controller.user_authenticate() + + local body = user_controller.get_body() + + user_controller.check_schema(schema.user.created, body) + + if body.password ~= body.valid_password then + pdk.response.exit(401, { err_message = "inconsistent password entry" }) + end + + if not user_controller.is_owner then + pdk.response.exit(401, { err_message = "no permission to create user" }) + end + + local _, err = db.user.create(body) + + if err then + pdk.response.exit(500, { err_message = err }) + end + pdk.response.exit(200, { err_message = "OK" }) +end + + +function user_controller.updated_password(params) + + user_controller.user_authenticate() + + local body = user_controller.get_body() + body.user_id = params.user_id + + user_controller.check_schema(schema.user.updated_password, body) + + if not user_controller.is_owner and params.user_id ~= user_controller.uid then + pdk.response.exit(401, { err_message = "no permission to create user" }) + end + + if body.password ~= body.valid_password then + pdk.response.exit(401, { err_message = "inconsistent password entry" }) + end + + local _, err = db.user.update_password(params.user_id, body.password) + + if err then + pdk.response.exit(500, { err_message = err }) + end + pdk.response.exit(200, { err_message = "OK" }) +end + +function user_controller.updated_status(params) + + user_controller.user_authenticate() + + local body = user_controller.get_body() + body.user_id = params.user_id + + user_controller.check_schema(schema.user.updated_status, body) + + if not user_controller.is_owner then + pdk.response.exit(401, { err_message = "no permission to create user" }) + end + + local _, err = db.user.update_status(params.user_id, body.is_enable) + + if err then + pdk.response.exit(500, { err_message = err }) + end + pdk.response.exit(200, { err_message = "OK" }) +end + +function user_controller.deleted(params) + + user_controller.user_authenticate() + + if not user_controller.is_owner then + pdk.response.exit(401, { err_message = "no permission to delete user" }) + end + + if params.user_id == user_controller.uid then + pdk.response.exit(401, { err_message = "no permission to delete user" }) + end + + user_controller.check_schema(schema.user.updated, params) + + local res, err = db.role.delete_by_uid(params.user_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + res, err = db.user.create(params.user_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + pdk.response.exit(200, { err_message = "OK" }) +end + +return user_controller -- Gitee From 4bf4b1de793fb7336058765f511240ed39046ca4 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:25:19 +0800 Subject: [PATCH 013/165] feature: add db.group model. --- apioak/db/group.lua | 89 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 apioak/db/group.lua diff --git a/apioak/db/group.lua b/apioak/db/group.lua new file mode 100644 index 0000000..82f3232 --- /dev/null +++ b/apioak/db/group.lua @@ -0,0 +1,89 @@ +local pdk = require("apioak.pdk") +local role = require("apioak.db.role") + +local table_name = "oak_groups" + +local _M = {} + +function _M.all(is_owner) + local sql + if is_owner then + sql = pdk.string.format("SELECT *, 1 AS is_admin FROM %s", table_name) + else + sql = pdk.string.format("SELECT * FROM %s", table_name) + end + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.create(params) + local sql = pdk.string.format("INSERT INTO %s (name, description) VALUES ('%s', '%s')", + table_name, params.name, params.description) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.update(group_id, params) + local sql = pdk.string.format("UPDATE %s SET name = '%s', description = '%s' WHERE id = %s", + table_name, params.name, params.description, group_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.delete(group_id) + local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", table_name, group_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.query(group_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE id = %s", table_name, group_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.query_by_name(name) + local sql = pdk.string.format("SELECT * FROM %s WHERE name = '%s'", + table_name, name) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +function _M.query_by_uid(user_id) + local sql = pdk.string.format( + "SELECT groups.*, roles.is_admin FROM %s as roles, %s as groups WHERE roles.group_id = groups.id AND roles.user_id = %s", + role.table_name, table_name, user_id) + + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +return _M -- Gitee From c83d5bc7b7c4a741f7797a3d8b383b25f8ffceb2 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:25:58 +0800 Subject: [PATCH 014/165] feature: add db.plugin model. --- apioak/db/plugin.lua | 73 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 apioak/db/plugin.lua diff --git a/apioak/db/plugin.lua b/apioak/db/plugin.lua new file mode 100644 index 0000000..be4abfc --- /dev/null +++ b/apioak/db/plugin.lua @@ -0,0 +1,73 @@ +local pdk = require("apioak.pdk") + +local table_name = "oak_plugins" + +local _M = {} + +_M.table_name = table_name + +_M.RESOURCES_TYPE_ROUTER = "ROUTER" + +_M.RESOURCES_TYPE_PROJECT = "PROJECT" + +function _M.query_by_res(res_type, res_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE res_type = '%s' AND res_id = %s", + table_name, res_type, res_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + + for i = 1, #res do + res[i].config = pdk.json.decode(res[i].config) + end + + return res, nil +end + +function _M.delete_by_res(res_type, res_id) + local sql = pdk.string.format("DELETE FROM %s WHERE res_type = '%s' AND res_id = %s", + table_name, res_type, res_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.create_by_res(res_type, res_id, params) + local sql = pdk.string.format("INSERT INTO %s (name, type, description, config, res_type, res_id) VALUES ('%s', '%s', '%s', '%s', '%s', '%s')", + table_name, params.name, params.type, params.description, pdk.json.encode(params.config), res_type, res_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.delete(plugin_id) + local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", + table_name, plugin_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.update(plugin_id, params) + local sql = pdk.string.format("UPDATE %s SET name = '%s', type = '%s', description = '%s', config = '%s' WHERE id = %s", + table_name, params.name, params.type, params.description, pdk.json.encode(params.config), plugin_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +return _M -- Gitee From 6adda262664f21a20b0add9992672903d029118d Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:27:10 +0800 Subject: [PATCH 015/165] feature: add db.project model. --- apioak/db/project.lua | 63 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 apioak/db/project.lua diff --git a/apioak/db/project.lua b/apioak/db/project.lua new file mode 100644 index 0000000..4d8bc09 --- /dev/null +++ b/apioak/db/project.lua @@ -0,0 +1,63 @@ +local pdk = require("apioak.pdk") + +local table_name = "oak_projects" + +local _M = {} + +_M.table_name = table_name + +function _M.query_by_gid(group_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE group_id = %s", table_name, group_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.create(params) + local sql = pdk.string.format("INSERT INTO %s (name, description, path, group_id, user_id) VALUES ('%s', '%s', '%s', '%s', '%s')", + table_name, params.name, params.description, params.path, params.group_id, params.user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.update(project_id, params) + local sql = pdk.string.format("UPDATE %s SET name = '%s', description = '%s', path = '%s' WHERE id = %s", + table_name, params.name, params.description, params.path, project_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.query(project_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE id = %s", + table_name, project_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.delete(project_id) + local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", + table_name, project_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +return _M -- Gitee From b7aca0133c7a88fcbb29af6fd0c7d05bdd3a186e Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:27:32 +0800 Subject: [PATCH 016/165] feature: add db.role model. --- apioak/db/role.lua | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 apioak/db/role.lua diff --git a/apioak/db/role.lua b/apioak/db/role.lua new file mode 100644 index 0000000..b06ed1b --- /dev/null +++ b/apioak/db/role.lua @@ -0,0 +1,93 @@ +local pdk = require("apioak.pdk") + +local table_name = "oak_roles" + +local _M = {} + +_M.table_name = table_name + +function _M.create(group_id, user_id, is_admin) + local sql = pdk.string.format("INSERT INTO %s (group_id, user_id, is_admin) VALUES ('%s', '%s', '%s')", + table_name, group_id, user_id, is_admin) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +function _M.query(group_id, user_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE group_id = %s AND user_id = %s", + table_name, group_id, user_id) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +function _M.delete(group_id, user_id) + local sql = pdk.string.format("DELETE FROM %s WHERE group_id = %s AND user_id = %s", + table_name, group_id, user_id) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +function _M.update(group_id, user_id, is_admin) + local sql = pdk.string.format("UPDATE %s SET is_admin = %s WHERE group_id = %s AND user_id = %s", + table_name, is_admin, group_id, user_id) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +function _M.query_by_uid(user_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE user_id = %s", table_name, user_id) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +function _M.query_by_gid(group_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE group_id = %s", table_name, group_id) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +function _M.delete_by_gid(group_id) + local sql = pdk.string.format("DELETE FROM %s WHERE group_id = %s", table_name, group_id) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +function _M.delete_by_uid(user_id) + local sql = pdk.string.format("DELETE FROM %s WHERE user_id = %s", table_name, user_id) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end + +return _M -- Gitee From 72a5ba9f0882a5f8b2388547712b31696d9c1b49 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:27:50 +0800 Subject: [PATCH 017/165] feature: add db.router model. --- apioak/db/router.lua | 132 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 apioak/db/router.lua diff --git a/apioak/db/router.lua b/apioak/db/router.lua new file mode 100644 index 0000000..41b29b9 --- /dev/null +++ b/apioak/db/router.lua @@ -0,0 +1,132 @@ +local pdk = require("apioak.pdk") + +local PROD_TABLE_FIELD = "PROD" +local BETA_TABLE_FIELD = "BETA" +local TEST_TABLE_FIELD = "TEST" + +local table_env_fields = {} +table_env_fields[PROD_TABLE_FIELD] = "env_prod_config" +table_env_fields[BETA_TABLE_FIELD] = "env_beta_config" +table_env_fields[TEST_TABLE_FIELD] = "env_test_config" + +local table_name = "oak_routers" + +local _M = {} + +_M.table_name = table_name + +_M.PROD_TABLE_FIELD = PROD_TABLE_FIELD +_M.BETA_TABLE_FIELD = BETA_TABLE_FIELD +_M.TEST_TABLE_FIELD = TEST_TABLE_FIELD + +function _M.query_by_pid(project_id) + local sql = "SELECT id, name, enable_cors, description, request_path, request_method FROM %s WHERE project_id = %s" + sql = pdk.string.format(sql, table_name, project_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + + return res, nil +end + +function _M.query(router_id) + local sql = "SELECT id, name, enable_cors, description, request_path, request_method, request_params, " .. + "backend_path, backend_method, backend_timeout, backend_params, constant_params, response_type, " .. + "response_success, response_failure, response_codes, response_schema, " .. + "IF(env_prod_config = NULL, 0, 1) AS env_prod_config, " .. + "IF(env_beta_config = NULL, 0, 1) AS env_beta_config, " .. + "IF(env_test_config = NULL, 0, 1) AS env_test_config, project_id FROM %s WHERE id = %s"; + sql = pdk.string.format(sql, table_name, router_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + + for i = 1, #res do + res[i].request_params = pdk.json.decode(res[i].request_params) + res[i].backend_params = pdk.json.decode(res[i].backend_params) + res[i].constant_params = pdk.json.decode(res[i].constant_params) + res[i].response_codes = pdk.json.decode(res[i].response_codes) + res[i].response_schema = pdk.json.decode(res[i].response_schema) + end + + return res, nil +end + +function _M.created(params) + local sql = "INSERT INTO %s (name, enable_cors, description, request_path, request_method, request_params, " .. + "backend_path, backend_method, backend_timeout, backend_params, constant_params, response_type, " .. + "response_success, response_failure, response_codes, response_schema, project_id, user_id) " .. + "VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', " .. + "'%s', '%s', '%s')" + sql = pdk.string.format(sql, table_name, params.name, params.enable_cors, params.description, params.request_path, + params.request_method, pdk.json.encode(params.request_params), params.backend_path, params.backend_method, + params.backend_timeout, pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), + params.response_type, params.response_success, params.response_failure, pdk.json.encode(params.response_codes), + pdk.json.encode(params.response_schema), params.project_id, params.user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.updated(router_id, params) + local sql = "UPDATE %s SET name = '%s', enable_cors = '%s', description = '%s', request_path = '%s', " .. + "request_method = '%s', request_params = '%s', backend_path = '%s', backend_method = '%s', " .. + "backend_timeout = '%s', backend_params = '%s', constant_params = '%s', response_type = '%s', " .. + "response_success = '%s', response_failure = '%s', response_codes = '%s', response_schema = '%s' ".. + "WHERE id = '%s'" + sql = pdk.string.format(sql, table_name, params.name, params.enable_cors, params.description, params.request_path, + params.request_method, pdk.json.encode(params.request_params), params.backend_path, params.backend_method, + params.backend_timeout, pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), + params.response_type, params.response_success, params.response_failure, pdk.json.encode(params.response_codes), + pdk.json.encode(params.response_schema), router_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.deleted(router_id) + local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", table_name, router_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.online(router_id, env, router_info) + local table_field = table_env_fields[env] + router_info = pdk.json.encode(router_info) + local sql = pdk.string.format("UPDATE %s SET %s = '%s' WHERE id = %s", + table_name, table_field, router_info, router_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.offline(router_id, env) + local table_field = table_env_fields[env] + local sql = pdk.string.format("UPDATE %s SET %s = NULL WHERE id = %s", + table_name, table_field, router_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +return _M -- Gitee From 343dc168b5580865e9e9871bca0f585740ba2721 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:28:27 +0800 Subject: [PATCH 018/165] feature: add db.token model. --- apioak/db/token.lua | 98 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 apioak/db/token.lua diff --git a/apioak/db/token.lua b/apioak/db/token.lua new file mode 100644 index 0000000..ef2efb8 --- /dev/null +++ b/apioak/db/token.lua @@ -0,0 +1,98 @@ +local pdk = require("apioak.pdk") + +local _M = {} + +local table_name = "oak_tokens" + +function _M.query_by_uid(user_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE user_id = %s", table_name, user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.query_by_token(token) + local sql = pdk.string.format("SELECT * FROM %s WHERE token = '%s'", table_name, token) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.create_by_uid(user_id) + local token = pdk.string.md5(pdk.time.time()) + local expired = pdk.time.date("%Y-%m-%d %H:%M:%S", pdk.time.time() + 86400) + + local sql = pdk.string.format("INSERT INTO %s (token, user_id, expired_at) VALUES ('%s', '%s', '%s')", + table_name, token, user_id, expired) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + res.token = token + res.expired = expired + return res, nil +end + +function _M.update_by_uid(user_id) + local token = pdk.string.md5(pdk.time.time()) + local expired = pdk.time.date("%Y-%m-%d %H:%M:%S", pdk.time.time() + 86400) + + local sql = pdk.string.format("UPDATE %s SET token = '%s', expired_at = '%s' WHERE user_id = %s", + table_name, token, expired, user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + res.token = token + res.expired = expired + return res, nil +end + +function _M.continue_by_uid(user_id) + local expired = pdk.time.date("%Y-%m-%d %H:%M:%S", pdk.time.time() + 86400) + + local sql = pdk.string.format("UPDATE %s SET expired_at = '%s' WHERE user_id = %s", + table_name, expired, user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + res.expired = expired + return res, nil +end + +function _M.continue_by_token(token) + local expired = pdk.time.date("%Y-%m-%d %H:%M:%S", pdk.time.time() + 86400) + + local sql = pdk.string.format("UPDATE %s SET expired_at = '%s' WHERE token = '%s'", + table_name, expired, token) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + res.expired = expired + return res, nil +end + +function _M.expire_by_token(token) + local sql = pdk.string.format("UPDATE %s SET expired_at = '%s' WHERE token = '%s'", table_name, + pdk.string.null, token) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +return _M -- Gitee From 9aad167aae6356ddb25dffedd956820525729906 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:28:46 +0800 Subject: [PATCH 019/165] feature: add db.upstream model. --- apioak/db/upstream.lua | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 apioak/db/upstream.lua diff --git a/apioak/db/upstream.lua b/apioak/db/upstream.lua new file mode 100644 index 0000000..3285077 --- /dev/null +++ b/apioak/db/upstream.lua @@ -0,0 +1,67 @@ +local pdk = require("apioak.pdk") + +local table_name = "oak_upstreams" + +local _M = {} + +_M.table_name = table_name + +function _M.create(params) + local sql = pdk.string.format("INSERT INTO %s (env, host, type, project_id, nodes) VALUES ('%s', '%s', '%s', '%s', '%s')", + table_name, params.env, params.host, params.type, params.project_id, pdk.json.encode(params.nodes)) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.update(upstream_id, params) + local sql = pdk.string.format("UPDATE %s SET env = '%s', host = '%s', type = '%s', project_id = '%s', nodes = '%s' WHERE id = %s", + table_name, params.env, params.host, params.type, params.project_id, pdk.json.encode(params.nodes), upstream_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.query(upstream_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE id = %s", table_name, upstream_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.query_by_pid(project_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE project_id = %s", table_name, project_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + + for i = 1, #res do + res[i].nodes = pdk.json.decode(res[i].nodes) + end + + return res, nil +end + +function _M.delete_by_pid(project_id) + local sql = pdk.string.format("DELETE FROM %s WHERE project_id = %s", table_name, project_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + + return res, nil +end + +return _M -- Gitee From 44f8048d43584e043d4141f13c31ffbdb755b402 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:29:00 +0800 Subject: [PATCH 020/165] feature: add db.user model. --- apioak/db/user.lua | 106 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 apioak/db/user.lua diff --git a/apioak/db/user.lua b/apioak/db/user.lua new file mode 100644 index 0000000..c0811f4 --- /dev/null +++ b/apioak/db/user.lua @@ -0,0 +1,106 @@ +local pdk = require("apioak.pdk") +local role = require("apioak.db.role") + +local _M = {} + +local table_name = "oak_users" + +_M.table_name = table_name + +function _M.all() + local sql = pdk.string.format("SELECT id, name, email, is_enable FROM %s", table_name) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + return res, nil +end + +function _M.create(params) + local sql = pdk.string.format( + "INSERT INTO %s (name, password, email, is_owner, is_enable) VALUES ('%s', '%s', '%s', '%s', '%s')", + table_name, params.name, pdk.string.md5(params.password), params.email, + params.is_owner or 0, params.is_enable or 0) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.update(user_id, params) + local sql = pdk.string.format("UPDATE %s SET name = '%s', password = '%s', email = '%s', is_enable = %s WHERE id = %s", + table_name, params.name, pdk.string.md5(params.password), params.email, params.is_enable, user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.delete(user_id) + local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", + table_name, user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.update_password(user_id, password) + local sql = pdk.string.format("UPDATE %s SET password = '%s' WHERE id = %s", + table_name, pdk.string.md5(password), user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.update_status(user_id, status) + local sql = pdk.string.format("UPDATE %s SET is_enable = %s WHERE id = %s", + table_name, status, user_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + +function _M.query_by_email(email) + local sql = pdk.string.format("SELECT * FROM %s WHERE email = '%s'", table_name, email) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + return res, err +end + +function _M.query_by_id(uid) + local sql = pdk.string.format("SELECT * FROM %s WHERE id = %s", table_name, uid) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + return res, err +end + +function _M.query_by_gid(gid) + local sql = pdk.string.format( + "SELECT users.id, users.name, users.email, roles.is_admin FROM %s AS roles LEFT JOIN %s AS users ON roles.user_id = users.id WHERE roles.group_id = %s", + role.table_name, table_name, gid) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, err +end + +return _M -- Gitee From 7c1440406a83415de5d8fc751bbc81d0c0ba5867 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:29:35 +0800 Subject: [PATCH 021/165] change: apioak.admin module. --- apioak/admin.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apioak/admin.lua b/apioak/admin.lua index 26c4411..5e6eaa5 100644 --- a/apioak/admin.lua +++ b/apioak/admin.lua @@ -1,5 +1,7 @@ return { + user = require("apioak.admin.user"), + group = require("apioak.admin.group"), router = require("apioak.admin.router"), + project = require("apioak.admin.project"), plugin = require("apioak.admin.plugin"), - service = require("apioak.admin.service"), } -- Gitee From 2443643b079b85dc33bf22e74f56a9a7117719c3 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:35:46 +0800 Subject: [PATCH 022/165] feature: add schema.group verification module. --- apioak/schema/group.lua | 213 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 apioak/schema/group.lua diff --git a/apioak/schema/group.lua b/apioak/schema/group.lua new file mode 100644 index 0000000..1838c43 --- /dev/null +++ b/apioak/schema/group.lua @@ -0,0 +1,213 @@ +local _M = {} + +_M.created = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 5, + maxLength = 20, + }, + description = { + type = 'string', + minLength = 5, + maxLength = 50, + } + }, + required = { "name", "description" } +} + +_M.updated = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = 'string', + minLength = 5, + maxLength = 20, + }, + description = { + type = 'string', + minLength = 5, + maxLength = 50, + } + }, + required = { "group_id", "name", "description" } +} + +_M.deleted = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.query = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.user_list = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.user_created = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + is_admin = { + type = "number", + minimum = 0 + } + }, + required = { "group_id", "user_id", "is_admin" } +} + +_M.user_deleted = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.user_updated = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + is_admin = { + type = "number", + minimum = 0 + } + }, + required = { "group_id", "user_id", "is_admin" } +} + +return _M -- Gitee From b781fd48313e77c76cc500e3feefeec7eb5bbaa9 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:36:15 +0800 Subject: [PATCH 023/165] feature: add schema.plugin verification module. --- apioak/schema/plugin.lua | 284 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 apioak/schema/plugin.lua diff --git a/apioak/schema/plugin.lua b/apioak/schema/plugin.lua new file mode 100644 index 0000000..59b49a6 --- /dev/null +++ b/apioak/schema/plugin.lua @@ -0,0 +1,284 @@ +local _M = {} + +_M.project_list = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.project_created = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + type = { + type = "string", + minLength = 5, + maxLength = 20, + }, + description = { + type = "string", + minLength = 5, + maxLength = 100, + }, + config = { + type = "object" + } + }, + required = { "project_id", "name", "type", "config", "description" } +} + +_M.project_updated = { + type = "object", + properties = { + plugin_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + type = { + type = "string", + minLength = 5, + maxLength = 20, + }, + description = { + type = "string", + minLength = 5, + maxLength = 100, + }, + config = { + type = "object" + } + }, + required = { "plugin_id", "name", "type", "config", "description" } +} + +_M.project_deleted = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + plugin_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + } +} + +_M.router_list = { + type = "object", + properties = { + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.router_created = { + type = "object", + properties = { + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + type = { + type = "string", + minLength = 5, + maxLength = 20, + }, + description = { + type = "string", + minLength = 5, + maxLength = 100, + }, + config = { + type = "object" + } + }, + required = { "router_id", "name", "type", "config", "description" } +} + +_M.router_updated = { + type = "object", + properties = { + plugin_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + type = { + type = "string", + minLength = 5, + maxLength = 20, + }, + description = { + type = "string", + minLength = 5, + maxLength = 100, + }, + config = { + type = "object" + } + }, + required = { "plugin_id", "name", "type", "config", "description" } +} + +_M.router_deleted = { + type = "object", + properties = { + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + plugin_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + } +} + + +return _M -- Gitee From 5f28a70e397f485c33450f9783988962ddf95717 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:36:34 +0800 Subject: [PATCH 024/165] feature: add schema.project verification module. --- apioak/schema/project.lua | 408 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 apioak/schema/project.lua diff --git a/apioak/schema/project.lua b/apioak/schema/project.lua new file mode 100644 index 0000000..5f18a18 --- /dev/null +++ b/apioak/schema/project.lua @@ -0,0 +1,408 @@ + +local _M = {} + +_M.list = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.deleted = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.query = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.updated = { + type = "object", + properties = { + id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + path = { + type = 'string', + minLength = 2, + maxLength = 20 + }, + description = { + type = 'string', + minLength = 5, + maxLength = 100 + }, + upstreams = { + type = "array", + minItems = 3, + maxItems = 3, + uniqueItems = true, + items = { + type = "object", + properties = { + id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + host = { + type = "string", + pattern = "^\\*?[0-9a-zA-Z-.]+$" + }, + type = { + type = "string", + enum = { "chash", "roundrobin" } + }, + env = { + type = "string", + enum = { "prod", "beta", "test" } + }, + nodes = { + type = "array", + minItems = 1, + uniqueItems = true, + items = { + type = "object", + properties = { + ip = { + type = "string", + pattern = "^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$" + }, + port = { + type = "number", + minimum = 1, + maximum = 65535, + }, + weight = { + type = "number", + minimum = 0, + maximum = 100, + }, + }, + required = { "ip", "port", "weight" } + } + } + }, + required = { "id", "host", "type", "env", "nodes" } + }, + } + }, + required = { "id", "name", "path", "upstreams", "description" } +} + +_M.created = { + type = "object", + properties = { + group_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + path = { + type = 'string', + minLength = 2, + maxLength = 20 + }, + description = { + type = 'string', + minLength = 5, + maxLength = 100 + }, + upstreams = { + type = "array", + minItems = 3, + maxItems = 3, + uniqueItems = true, + items = { + type = "object", + properties = { + host = { + type = "string", + pattern = "^\\*?[0-9a-zA-Z-.]+$" + }, + type = { + type = "string", + enum = { "chash", "roundrobin" } + }, + env = { + type = "string", + enum = { "prod", "beta", "test" } + }, + nodes = { + type = "array", + minItems = 1, + uniqueItems = true, + items = { + type = "object", + properties = { + ip = { + type = "string", + pattern = "^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$" + }, + port = { + type = "number", + minimum = 1, + maximum = 65535, + }, + weight = { + type = "number", + minimum = 0, + maximum = 100, + }, + }, + required = { "ip", "port", "weight" } + } + } + }, + required = { "host", "type", "env", "nodes" } + }, + } + }, + required = { "group_id", "name", "path", "upstreams", "description" } +} + +_M.plugin_list = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.plugin_created = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + type = { + type = "string", + minLength = 5, + maxLength = 20, + }, + description = { + type = "string", + minLength = 5, + maxLength = 100, + }, + config = { + type = "object" + } + }, + required = { "project_id", "name", "type", "config", "description" } +} + +_M.plugin_updated = { + type = "object", + properties = { + plugin_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + type = { + type = "string", + minLength = 5, + maxLength = 20, + }, + description = { + type = "string", + minLength = 5, + maxLength = 100, + }, + config = { + type = "object" + } + }, + required = { "plugin_id", "name", "type", "config", "description" } +} + +_M.plugin_deleted = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + plugin_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + } +} + +return _M -- Gitee From 1bb9231b091307f3eaab03c40543de288828324b Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:36:47 +0800 Subject: [PATCH 025/165] feature: add schema.router verification module. --- apioak/schema/router.lua | 545 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 545 insertions(+) create mode 100644 apioak/schema/router.lua diff --git a/apioak/schema/router.lua b/apioak/schema/router.lua new file mode 100644 index 0000000..a0a550f --- /dev/null +++ b/apioak/schema/router.lua @@ -0,0 +1,545 @@ +local _M = {} + +_M.list = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.created = { + type = "object", + properties = { + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + enable_cors = { + type = "number", + enum = { 0, 1 } + }, + description = { + type = 'string', + minLength = 5, + maxLength = 100 + }, + request_path = { + type = 'string', + minLength = 2, + maxLength = 50 + }, + request_method = { + type = "string", + enum = { "GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE", "TRACE", "CONNECT" } + }, + request_params = { + type = "array", + uniqueItems = true, + items = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + position = { + type = "string", + enum = { "QUERY", "HEADER", "PATH" } + }, + type = { + type = "string", + enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } + }, + default_value = { + type = "string" + }, + required = { + type = "number", + enum = { 0, 1 } + }, + description = { + type = "string" + } + }, + required = { "name", "position", "type", "required" } + } + }, + backend_path = { + type = 'string', + minLength = 2, + maxLength = 50 + }, + backend_method = { + type = "string", + enum = { "GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE", "TRACE", "CONNECT" } + }, + backend_timeout = { + type = "number", + minimum = 0 + }, + backend_params = { + type = "array", + uniqueItems = true, + items = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + position = { + type = "string", + enum = { "QUERY", "HEADER", "PATH" } + }, + request_param_name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + request_param_position = { + type = "string", + enum = { "QUERY", "HEADER", "PATH" } + }, + request_param_type = { + type = "string", + enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } + }, + request_param_description = { + type = "string" + } + }, + required = { "name", "position", "request_param_name", "request_param_position", "request_param_type", "request_param_description" } + } + }, + constant_params = { + type = "array", + uniqueItems = true, + items = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + position = { + type = "string", + enum = { "QUERY", "HEADER", "PATH" } + }, + type = { + type = "string", + enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } + }, + value = { + type = "string" + }, + }, + required = { "name", "position", "type", "value" } + } + }, + response_type = { + type = "string", + enum = { + "text/html", + "text/xml", + "application/json", + } + }, + response_success = { + type = 'string', + minLength = 0 + }, + response_failure = { + type = 'string', + minLength = 0 + }, + response_codes = { + type = "array", + uniqueItems = true, + items = { + type = "object", + properties = { + code = { + type = "number", + minimum = 200, + maximum = 599 + }, + message = { + type = "string", + minLength = 1, + maxLength = 20 + }, + description = { + type = "string", + minLength = 0, + maxLength = 50 + } + }, + required = { "code", "message" } + } + }, + response_schema = { + type = "array", + }, + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + }, + required = { "name", "enable_cors", "description", "request_path", "request_method", "request_params", "backend_path", + "backend_method", "backend_timeout", "backend_params", "constant_params", "response_type", + "response_success", "response_failure", "response_codes", "response_schema", "project_id" } +} + +_M.updated = { + type = "object", + properties = { + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + enable_cors = { + type = "number", + enum = { 0, 1 } + }, + description = { + type = 'string', + minLength = 5, + maxLength = 100 + }, + request_path = { + type = 'string', + minLength = 2, + maxLength = 50 + }, + request_method = { + type = "string", + enum = { "GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE", "TRACE", "CONNECT" } + }, + request_params = { + type = "array", + uniqueItems = true, + items = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + position = { + type = "string", + enum = { "QUERY", "HEADER", "PATH" } + }, + type = { + type = "string", + enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } + }, + default_value = { + type = "string" + }, + required = { + type = "number", + enum = { 0, 1 } + }, + description = { + type = "string" + } + }, + required = { "name", "position", "type", "required" } + } + }, + backend_path = { + type = 'string', + minLength = 2, + maxLength = 50 + }, + backend_method = { + type = "string", + enum = { "GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE", "TRACE", "CONNECT" } + }, + backend_timeout = { + type = "number", + minimum = 0 + }, + backend_params = { + type = "array", + uniqueItems = true, + items = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + position = { + type = "string", + enum = { "QUERY", "HEADER", "PATH" } + }, + request_param_name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + request_param_position = { + type = "string", + enum = { "QUERY", "HEADER", "PATH" } + }, + request_param_type = { + type = "string", + enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } + }, + request_param_description = { + type = "string" + } + }, + required = { "name", "position", "request_param_name", "request_param_position", "request_param_type", "request_param_description" } + } + }, + constant_params = { + type = "array", + uniqueItems = true, + items = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 1, + maxLength = 50 + }, + position = { + type = "string", + enum = { "QUERY", "HEADER", "PATH" } + }, + type = { + type = "string", + enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } + }, + value = { + type = "string" + }, + }, + required = { "name", "position", "type", "value" } + } + }, + response_type = { + type = "string", + enum = { + "text/html", + "text/xml", + "application/json", + } + }, + response_success = { + type = 'string', + minLength = 0 + }, + response_failure = { + type = 'string', + minLength = 0 + }, + response_codes = { + type = "array", + uniqueItems = true, + items = { + type = "object", + properties = { + code = { + type = "number", + minimum = 200, + maximum = 599 + }, + message = { + type = "string", + minLength = 1, + maxLength = 20 + }, + description = { + type = "string", + minLength = 0, + maxLength = 50 + } + }, + required = { "code", "message" } + } + }, + response_schema = { + type = "array", + }, + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + }, + required = { "name", "enable_cors", "description", "request_path", "request_method", "request_params", "backend_path", + "backend_method", "backend_timeout", "backend_params", "constant_params", "response_type", + "response_success", "response_failure", "response_codes", "response_schema", "project_id", "router_id" } +} + +_M.deleted = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.query = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.online = { + type = "object", + properties = { + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + env = { + type = "string", + enum = { + "PROD", + "BETA", + "TEST", + } + } + } +} + +_M.offline = { + type = "object", + properties = { + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + env = { + type = "string", + enum = { + "PROD", + "BETA", + "TEST", + } + } + } +} + +return _M -- Gitee From 07a830cd1c8a60a7720fc116be6655eb3a6bbd47 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:36:57 +0800 Subject: [PATCH 026/165] feature: add schema.user verification module. --- apioak/schema/user.lua | 147 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 apioak/schema/user.lua diff --git a/apioak/schema/user.lua b/apioak/schema/user.lua new file mode 100644 index 0000000..d9d9300 --- /dev/null +++ b/apioak/schema/user.lua @@ -0,0 +1,147 @@ +local _M = {} + +_M.register = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 4, + maxLength = 16, + }, + password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + valid_password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + email = { + type = 'string', + pattern = "^[a-zA-Z0-9\\_\\-\\.]+\\@[a-zA-Z0-9_-]+\\.[a-zA-Z\\.]+$" + } + }, + required = { "name", "password", "valid_password", "email" } +} + +_M.login = { + type = "object", + properties = { + password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + email = { + type = 'string', + pattern = "^[a-zA-Z0-9\\_\\-\\.]+\\@[a-zA-Z0-9_-]+\\.[a-zA-Z\\.]+$" + } + }, + required = { "password", "email" } +} + +_M.created = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 4, + maxLength = 16, + }, + password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + valid_password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + email = { + type = 'string', + pattern = "^[a-zA-Z0-9\\_\\-\\.]+\\@[a-zA-Z0-9_-]+\\.[a-zA-Z\\.]+$" + }, + is_enable = { + type = "number", + minimum = 0 + } + }, + required = { "name", "password", "valid_password", "email", "is_enable" } +} + +_M.updated_password = { + type = "object", + properties = { + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + valid_password = { + type = 'string', + minLength = 6, + maxLength = 20, + } + } +} + +_M.updated_status = { + type = "object", + properties = { + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + is_enable = { + type = 'number', + minimum = 0 + } + } + +} + +_M.deleted = { + type = "object", + properties = { + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[1-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +return _M -- Gitee From 61feba88d68feba066f9cc6228ee4ea0b1cd6b5b Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:39:09 +0800 Subject: [PATCH 027/165] feature: add apioak.db control module. --- apioak/db.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 apioak/db.lua diff --git a/apioak/db.lua b/apioak/db.lua new file mode 100644 index 0000000..7e3e000 --- /dev/null +++ b/apioak/db.lua @@ -0,0 +1,10 @@ +return { + user = require("apioak.db.user"), + role = require("apioak.db.role"), + group = require("apioak.db.group"), + token = require("apioak.db.token"), + router = require("apioak.db.router"), + plugin = require("apioak.db.plugin"), + project = require("apioak.db.project"), + upstream = require("apioak.db.upstream"), +} -- Gitee From 1c372d7aca657336f04ccbd1aaed9827dcd1c9cc Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:39:53 +0800 Subject: [PATCH 028/165] feature: add apioak.schema control module. --- apioak/schema.lua | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 apioak/schema.lua diff --git a/apioak/schema.lua b/apioak/schema.lua new file mode 100644 index 0000000..3c9da5b --- /dev/null +++ b/apioak/schema.lua @@ -0,0 +1,7 @@ +return { + user = require("apioak.schema.user"), + group = require("apioak.schema.group"), + router = require("apioak.schema.router"), + plugin = require("apioak.schema.plugin"), + project = require("apioak.schema.project"), +} -- Gitee From 2ca1e4796f3083c94ea0a970868275e94ea15924 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:41:09 +0800 Subject: [PATCH 029/165] change: update apioak.pdk control module. --- apioak/pdk.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apioak/pdk.lua b/apioak/pdk.lua index 3fa2ab2..4d9ca10 100644 --- a/apioak/pdk.lua +++ b/apioak/pdk.lua @@ -2,6 +2,7 @@ return { log = require("apioak.pdk.log"), ctx = require("apioak.pdk.ctx"), json = require("apioak.pdk.json"), + time = require("apioak.pdk.time"), etcd = require("apioak.pdk.etcd"), config = require("apioak.pdk.config"), shared = require("apioak.pdk.shared"), @@ -14,4 +15,5 @@ return { admin = require("apioak.pdk.admin"), pool = require("apioak.pdk.tablepool"), const = require("apioak.pdk.const"), + mysql = require("apioak.pdk.mysql"), } -- Gitee From a0b89551147c1417946a42b4ea51c7613c34cf6e Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:43:35 +0800 Subject: [PATCH 030/165] change: update sys.admin routers module. --- apioak/sys/admin.lua | 119 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 25 deletions(-) diff --git a/apioak/sys/admin.lua b/apioak/sys/admin.lua index 4c3a532..0c6139a 100644 --- a/apioak/sys/admin.lua +++ b/apioak/sys/admin.lua @@ -1,67 +1,136 @@ local r3route = require("resty.r3") -local admin = require("apioak.admin") -local _M = {} - +local admin = require("apioak.admin") local router +local _M = {} + function _M.init_worker() router = r3route.new() - -- Router Manager URI - router:insert_route("/apioak/admin/routers", admin.router.list, + -- Plugin Manager URI + router:insert_route("/apioak/admin/plugins", admin.plugin.plugin_list, + { method = { "GET" } }) + + router:insert_route("/apioak/admin/project/{project_id}/plugins", admin.plugin.project_list, { method = { "GET" } }) - router:insert_route("/apioak/admin/router", admin.router.create, + router:insert_route("/apioak/admin/project/{project_id}/plugin", admin.plugin.project_created, { method = { "POST" } }) - router:insert_route("/apioak/admin/router/{router_id}", admin.router.query, + router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.plugin.project_updated, + { method = { "PUT" } }) + + router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.plugin.project_deleted, + { method = { "DELETE" } }) + + router:insert_route("/apioak/admin/router/{router_id}/plugins", admin.plugin.router_list, { method = { "GET" } }) - router:insert_route("/apioak/admin/router/{router_id}", admin.router.update, + router:insert_route("/apioak/admin/router/{router_id}/plugin", admin.plugin.router_created, + { method = { "POST" } }) + + router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.plugin.router_updated, { method = { "PUT" } }) - router:insert_route("/apioak/admin/router/{router_id}", admin.router.delete, + router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.plugin.router_deleted, { method = { "DELETE" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugin", admin.router.plugin_create, - { method = { "POST", "PUT" } }) + -- Group Manager URI + router:insert_route("/apioak/admin/groups", admin.group.list, + { method = { "GET" } }) + + router:insert_route("/apioak/admin/group", admin.group.created, + { method = { "POST" } }) + + router:insert_route("/apioak/admin/group/{group_id}", admin.group.query, + { method = { "GET" } }) + + router:insert_route("/apioak/admin/group/{group_id}", admin.group.updated, + { method = { "PUT" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_key}", admin.router.plugin_delete, + router:insert_route("/apioak/admin/group/{group_id}", admin.group.deleted, { method = { "DELETE" } }) - router:insert_route("/apioak/admin/router/{router_id}/env/{env}", admin.router.env_create, - { method = { "POST", "PUT" } }) + -- Group User Manager URI + router:insert_route("/apioak/admin/group/{group_id}/users", admin.group.user_list, + { method = { "GET" } }) + + router:insert_route("/apioak/admin/group/{group_id}/user", admin.group.user_create, + { method = { "POST" } }) - router:insert_route("/apioak/admin/router/{router_id}/env/{env}", admin.router.env_delete, + router:insert_route("/apioak/admin/group/{group_id}/user/{user_id}", admin.group.user_update, + { method = { "PUT" } }) + + router:insert_route("/apioak/admin/group/{group_id}/user/{user_id}", admin.group.user_delete, { method = { "DELETE" } }) - -- Service Manager URI - router:insert_route("/apioak/admin/services", admin.service.list, + -- Group Project Manager URI + router:insert_route("/apioak/admin/group/{group_id}/projects", admin.project.list, { method = { "GET" } }) - router:insert_route("/apioak/admin/service", admin.service.create, + router:insert_route("/apioak/admin/group/{group_id}/project", admin.project.create, { method = { "POST" } }) - router:insert_route("/apioak/admin/service/{service_id}", admin.service.update, + router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.query, + { method = { "GET" } }) + + router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.update, { method = { "PUT" } }) - router:insert_route("/apioak/admin/service/{service_id}", admin.service.delete, + router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.update, { method = { "DELETE" } }) - router:insert_route("/apioak/admin/service/{service_id}", admin.service.query, + + -- Project Router Manager URI + router:insert_route("/apioak/admin/project/{project_id}/routers", admin.router.list, { method = { "GET" } }) - router:insert_route("/apioak/admin/service/{service_id}/plugin", admin.service.plugin_create, + router:insert_route("/apioak/admin/project/{project_id}/router", admin.router.created, { method = { "POST" } }) - router:insert_route("/apioak/admin/service/{service_id}/plugin/{plugin_key}", admin.service.plugin_delete, + router:insert_route("/apioak/admin/project/{project_id}/router/{router_id}", admin.router.query, + { method = { "GET" } }) + + router:insert_route("/apioak/admin/project/{project_id}/router/{router_id}", admin.router.updated, + { method = { "PUT" } }) + + router:insert_route("/apioak/admin/project/{project_id}/router/{router_id}", admin.router.deleted, { method = { "DELETE" } }) - -- Plugin Manager URI - router:insert_route("/apioak/admin/plugins", admin.plugin.list, + -- Router Publish Manager URI + router:insert_route("/apioak/admin/router/{router_id}/publish/{env}", admin.router.online, + { method = { "POST" } }) + + router:insert_route("/apioak/admin/router/{router_id}/publish/{env}", admin.router.offline, + { method = { "DELETE" } }) + + -- Account Manager API + router:insert_route("/apioak/admin/account/register", admin.user.register, + { method = { "POST" } }) + + router:insert_route("/apioak/admin/account/login", admin.user.login, + { method = { "POST" } }) + + router:insert_route("/apioak/admin/account/logout", admin.user.logout, + { method = { "GET" } }) + + -- User Manager API + router:insert_route("/apioak/admin/users", admin.user.list, { method = { "GET" } }) + router:insert_route("/apioak/admin/user", admin.user.created, + { method = { "POST" } }) + + router:insert_route("/apioak/admin/user/{user_id}/password", admin.user.updated_password, + { method = { "PUT" } }) + + router:insert_route("/apioak/admin/user/{user_id}/status", admin.user.updated_status, + { method = { "PUT" } }) + + router:insert_route("/apioak/admin/user/{user_id}", admin.user.deleted, + { method = { "DELETE" } }) + router:compile() end -- Gitee From 6291a8e2bf5d316117274a2d955226ce49441640 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:52:45 +0800 Subject: [PATCH 031/165] change: updated plugin.jwt-auth description. --- apioak/plugin/jwt-auth.lua | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/apioak/plugin/jwt-auth.lua b/apioak/plugin/jwt-auth.lua index 068f65f..7a80a25 100644 --- a/apioak/plugin/jwt-auth.lua +++ b/apioak/plugin/jwt-auth.lua @@ -2,20 +2,18 @@ local jwt = require("resty.jwt") local pdk = require("apioak.pdk") local _M = { - type = 'Authentication', - name = "Jwt Auth", - desc = "Lua module for JWT Authentication.", - key = "jwt-auth", - order = 1301, - parameter = { + name = "jwt-auth", + type = "Authentication", + description = "Lua module for JWT Authentication.", + config = { secret = { - type = "string", - default = "A65001FB250D8F2E87E3B5821B2C48C7", - minLength = 10, - maxLength = 32, - desc = "signature secret key.", + type = "string", + default = "A65001FB250D8F2E87E3B5821B2C48C7", + minLength = 10, + maxLength = 32, + description = "signature secret key", } - }, + } } local schema = { -- Gitee From 96a0b377ac30f5a34bd2047b67c5a0b1d8194e81 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:53:23 +0800 Subject: [PATCH 032/165] change: updated plugin.key-auth description. --- apioak/plugin/key-auth.lua | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/apioak/plugin/key-auth.lua b/apioak/plugin/key-auth.lua index a56f994..c0ec4f0 100644 --- a/apioak/plugin/key-auth.lua +++ b/apioak/plugin/key-auth.lua @@ -1,20 +1,18 @@ local pdk = require("apioak.pdk") local _M = { - type = 'Authentication', - name = "Key Auth", - desc = "Lua module for Key Authentication.", - key = "key-auth", - order = 1201, - parameter = { + name = "key-auth", + type = "Authentication", + description = "Lua module for Key Authentication.", + config = { secret = { - type = "string", - default = "A65001FB250D8F2E87E3B5821B2C48C7", - minLength = 10, - maxLength = 32, - desc = "signature secret key.", + type = "string", + default = "A65001FB250D8F2E87E3B5821B2C48C7", + minLength = 10, + maxLength = 32, + description = "signature secret key.", } - }, + } } local function key_verify(secret_key, secret) -- Gitee From a5153a94f1dcf5ae9f9ea1168caac997bc636e10 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:54:03 +0800 Subject: [PATCH 033/165] change: updated plugin.limit-conn description. --- apioak/plugin/limit-conn.lua | 40 +++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/apioak/plugin/limit-conn.lua b/apioak/plugin/limit-conn.lua index 889ad66..c71b937 100644 --- a/apioak/plugin/limit-conn.lua +++ b/apioak/plugin/limit-conn.lua @@ -3,32 +3,30 @@ local ngx_var = ngx.var local pdk = require("apioak.pdk") local _M = { - type = "Traffic Control", - name = "Limit Conn", - desc = "Lua module for limiting request concurrency (or concurrent connections).", - key = "limit-conn", - order = 1101, - parameter = { + name = "limit-conn", + type = "Traffic Control", + description = "Lua module for limiting request concurrency (or concurrent connections).", + config = { rate = { - type = "number", - minimum = 1, - maximum = 0, - default = 200, - desc = "the maximum number of concurrent requests allowed." + type = "number", + minimum = 1, + maximum = 0, + default = 200, + description = "the maximum number of concurrent requests allowed." }, burst = { - type = "number", - minimum = 1, - maximum = 0, - default = 100, - desc = "the number of excessive concurrent requests (or connections) allowed to be delayed." + type = "number", + minimum = 1, + maximum = 0, + default = 100, + description = "the number of excessive concurrent requests (or connections) allowed to be delayed." }, default_conn_delay = { - type = "number", - minimum = 1, - maximum = 60, - default = 1, - desc = "the default processing latency of a typical connection (or request)." + type = "number", + minimum = 1, + maximum = 60, + default = 1, + description = "the default processing latency of a typical connection (or request)." } }, conf = {} -- Gitee From dbd8d7475f7140b894f8aa8081d8445e822a4370 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:54:29 +0800 Subject: [PATCH 034/165] change: updated plugin.limit-count description. --- apioak/plugin/limit-count.lua | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/apioak/plugin/limit-count.lua b/apioak/plugin/limit-count.lua index a38783a..be5eb46 100644 --- a/apioak/plugin/limit-count.lua +++ b/apioak/plugin/limit-count.lua @@ -3,25 +3,23 @@ local ngx_var = ngx.var local pdk = require("apioak.pdk") local _M = { - type = "Traffic Control", - name = "Limit Count", - desc = "Lua module for limiting request counts.", - key = "limit-count", - order = 1102, - parameter = { + name = "limit-count", + type = "Traffic Control", + description = "Lua module for limiting request counts.", + config = { count = { - type = "number", - minimum = 1, - maximum = 0, - default = 5000, - desc = "the specified number of requests threshold.", + type = "number", + minimum = 1, + maximum = 0, + default = 5000, + description = "the specified number of requests threshold.", }, time_window = { - type = "number", - minimum = 1, - maximum = 0, - default = 3600, - desc = "the time window in seconds before the request count is reset.", + type = "number", + minimum = 1, + maximum = 0, + default = 3600, + description = "the time window in seconds before the request count is reset.", } } } -- Gitee From 4fca5b89f46002be3798d5ccbb75c1a20898d061 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 18:54:42 +0800 Subject: [PATCH 035/165] change: updated plugin.limit-req description. --- apioak/plugin/limit-req.lua | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/apioak/plugin/limit-req.lua b/apioak/plugin/limit-req.lua index efa033e..f89e1c3 100644 --- a/apioak/plugin/limit-req.lua +++ b/apioak/plugin/limit-req.lua @@ -3,25 +3,23 @@ local ngx_var = ngx.var local pdk = require("apioak.pdk") local _M = { - type = "Traffic Control", - name = "Limit Req", - desc = "Lua module for limiting request rate.", - key = "limit-req", - order = 1103, - parameter = { + name = "limit-req", + type = "Traffic Control", + description = "Lua module for limiting request rate.", + config = { rate = { - type = "number", - minimum = 1, - maximum = 0, - default = 200, - desc = "the specified request rate (number per second) threshold." + type = "number", + minimum = 1, + maximum = 0, + default = 200, + description = "the specified request rate (number per second) threshold." }, burst = { - type = "number", - minimum = 1, - maximum = 0, - default = 3600, - desc = "the number of excessive requests per second allowed to be delayed." + type = "number", + minimum = 1, + maximum = 0, + default = 3600, + description = "the number of excessive requests per second allowed to be delayed." } } } -- Gitee From c1c4fd53defbc3558af510d342b9ba7b4837d0c9 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 3 Mar 2020 19:05:20 +0800 Subject: [PATCH 036/165] feature: add apioak mysql data table config file. --- conf/apioak.sql | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 conf/apioak.sql diff --git a/conf/apioak.sql b/conf/apioak.sql new file mode 100644 index 0000000..c0c55fc --- /dev/null +++ b/conf/apioak.sql @@ -0,0 +1,145 @@ +DROP TABLE IF EXISTS `oak_groups`; + +CREATE TABLE `oak_groups` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) DEFAULT NULL COMMENT '组名称', + `description` text COMMENT '组描述', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQ_NAME` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目组表'; + + + +DROP TABLE IF EXISTS `oak_plugins`; + +CREATE TABLE `oak_plugins` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(20) DEFAULT NULL COMMENT '插件名称', + `type` varchar(20) DEFAULT NULL COMMENT '插件类型', + `description` text COMMENT '插件描述', + `config` json DEFAULT NULL COMMENT '插件配置', + `res_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '资源ID', + `res_type` varchar(20) DEFAULT NULL COMMENT '资源类型(PROJECT/ROUTER)', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQ_RESOURCES` (`name`,`res_id`,`res_type`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件表'; + + + +DROP TABLE IF EXISTS `oak_projects`; + +CREATE TABLE `oak_projects` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) DEFAULT NULL COMMENT '项目名称', + `description` text COMMENT '项目描述', + `path` varchar(50) DEFAULT NULL COMMENT '项目前缀', + `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目组ID', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQ_PATH` (`path`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目表'; + + + +DROP TABLE IF EXISTS `oak_roles`; + +CREATE TABLE `oak_roles` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '组ID', + `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `is_admin` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '组管理员', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQ_GROUP_USER` (`group_id`,`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; + + + +DROP TABLE IF EXISTS `oak_routers`; + +CREATE TABLE `oak_routers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) DEFAULT NULL COMMENT '接口名称', + `enable_cors` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '开启跨域', + `description` text COMMENT '接口描述', + `request_path` varchar(100) DEFAULT NULL COMMENT '请求路径', + `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式', + `request_params` json DEFAULT NULL COMMENT '请求参数', + `backend_path` varchar(100) DEFAULT NULL COMMENT '后端请求路径', + `backend_method` varchar(10) DEFAULT NULL COMMENT '后端请求方式', + `backend_timeout` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '后端超时时间', + `backend_params` json DEFAULT NULL COMMENT '后端参数', + `constant_params` json DEFAULT NULL COMMENT '常量参数', + `response_type` varchar(50) DEFAULT NULL COMMENT '响应类型', + `response_success` text COMMENT '响应成功消息', + `response_failure` text COMMENT '响应失败消息', + `response_codes` json DEFAULT NULL COMMENT '响应错误码', + `response_schema` json DEFAULT NULL COMMENT '响应描述', + `env_prod_config` json DEFAULT NULL COMMENT '生产环境配置', + `env_beta_config` json DEFAULT NULL COMMENT '预发环境配置', + `env_test_config` json DEFAULT NULL COMMENT '测试环境配置', + `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', + `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQ_REQUEST_PATH` (`request_path`), + UNIQUE KEY `UNIQ_NAME` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='路由表'; + + + +DROP TABLE IF EXISTS `oak_tokens`; + +CREATE TABLE `oak_tokens` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `token` char(32) DEFAULT NULL COMMENT '登录令牌', + `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID', + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `expired_at` timestamp NULL DEFAULT NULL COMMENT '超时时间', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQ_USER` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='令牌表'; + + + +DROP TABLE IF EXISTS `oak_upstreams`; + +CREATE TABLE `oak_upstreams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `env` varchar(10) DEFAULT NULL COMMENT '发布环境', + `host` varchar(50) DEFAULT NULL COMMENT '主机地址', + `retries` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '重试次数', + `type` varchar(10) DEFAULT NULL COMMENT '负载均衡算法', + `nodes` json DEFAULT NULL COMMENT '服务节点', + `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQ_ENV_PROJECT` (`env`,`project_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务节点表'; + + + +DROP TABLE IF EXISTS `oak_users`; + +CREATE TABLE `oak_users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) DEFAULT NULL COMMENT '用户名', + `password` char(32) DEFAULT NULL COMMENT '密码', + `email` varchar(50) DEFAULT NULL COMMENT '邮箱', + `is_owner` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '超级管理员', + `is_enable` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否启用', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `UNIQ_EMAIL` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- Gitee From e7020b01103704e5c609373de3d81fc182c80a1c Mon Sep 17 00:00:00 2001 From: Janko Date: Wed, 4 Mar 2020 10:58:34 +0800 Subject: [PATCH 037/165] change: update project schema valid params. --- apioak/schema/project.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apioak/schema/project.lua b/apioak/schema/project.lua index 5f18a18..8ac9b62 100644 --- a/apioak/schema/project.lua +++ b/apioak/schema/project.lua @@ -142,11 +142,11 @@ _M.updated = { }, type = { type = "string", - enum = { "chash", "roundrobin" } + enum = { "CHASH", "ROUNDROBIN" } }, env = { type = "string", - enum = { "prod", "beta", "test" } + enum = { "PROD", "BETA", "TEST" } }, nodes = { type = "array", @@ -226,11 +226,11 @@ _M.created = { }, type = { type = "string", - enum = { "chash", "roundrobin" } + enum = { "CHASH", "ROUNDROBIN" } }, env = { type = "string", - enum = { "prod", "beta", "test" } + enum = { "PROD", "BETA", "TEST" } }, nodes = { type = "array", -- Gitee From 5619f40c01815d5d0da47dccecbf44529b7fb816 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:40:24 +0800 Subject: [PATCH 038/165] feature: pdk.table add table init function. --- apioak/pdk/table.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apioak/pdk/table.lua b/apioak/pdk/table.lua index 39073a3..e6f9b46 100644 --- a/apioak/pdk/table.lua +++ b/apioak/pdk/table.lua @@ -10,6 +10,8 @@ _M.clear = table.clear _M.remove = table.remove +_M.new = table.new + _M.has = function(val, tab) for _, v in ipairs(tab) do if v == val then -- Gitee From c06b4c96fdc6c6fcf72629e886dc143f6adf2aa0 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:41:01 +0800 Subject: [PATCH 039/165] feature: pdk.request add add header function. --- apioak/pdk/request.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apioak/pdk/request.lua b/apioak/pdk/request.lua index 374b303..3a06f7e 100644 --- a/apioak/pdk/request.lua +++ b/apioak/pdk/request.lua @@ -76,4 +76,6 @@ end _M.header = _header +_M.add_header = ngx.req.set_header + return _M -- Gitee From e8a6f2cc255199292e8171a258288c6eeebca2fa Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:43:45 +0800 Subject: [PATCH 040/165] change: updated constant from pdk.const. --- apioak/pdk/const.lua | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apioak/pdk/const.lua b/apioak/pdk/const.lua index 8bff543..45925f8 100644 --- a/apioak/pdk/const.lua +++ b/apioak/pdk/const.lua @@ -4,26 +4,32 @@ _M.LOCAL_IP = "127.0.0.1" _M.LOCAL_HOST = "localhost" -_M.BALANCER_CHASH = "chash" +_M.BALANCER_CHASH = "CHASH" -_M.BALANCER_ROUNDROBIN = "roundrobin" +_M.BALANCER_ROUNDROBIN = "ROUNDROBIN" -_M.CONTENT_TYPE_JSON = "application/json" +_M.ENVIRONMENT_PROD = "PROD" -_M.CONTENT_TYPE_HTML = "text/html" +_M.ENVIRONMENT_BETA = "BETA" -_M.CONTENT_TYPE_XML = "text/xml" +_M.ENVIRONMENT_TEST = "TEST" -_M.BALANCER_COMMECT_TIMEOUT = 60 +_M.REQUEST_API_ENV_KEY = "APIOAK-API-ENV" -_M.BALANCER_SEND_TIMEOUT = 60 +_M.REQUEST_ADMIN_TOKEN_KEY = "APIOAK-ADMIN-TOKEN" -_M.BALANCER_READ_TIMEOUT = 10 +_M.REQUEST_PARAM_POS_QUERY = "QUERY" -_M.TRY_MAX_NUMBER = 1 +_M.REQUEST_PARAM_POS_PATH = "PATH" -_M.TRY_DEFAULT_NUMBER = 0 +_M.REQUEST_PARAM_POS_HEADER = "HEADER" -_M.TRY_INC_NUMBER = 1 +_M.REQUEST_PARAM_NGX_VARIABLE = "VARIABLE" + +_M.CONTENT_TYPE_JSON = "application/json" + +_M.CONTENT_TYPE_HTML = "text/html" + +_M.CONTENT_TYPE_XML = "text/xml" return _M -- Gitee From 9ac698565043e8a95e43d23c360eae502c54392d Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:45:21 +0800 Subject: [PATCH 041/165] change: updated jwt auth plugin config variable name. --- apioak/plugin/jwt-auth.lua | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apioak/plugin/jwt-auth.lua b/apioak/plugin/jwt-auth.lua index 7a80a25..ea4c73c 100644 --- a/apioak/plugin/jwt-auth.lua +++ b/apioak/plugin/jwt-auth.lua @@ -49,14 +49,11 @@ end function _M.http_access(oak_ctx) - if not oak_ctx['plugins'] then - return false, nil - end - if not oak_ctx.plugins[_M.key] then + if not oak_ctx.plugins or not oak_ctx.plugins[_M.name] then return false, nil end - local plugin_conf = oak_ctx.plugins[_M.key] + local plugin_conf = oak_ctx.plugins[_M.name] local _, err = pdk.schema.check(schema, plugin_conf) if err then return false, nil @@ -74,6 +71,3 @@ end return _M - - - -- Gitee From 34d43891fbba70f3b123af9ba580a7069aa6f404 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:45:52 +0800 Subject: [PATCH 042/165] change: updated key auth plugin config variable name. --- apioak/plugin/key-auth.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apioak/plugin/key-auth.lua b/apioak/plugin/key-auth.lua index c0ec4f0..c1a8318 100644 --- a/apioak/plugin/key-auth.lua +++ b/apioak/plugin/key-auth.lua @@ -25,9 +25,9 @@ end function _M.http_access(oak_ctx) - if oak_ctx['plugins'] and oak_ctx.plugins[_M.key] then + if oak_ctx.plugins and oak_ctx.plugins[_M.name] then - local plugin_conf = oak_ctx.plugins[_M.key] + local plugin_conf = oak_ctx.plugins[_M.name] if plugin_conf.secret then local secret_key = pdk.request.header('Authentication') @@ -44,6 +44,3 @@ function _M.http_access(oak_ctx) end return _M - - - -- Gitee From f4051e5c9f89e841a3db82708f74051353489536 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:46:31 +0800 Subject: [PATCH 043/165] change: updated limit conn plugin config variable name. --- apioak/plugin/limit-conn.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apioak/plugin/limit-conn.lua b/apioak/plugin/limit-conn.lua index c71b937..7e438ab 100644 --- a/apioak/plugin/limit-conn.lua +++ b/apioak/plugin/limit-conn.lua @@ -10,14 +10,14 @@ local _M = { rate = { type = "number", minimum = 1, - maximum = 0, + maximum = 100000, default = 200, description = "the maximum number of concurrent requests allowed." }, burst = { type = "number", minimum = 1, - maximum = 0, + maximum = 50000, default = 100, description = "the number of excessive concurrent requests (or connections) allowed to be delayed." }, @@ -68,14 +68,11 @@ local function create_limit_obj(conf) end function _M.http_access(oak_ctx) - if not oak_ctx['plugins'] then + if not oak_ctx.plugins or not oak_ctx.plugins[_M.name] then return false, nil end - if not oak_ctx.plugins[_M.key] then - return false, nil - end - local plugin_conf = oak_ctx.plugins[_M.key] + local plugin_conf = oak_ctx.plugins[_M.name] local _, err = pdk.schema.check(schema, plugin_conf) if err then return false, nil -- Gitee From 612801447697b0773dc996e7e1ea37ed78d0e5d3 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:46:57 +0800 Subject: [PATCH 044/165] change: updated limit count plugin config variable name. --- apioak/plugin/limit-count.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apioak/plugin/limit-count.lua b/apioak/plugin/limit-count.lua index be5eb46..661d06d 100644 --- a/apioak/plugin/limit-count.lua +++ b/apioak/plugin/limit-count.lua @@ -10,14 +10,14 @@ local _M = { count = { type = "number", minimum = 1, - maximum = 0, + maximum = 100000000, default = 5000, description = "the specified number of requests threshold.", }, time_window = { type = "number", minimum = 1, - maximum = 0, + maximum = 86400, default = 3600, description = "the time window in seconds before the request count is reset.", } @@ -57,14 +57,11 @@ local function create_limit_obj(conf) end function _M.http_access(oak_ctx) - if not oak_ctx['plugins'] then + if not oak_ctx.plugins or not oak_ctx.plugins[_M.name] then return false, nil end - if not oak_ctx.plugins[_M.key] then - return false, nil - end - local plugin_conf = oak_ctx.plugins[_M.key] + local plugin_conf = oak_ctx.plugins[_M.name] local _, err = pdk.schema.check(schema, plugin_conf) if err then return false, nil -- Gitee From d84d676611d344e1253e6bf746a26a8ad8dd73f6 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:47:28 +0800 Subject: [PATCH 045/165] change: updated limit req plugin config variable name. --- apioak/plugin/limit-req.lua | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apioak/plugin/limit-req.lua b/apioak/plugin/limit-req.lua index f89e1c3..58dc49a 100644 --- a/apioak/plugin/limit-req.lua +++ b/apioak/plugin/limit-req.lua @@ -10,15 +10,15 @@ local _M = { rate = { type = "number", minimum = 1, - maximum = 0, + maximum = 10000, default = 200, description = "the specified request rate (number per second) threshold." }, burst = { type = "number", minimum = 1, - maximum = 0, - default = 3600, + maximum = 5000, + default = 100, description = "the number of excessive requests per second allowed to be delayed." } } @@ -57,15 +57,11 @@ local function create_limit_obj(conf) end function _M.http_access(oak_ctx) - if not oak_ctx['plugins'] then + if not oak_ctx.plugins or not oak_ctx.plugins[_M.name] then return false, nil end - if not oak_ctx.plugins[_M.key] then - return false, nil - end - - local plugin_conf = oak_ctx.plugins[_M.key] + local plugin_conf = oak_ctx.plugins[_M.name] local _, err = pdk.schema.check(schema, plugin_conf) if err then return false, nil -- Gitee From 4aa68ed508c892f6e3c186b58afdc3d691349663 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:49:35 +0800 Subject: [PATCH 046/165] change: updated admin base controller attribute variable. --- apioak/admin/controller.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apioak/admin/controller.lua b/apioak/admin/controller.lua index 63b01a1..3dc372c 100644 --- a/apioak/admin/controller.lua +++ b/apioak/admin/controller.lua @@ -5,15 +5,13 @@ local controller = function(name) local cls = {} - cls.__class = name + cls.__class = name - cls.header_token_key = "APIOAK-ADMIN-TOKEN" + cls.uid = nil - cls.uid = nil + cls.token = nil - cls.token = nil - - cls.is_owner = nil + cls.is_owner = nil cls.get_body = function(key) local body, err = pdk.request.body() @@ -41,7 +39,7 @@ local controller = function(name) cls.get_header_token = function() - local token = cls.get_header(cls.header_token_key) + local token = cls.get_header(pdk.const.REQUEST_ADMIN_TOKEN_KEY) if not token then pdk.response.exit(401, { err_message = pdk.string.format( "property header \"%s\" is required", cls.header_token_key) }) -- Gitee From c632aff4f46203e460ad25d9bc396e4cfa1de817 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:50:44 +0800 Subject: [PATCH 047/165] change: updated admin.router publish api. --- apioak/admin/router.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apioak/admin/router.lua b/apioak/admin/router.lua index 4f01f2b..5f517ff 100644 --- a/apioak/admin/router.lua +++ b/apioak/admin/router.lua @@ -127,15 +127,13 @@ function router_controller.online(params) router.response_failure = pdk.json.decode(router.response_failure) end + local plugins = {} res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id) if err then pdk.response.exit(500, { err_message = err }) end - - local plugins = {} - for i = 1, #res do - local plugin = res[i] - plugins[plugin.name] = plugin + for q = 1, #res do + plugins[res[q].name] = res[q] end router.plugins = plugins -- Gitee From 2204ebcaa3b0a5926aac6fb542592442a29a8c54 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:54:01 +0800 Subject: [PATCH 048/165] change: updated db.router query functions. --- apioak/db/router.lua | 94 +++++++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/apioak/db/router.lua b/apioak/db/router.lua index 41b29b9..86c1e18 100644 --- a/apioak/db/router.lua +++ b/apioak/db/router.lua @@ -1,26 +1,14 @@ local pdk = require("apioak.pdk") -local PROD_TABLE_FIELD = "PROD" -local BETA_TABLE_FIELD = "BETA" -local TEST_TABLE_FIELD = "TEST" - -local table_env_fields = {} -table_env_fields[PROD_TABLE_FIELD] = "env_prod_config" -table_env_fields[BETA_TABLE_FIELD] = "env_beta_config" -table_env_fields[TEST_TABLE_FIELD] = "env_test_config" - local table_name = "oak_routers" local _M = {} _M.table_name = table_name -_M.PROD_TABLE_FIELD = PROD_TABLE_FIELD -_M.BETA_TABLE_FIELD = BETA_TABLE_FIELD -_M.TEST_TABLE_FIELD = TEST_TABLE_FIELD - function _M.query_by_pid(project_id) - local sql = "SELECT id, name, enable_cors, description, request_path, request_method FROM %s WHERE project_id = %s" + local sql = "SELECT id, name, enable_cors, description, request_path, request_method, env_prod_config, " .. + "env_beta_config, env_test_config FROM %s WHERE project_id = %s" sql = pdk.string.format(sql, table_name, project_id) local res, err = pdk.mysql.execute(sql) @@ -28,16 +16,37 @@ function _M.query_by_pid(project_id) return nil, err end + for i = 1, #res do + if res[i].env_prod_config == pdk.string.null then + res[i].env_prod_publish = 0 + else + res[i].env_prod_publish = 1 + end + res[i].env_prod_config = nil + + if res[i].env_beta_config == pdk.string.null then + res[i].env_beta_publish = 0 + else + res[i].env_beta_publish = 1 + end + res[i].env_beta_config = nil + + if res[i].env_test_config == pdk.string.null then + res[i].env_test_publish = 0 + else + res[i].env_test_publish = 1 + end + res[i].env_test_config = nil + end + return res, nil end function _M.query(router_id) local sql = "SELECT id, name, enable_cors, description, request_path, request_method, request_params, " .. "backend_path, backend_method, backend_timeout, backend_params, constant_params, response_type, " .. - "response_success, response_failure, response_codes, response_schema, " .. - "IF(env_prod_config = NULL, 0, 1) AS env_prod_config, " .. - "IF(env_beta_config = NULL, 0, 1) AS env_beta_config, " .. - "IF(env_test_config = NULL, 0, 1) AS env_test_config, project_id FROM %s WHERE id = %s"; + "response_success, response_failure, response_codes, response_schema, env_prod_config, env_beta_config, " .. + "env_test_config, project_id FROM %s WHERE id = %s"; sql = pdk.string.format(sql, table_name, router_id) local res, err = pdk.mysql.execute(sql) @@ -51,6 +60,27 @@ function _M.query(router_id) res[i].constant_params = pdk.json.decode(res[i].constant_params) res[i].response_codes = pdk.json.decode(res[i].response_codes) res[i].response_schema = pdk.json.decode(res[i].response_schema) + + if res[i].env_prod_config == pdk.string.null then + res[i].env_prod_publish = 0 + else + res[i].env_prod_publish = 1 + end + res[i].env_prod_config = nil + + if res[i].env_beta_config == pdk.string.null then + res[i].env_beta_publish = 0 + else + res[i].env_beta_publish = 1 + end + res[i].env_beta_config = nil + + if res[i].env_test_config == pdk.string.null then + res[i].env_test_publish = 0 + else + res[i].env_test_publish = 1 + end + res[i].env_test_config = nil end return res, nil @@ -105,10 +135,9 @@ function _M.deleted(router_id) end function _M.online(router_id, env, router_info) - local table_field = table_env_fields[env] router_info = pdk.json.encode(router_info) - local sql = pdk.string.format("UPDATE %s SET %s = '%s' WHERE id = %s", - table_name, table_field, router_info, router_id) + local sql = pdk.string.format("UPDATE %s SET env_%s_config = '%s' WHERE id = %s", + table_name, pdk.string.lower(env), router_info, router_id) local res, err = pdk.mysql.execute(sql) if err then @@ -118,9 +147,8 @@ function _M.online(router_id, env, router_info) end function _M.offline(router_id, env) - local table_field = table_env_fields[env] - local sql = pdk.string.format("UPDATE %s SET %s = NULL WHERE id = %s", - table_name, table_field, router_id) + local sql = pdk.string.format("UPDATE %s SET env_%s_config = NULL WHERE id = %s", + table_name, pdk.string.lower(env), router_id) local res, err = pdk.mysql.execute(sql) if err then @@ -129,4 +157,22 @@ function _M.offline(router_id, env) return res, nil end +function _M.query_env_by_pid(project_id) + local sql = "SELECT id, request_method, request_path, response_type, response_success, env_prod_config, " .. + "env_beta_config, env_test_config FROM %s WHERE project_id = %s" + sql = pdk.string.format(sql, table_name, project_id) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + for i = 1, #res do + res[i].env_prod_config = pdk.json.decode(res[i].env_prod_config) + res[i].env_beta_config = pdk.json.decode(res[i].env_beta_config) + res[i].env_test_config = pdk.json.decode(res[i].env_test_config) + end + + return res, nil +end + return _M -- Gitee From dff14d4bcd6e8c9ea74f859d8e7f0ac8a08ad291 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 21:55:48 +0800 Subject: [PATCH 049/165] change: updated db.project query functions. --- apioak/db/project.lua | 51 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/apioak/db/project.lua b/apioak/db/project.lua index 4d8bc09..c4dd682 100644 --- a/apioak/db/project.lua +++ b/apioak/db/project.lua @@ -1,4 +1,6 @@ -local pdk = require("apioak.pdk") +local pdk = require("apioak.pdk") +local plugin = require("apioak.db.plugin") +local upstream = require("apioak.db.upstream") local table_name = "oak_projects" @@ -6,6 +8,16 @@ local _M = {} _M.table_name = table_name +function _M.all() + local sql = pdk.string.format("SELECT * FROM %s", table_name) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + return res, nil +end + function _M.query_by_gid(group_id) local sql = pdk.string.format("SELECT * FROM %s WHERE group_id = %s", table_name, group_id) local res, err = pdk.mysql.execute(sql) @@ -60,4 +72,41 @@ function _M.delete(project_id) return res, nil end +function _M.query_env_all() + local sql = pdk.string.format("SELECT id, path FROM %s", table_name) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + + local projects = res + for i = 1, #projects do + local project = projects[i] + res, err = plugin.query_by_res(plugin.RESOURCES_TYPE_PROJECT, project.id) + if err then + return nil, err + end + + local plugins = {} + for p = 1, #res do + plugins[res[p].name] = res[p] + end + projects[i].plugins = plugins + + res, err = upstream.query_by_pid(project.id) + if err then + return nil, err + end + + local upstreams = {} + for u = 1, #res do + upstreams[res[u].env] = res[u] + end + projects[i].upstreams = upstreams + end + + return projects, nil +end + return _M -- Gitee From 68d395681825fd2a77cc755ff33d6705fe522b2b Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:17:06 +0800 Subject: [PATCH 050/165] change: update schema.group ID check rules. --- apioak/schema/group.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apioak/schema/group.lua b/apioak/schema/group.lua index 1838c43..2a319c6 100644 --- a/apioak/schema/group.lua +++ b/apioak/schema/group.lua @@ -25,7 +25,7 @@ _M.updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -55,7 +55,7 @@ _M.deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -74,7 +74,7 @@ _M.query = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -93,7 +93,7 @@ _M.user_list = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -112,7 +112,7 @@ _M.user_created = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -125,7 +125,7 @@ _M.user_created = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -135,7 +135,7 @@ _M.user_created = { }, is_admin = { type = "number", - minimum = 0 + enum = { 0, 1 } } }, required = { "group_id", "user_id", "is_admin" } @@ -149,7 +149,7 @@ _M.user_deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -162,7 +162,7 @@ _M.user_deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -181,7 +181,7 @@ _M.user_updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -194,7 +194,7 @@ _M.user_updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -204,7 +204,7 @@ _M.user_updated = { }, is_admin = { type = "number", - minimum = 0 + enum = { 0, 1 } } }, required = { "group_id", "user_id", "is_admin" } -- Gitee From 3c455af0dd1a34d190968f5402562b6faf883ded Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:17:28 +0800 Subject: [PATCH 051/165] change: update schema.plugin ID check rules. --- apioak/schema/plugin.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apioak/schema/plugin.lua b/apioak/schema/plugin.lua index 59b49a6..12bbe11 100644 --- a/apioak/schema/plugin.lua +++ b/apioak/schema/plugin.lua @@ -8,7 +8,7 @@ _M.project_list = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -27,7 +27,7 @@ _M.project_created = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -65,7 +65,7 @@ _M.project_updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -78,7 +78,7 @@ _M.project_updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -116,7 +116,7 @@ _M.project_deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -129,7 +129,7 @@ _M.project_deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -148,7 +148,7 @@ _M.router_list = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -167,7 +167,7 @@ _M.router_created = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -205,7 +205,7 @@ _M.router_updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -218,7 +218,7 @@ _M.router_updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -256,7 +256,7 @@ _M.router_deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -269,7 +269,7 @@ _M.router_deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", -- Gitee From 2bb0bb49e7951f5a3014be13011b28df66ee349e Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:17:59 +0800 Subject: [PATCH 052/165] change: update schema.project ID check rules. --- apioak/schema/project.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/apioak/schema/project.lua b/apioak/schema/project.lua index 8ac9b62..b1fe638 100644 --- a/apioak/schema/project.lua +++ b/apioak/schema/project.lua @@ -9,7 +9,7 @@ _M.list = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -28,7 +28,7 @@ _M.deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -41,7 +41,7 @@ _M.deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -60,7 +60,7 @@ _M.query = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -73,7 +73,7 @@ _M.query = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -92,7 +92,7 @@ _M.updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -128,7 +128,7 @@ _M.updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -189,7 +189,7 @@ _M.created = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -273,7 +273,7 @@ _M.plugin_list = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -292,7 +292,7 @@ _M.plugin_created = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -330,7 +330,7 @@ _M.plugin_updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -343,7 +343,7 @@ _M.plugin_updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -381,7 +381,7 @@ _M.plugin_deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -394,7 +394,7 @@ _M.plugin_deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", -- Gitee From d605014e7b47dba97bffd753e460a12c361fe795 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:18:25 +0800 Subject: [PATCH 053/165] change: update schema.router ID check rules. --- apioak/schema/router.lua | 62 +++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/apioak/schema/router.lua b/apioak/schema/router.lua index a0a550f..207582e 100644 --- a/apioak/schema/router.lua +++ b/apioak/schema/router.lua @@ -8,7 +8,7 @@ _M.list = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -65,14 +65,18 @@ _M.created = { enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } }, default_value = { - type = "string" + type = "string", + minLength = 0, + maxLength = 50 }, required = { type = "number", enum = { 0, 1 } }, description = { - type = "string" + type = "string", + minLength = 0, + maxLength = 100 } }, required = { "name", "position", "type", "required" } @@ -119,8 +123,19 @@ _M.created = { type = "string", enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } }, + request_param_required = { + type = "number", + enum = { 0, 1 } + }, + request_param_default_val = { + type = "string", + minLength = 0, + maxLength = 50 + }, request_param_description = { - type = "string" + type = "string", + minLength = 0, + maxLength = 100 } }, required = { "name", "position", "request_param_name", "request_param_position", "request_param_type", "request_param_description" } @@ -201,7 +216,7 @@ _M.created = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -261,14 +276,18 @@ _M.updated = { enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } }, default_value = { - type = "string" + type = "string", + minLength = 0, + maxLength = 50 }, required = { type = "number", enum = { 0, 1 } }, description = { - type = "string" + type = "string", + minLength = 0, + maxLength = 100 } }, required = { "name", "position", "type", "required" } @@ -315,8 +334,19 @@ _M.updated = { type = "string", enum = { "BOOLEAN", "INTEGER", "FLOAT", "STRING" } }, + request_param_required = { + type = "number", + enum = { 0, 1 } + }, + request_param_default_val = { + type = "string", + minLength = 0, + maxLength = 50 + }, request_param_description = { - type = "string" + type = "string", + minLength = 0, + maxLength = 100 } }, required = { "name", "position", "request_param_name", "request_param_position", "request_param_type", "request_param_description" } @@ -397,7 +427,7 @@ _M.updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -410,7 +440,7 @@ _M.updated = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -432,7 +462,7 @@ _M.deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -445,7 +475,7 @@ _M.deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -464,7 +494,7 @@ _M.query = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -477,7 +507,7 @@ _M.query = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -496,7 +526,7 @@ _M.online = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -523,7 +553,7 @@ _M.offline = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", -- Gitee From 62105cf36d9e7f172a0b4adee9cca405526043ab Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:18:45 +0800 Subject: [PATCH 054/165] change: update schema.user ID check rules. --- apioak/schema/user.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apioak/schema/user.lua b/apioak/schema/user.lua index d9d9300..31a1dd0 100644 --- a/apioak/schema/user.lua +++ b/apioak/schema/user.lua @@ -66,7 +66,7 @@ _M.created = { }, is_enable = { type = "number", - minimum = 0 + enum = { 0, 1 } } }, required = { "name", "password", "valid_password", "email", "is_enable" } @@ -80,7 +80,7 @@ _M.updated_password = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -109,7 +109,7 @@ _M.updated_status = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", @@ -119,7 +119,7 @@ _M.updated_status = { }, is_enable = { type = 'number', - minimum = 0 + enum = { 0, 1 } } } @@ -133,7 +133,7 @@ _M.deleted = { { type = "string", minLength = 1, - pattern = [[^[1-9]+$]] + pattern = [[^[0-9]+$]] }, { type = "number", -- Gitee From b5db57633825cae03d01c9284aa8dd0346f69783 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:21:22 +0800 Subject: [PATCH 055/165] feature: sys.admin add router online and offline APIs. --- apioak/sys/admin.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apioak/sys/admin.lua b/apioak/sys/admin.lua index 0c6139a..46afd8e 100644 --- a/apioak/sys/admin.lua +++ b/apioak/sys/admin.lua @@ -78,7 +78,7 @@ function _M.init_worker() router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.update, { method = { "PUT" } }) - router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.update, + router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.delete, { method = { "DELETE" } }) @@ -99,10 +99,10 @@ function _M.init_worker() { method = { "DELETE" } }) -- Router Publish Manager URI - router:insert_route("/apioak/admin/router/{router_id}/publish/{env}", admin.router.online, + router:insert_route("/apioak/admin/router/{router_id}/online/{env}", admin.router.online, { method = { "POST" } }) - router:insert_route("/apioak/admin/router/{router_id}/publish/{env}", admin.router.offline, + router:insert_route("/apioak/admin/router/{router_id}/offline/{env}", admin.router.offline, { method = { "DELETE" } }) -- Account Manager API -- Gitee From 5fbef3fb725201a4843dc7af6684135f5d791e80 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:22:42 +0800 Subject: [PATCH 056/165] change: update balancer module. --- apioak/sys/balancer.lua | 107 +++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/apioak/sys/balancer.lua b/apioak/sys/balancer.lua index c3cb8c3..52fccd3 100644 --- a/apioak/sys/balancer.lua +++ b/apioak/sys/balancer.lua @@ -1,91 +1,88 @@ -local pdk = require("apioak.pdk") -local balancer = require("ngx.balancer") +local pdk = require("apioak.pdk") +local balancer = require("ngx.balancer") local balancer_chash = require('resty.chash') local balancer_round = require('resty.roundrobin') local set_current_peer = balancer.set_current_peer local get_last_failure = balancer.get_last_failure -local set_timeouts = balancer.set_timeouts -local set_more_tries = balancer.set_more_tries -local ipairs = ipairs -local tostring = tostring -local tonumber = tonumber +local set_more_tries = balancer.set_more_tries +local set_timeouts = balancer.set_timeouts local _M = {} function _M.go(oak_ctx) + local router = oak_ctx.router + local upstream = router.upstream + -- default enable tries + upstream.enable_tries = 1 - local ngx_ctx = ngx.ctx - local upstream = oak_ctx.upstream or nil - if not upstream or not upstream.nodes then - pdk.log.error("[sys.balancer] upstream nodes not found") + if not upstream then + pdk.log.error("[sys.balancer] upstream undefined") pdk.response.exit(500) end - if not ngx_ctx.tries then - ngx_ctx.tries = pdk.const.TRY_DEFAULT_NUMBER - end - if not ngx_ctx.down then - ngx_ctx.down = {} + local try_nodes = upstream.try_nodes or pdk.table.new(10, 0) + + local state, code = get_last_failure() + if state == "failed" then + pdk.log.error("[sys.balancer] connection " .. try_nodes[#try_nodes], " state: ", state, " code: ", code) end - local nodes = upstream.nodes - local address - local try_number = tonumber(upstream.try_number) or pdk.const.TRY_MAX_NUMBER - local servers = {} - for _, server in ipairs(nodes) do - local key = server.ip .. ":" .. tostring(server.port) - if pdk.table.has(key, ngx_ctx.down) == false then - servers[key] = server.weight + local nodes = upstream.nodes + local servers = pdk.table.new(10, 0) + for i = 1, #nodes do + local node = pdk.string.format("%s:%s", nodes[i].ip, nodes[i].port) + if pdk.table.has(node, try_nodes) then + pdk.table.remove(nodes, i) + else + servers[node] = nodes[i].weight end end - if #servers == 0 then - pdk.log.error("[sys.balancer] node all down") + if not servers or #nodes == 0 then + pdk.log.error("[sys.balancer] upstream.nodes undefined") pdk.response.exit(500) end - if ngx_ctx.tries == try_number then - local state, code = get_last_failure() - if ngx_ctx.host and ngx_ctx.port then - local down = ngx_ctx.host .. ":" .. tostring(ngx_ctx.port) - ngx_ctx.tries = pdk.const.TRY_DEFAULT_NUMBER - pdk.table.insert(ngx_ctx.down, down) - end - pdk.log.error("[sys.balancer] node state " .. state .. " code " .. code) + upstream.tries = #nodes - 1 + if upstream.enable_tries == 0 or upstream.tries <= 1 then + upstream.tries = 0 end - local ok, err = set_timeouts(pdk.const.BALANCER_COMMECT_TIMEOUT, pdk.const.BALANCER_SEND_TIMEOUT, pdk.const.BALANCER_READ_TIMEOUT) - if not ok then - pdk.log.error("[sys.balancer] set timeouts error " .. err) - pdk.response.exit(500) + set_more_tries(upstream.tries) + + local timeout = upstream.timeout + if timeout then + local ok, err = set_timeouts(timeout.connect, timeout.send, timeout.read) + if not ok then + pdk.log.error("[sys.balancer] could not set upstream timeouts: ", err) + end end - local ok, err = set_more_tries(try_number) - if not ok then - pdk.log.error("[sys.balancer] set more tries error " .. err) - pdk.response.exit(500) + local address + if upstream.type == pdk.const.BALANCER_CHASH then + local chash_up = balancer_chash:new(servers) + address = chash_up:find(oak_ctx.matched.variable.remote_addr or pdk.const.LOCAL_IP) end - ngx_ctx.tries = ngx_ctx.tries + pdk.const.TRY_INC_NUMBER - local upstream_type = upstream.type or pdk.const.BALANCER_ROUNDROBIN - if upstream_type == pdk.const.BALANCER_CHASH then - local hash = balancer_chash:new(servers) - address = hash:find(oak_ctx.request.client_ip or pdk.const.LOCAL_IP) + if upstream.type == pdk.const.BALANCER_ROUNDROBIN then + local round_up = balancer_round:new(servers) + address = round_up:find() end - if upstream_type == pdk.const.BALANCER_ROUNDROBIN then - local round = balancer_round:new(servers) - address = round:find() + if not address then + pdk.log.error("[sys.balancer] active upstream.nodes number is 0") + pdk.response.exit(500) end - local server_info = pdk.string.split(address, ":") - ngx_ctx.host = server_info[1] - ngx_ctx.port = tonumber(server_info[2]) - local ok, err = set_current_peer(ngx_ctx.host, ngx_ctx.port) + address = pdk.string.split(address, ":") + local ok, err = set_current_peer(address[1], pdk.string.tonumber(address[2])) if not ok then - pdk.log.error("[sys.balancer] " .. address .. " error " .. err) + pdk.log.error("[sys.balancer] failed to set the current peer: ", err) pdk.response.exit(500) end + + pdk.table.insert(try_nodes, address) + oak_ctx.router.upstream.try_nodes = try_nodes end return _M -- Gitee From f1e269137efebd8bb74b44e8c0b31b5e7afb6481 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:25:20 +0800 Subject: [PATCH 057/165] feature: refactoring sys.router route loader. --- apioak/sys/router.lua | 286 +++++++++++++++++------------------------- 1 file changed, 114 insertions(+), 172 deletions(-) diff --git a/apioak/sys/router.lua b/apioak/sys/router.lua index 556dbaa..c8b1607 100644 --- a/apioak/sys/router.lua +++ b/apioak/sys/router.lua @@ -1,220 +1,162 @@ -local ngx = ngx local pdk = require("apioak.pdk") -local ipairs = ipairs +local db = require("apioak.db") +local pairs = pairs local r3route = require("resty.r3") -local timer_at = ngx.timer.at +local ngx_sleep = ngx.sleep +local ngx_timer_at = ngx.timer.at +local ngx_worker_exiting = ngx.worker.exiting -local router +local router_objects -local merge_plugins = function(service_plugins, router_plugins) - if not router_plugins then - return service_plugins - end - if service_plugins then - for plugin_key, plugin_config in ipairs(service_plugins) do - if not router_plugins[plugin_key] then - router_plugins[plugin_key] = plugin_config - end +local _M = {} + +local create_routers = function(project, router, env_router, environment) + if not env_router then + env_router = {} + env_router.request_path = "/" .. environment .. project.path .. router.request_path + env_router.request_method = router.request_method + env_router.response_type = router.response_type + env_router.response_success = router.response_success + env_router.is_mock_request = true + else + local project_upstreams = project.upstreams + if project_upstreams then + env_router.upstream = project_upstreams[environment] or {} + end + + local router_plugins = env_router.plugins + local project_plugins = project.plugins + for router_plugin_name, router_plugin_config in pairs(router_plugins) do + project_plugins[router_plugin_name] = router_plugin_config end + env_router.plugins = project_plugins or {} + + env_router.request_path = "/" .. environment .. project.path .. router.request_path end - return router_plugins -end -local _M = {} + return { + path = env_router.request_path, + method = env_router.request_method, + handler = function(params, oak_ctx) + oak_ctx.router = env_router + oak_ctx.matched = {} + oak_ctx.matched.path = params + oak_ctx.matched.query = pdk.request.query() + oak_ctx.matched.header = pdk.request.header() + oak_ctx.matched.variable = { + remote_addr = ngx.var.remote_addr + } + end + } +end local loading_routers = function() - local service_etcd_key = pdk.admin.get_service_etcd_key() - local res, code, err = pdk.etcd.query(service_etcd_key) - if not res then - pdk.log.error("failed to read \"service\" response body when try to fetch etcd") + local router_caches = {} + local res, err = db.project.query_env_all() + if err then + pdk.log.error("[sys.router] reading projects from MySQL failure, ", err) + return end - local routers = {} - for _, service in ipairs(res.nodes) do - if service.value then - local service_id = pdk.admin.get_service_id_by_etcd_key(service.key) - local service_prefix = service.value.prefix - local service_plugins = service.value.plugins - local service_upstreams = service.value.upstreams - - local envs = pdk.admin.envs - for _, env in ipairs(envs) do - local router_etcd_key = pdk.admin.get_router_etcd_key(env, service_id) - res, code, err = pdk.etcd.query(router_etcd_key) - if res and res.nodes then - for _, service_router in ipairs(res.nodes) do - if service_router.value then - local router_info = service_router.value - local router_upstream = service_upstreams[env] - local router_plugins = merge_plugins(service_plugins, router_info.plugins) - local router_abs_path = "/" .. env .. service_prefix .. router_info.path - - pdk.table.insert(routers, { - path = router_abs_path, - method = { router_info.method }, - handler = function(params, oak_ctx) - oak_ctx.router = {} - oak_ctx.router.uri = router_info.path - oak_ctx.router.method = router_info.method - oak_ctx.router.abs_uri = router_abs_path - oak_ctx.router.environment = env - oak_ctx.router.enable_cors = router_info.enable_cors - oak_ctx.router.request_params = router_info.request_params - oak_ctx.router.uri_prefix = service_prefix - - oak_ctx.backend = {} - oak_ctx.backend.uri = router_info.service_path - oak_ctx.backend.method = router_info.service_method - oak_ctx.backend.timeout = router_info.timeout - oak_ctx.backend.request_params = router_info.service_params - oak_ctx.backend.constant_params = router_info.constant_params - - oak_ctx.response = {} - oak_ctx.response.type = router_info.response_type - oak_ctx.response.success_content = router_info.response_success - oak_ctx.response.failure_content = router_info.response_fail - oak_ctx.response.error_codes = router_info.response_error_codes - - oak_ctx.upstream = router_upstream - oak_ctx.plugins = router_plugins - - oak_ctx.request = {} - oak_ctx.request.client_ip = ngx.var.remote_addr - - oak_ctx.matched = {} - oak_ctx.matched.params = params or {} - end - }) - end - end - end - end + local projects = res + for p = 1, #projects do + local project = projects[p] + res, err = db.router.query_env_by_pid(project.id) + if err then + pdk.log.error("[sys.router] reading routers from MySQL failure, ", err) + return + end + + local routers = res + for a = 1, #routers do + local router = routers[a] + + local prod_env_handler = create_routers(project, router, router.env_prod_config, pdk.const.ENVIRONMENT_PROD) + pdk.table.insert(router_caches, prod_env_handler) + + local beta_env_handler = create_routers(project, router, router.env_beta_config, pdk.const.ENVIRONMENT_BETA) + pdk.table.insert(router_caches, beta_env_handler) + + local test_env_handler = create_routers(project, router, router.env_test_config, pdk.const.ENVIRONMENT_TEST) + pdk.table.insert(router_caches, test_env_handler) end end - router = r3route.new(routers) - router:compile() + + router_objects = r3route.new(router_caches) + router_objects:compile() end function _M.init_worker() - timer_at(0, function (premature) + ngx_timer_at(0, function (premature) if premature then return end - local service_etcd_key = pdk.admin.get_service_etcd_key() - local res, code, err = pdk.etcd.query(service_etcd_key) - if not res then - pdk.log.error("failed to read \"service\" response body when try to fetch etcd") + while not ngx_worker_exiting() do + loading_routers() + ngx_sleep(30) end - loading_routers() - end) end -local function format_params(params, request_params, matched) - - if string.upper(request_params.position) == "QUERY" then - if pdk.request.query(request_params.name) then - params[request_params.service_name] = pdk.request.query(request_params.name) +local checked_request_params = function(rule, params) + local query_val = params[rule.request_param_name] + if rule.required == 1 then + if not query_val then + return nil, "request param \"[" .. rule.request_param_position .. "." .. rule.request_param_name .. "]\" undefined" end - elseif string.upper(request_params.position) == "PATH" then - if matched.params[request_params.name] then - params[request_params.service_name] = matched.params[request_params.name] - end - elseif string.upper(request_params.position) == "HEADER" then - if pdk.request.header(request_params.name) then - params[request_params.service_name] = pdk.request.header(request_params.name) + else + if not query_val then + query_val = rule.request_param_default_val end end - return params, nil + return query_val, nil end -local function get_backend_uri(backend, matched, constants) - - local backend_uri = "" - if not backend then - return nil, "\"backend\" is null" - end - - if backend.request_params then +function _M.init_request(oak_ctx) + local router = oak_ctx.router + local params = router.backend_params + for i = 1, #params do + local param = params[i] + local backend_params_position = pdk.string.lower(param.position) + local request_param_position = pdk.string.lower(param.request_param_position) - local path_params = {} - local query_params = {} - local header_params = {} - local tmp_query_params = {} + if backend_params_position == request_param_position then + local request_params = oak_ctx.matched[request_param_position] - for _, request_params in pairs(backend.request_params) do - if string.upper(request_params.service_position) == "QUERY" then - tmp_query_params = format_params(tmp_query_params, request_params, matched) - elseif string.upper(request_params.service_position) == "PATH" then - path_params = format_params(path_params, request_params, matched) - elseif string.upper(request_params.service_position) == "HEADER" then - header_params = format_params(header_params, request_params, matched) + local request_value, err = checked_request_params(param, request_params) + if err then + pdk.response.exit(401, { err_message = err }) end - end - if constants then - for _, constant in pairs(constants) do - if constant.name and constant.value and constant.position then - if string.upper(constant.position) == "QUERY" then - tmp_query_params[constant.name] = constant.value - elseif string.upper(constant.position) == "PATH" then - path_params[constant.name] = constant.value - elseif string.upper(constant.position) == "HEADER" then - header_params[constant.name] = constant.value - end - end + if param.name ~= param.request_param_name then + request_params[param.request_param_name] = nil + request_params[param.name] = request_value end - end - if tmp_query_params then - for path_name, path_value in pairs(tmp_query_params) do - table.insert(query_params, path_name .. "=" .. path_value) - end - end + oak_ctx.matched[request_param_position] = request_params + else + local request_params = oak_ctx.matched[request_param_position] + local backend_params = oak_ctx.matched[backend_params_position] - backend_uri = backend.uri - if header_params then - for header_name, header_value in pairs(header_params) do - ngx.req.set_header(header_name, header_value) + local request_value, err = checked_request_params(param, request_params) + if err then + pdk.response.exit(401, { err_message = err }) end - end - if path_params then - for path_name, path_value in pairs(path_params) do - backend_uri = string.gsub(backend_uri, "{" .. path_name .. "}", path_value) - end - end - if query_params then - backend_uri = backend_uri .. "?" .. table.concat(query_params, "&") - end - end - return backend_uri, nil -end - -function _M.analysis_uri(oak_ctx) - if (not oak_ctx.backend) or (not oak_ctx.backend.uri) then - pdk.log.error("backend uri service is empty") - return - end + request_params[param.request_param_name] = nil + backend_params[param.name] = request_value - local uri, err = get_backend_uri(oak_ctx.backend, oak_ctx.matched, oak_ctx.backend.constant_params) - if not uri then - pdk.log.error(err) - return + oak_ctx.matched[request_param_position] = request_params + oak_ctx.matched[backend_params_position] = backend_params + end end - - oak_ctx.backend.uri = uri - ngx.var.upstream_uri = uri end function _M.get() - if not router then - loading_routers() - end - return router + return router_objects end return _M -- Gitee From 75c44afa1265a31bde19b3e2eac2085b2fb4d6e7 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 9 Mar 2020 22:28:16 +0800 Subject: [PATCH 058/165] feature: update route rewrite rules. --- apioak/apioak.lua | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/apioak/apioak.lua b/apioak/apioak.lua index 5059f2c..1bfc4a7 100644 --- a/apioak/apioak.lua +++ b/apioak/apioak.lua @@ -1,5 +1,6 @@ local ngx = ngx local ipairs = ipairs +local pairs = pairs local pdk = require("apioak.pdk") local sys = require("apioak.sys") @@ -37,6 +38,7 @@ function APIOAK.init_worker() end function APIOAK.http_access() + local ngx_ctx = ngx.ctx local oak_ctx = ngx_ctx.oak_ctx if not oak_ctx then @@ -44,24 +46,46 @@ function APIOAK.http_access() ngx_ctx.oak_ctx = oak_ctx end - local env = pdk.request.header("APIOAK-ENV") + local env = pdk.request.header(pdk.const.REQUEST_API_ENV_KEY) if env then - env = pdk.string.lower(env) + env = pdk.string.upper(env) else - env = pdk.admin.ENV_MASTER + env = pdk.const.ENVIRONMENT_PROD end - local routers = sys.router.get() + local routers = sys.router.get() local match_ok = routers:dispatch("/" .. env .. ngx.var.uri, ngx.req.get_method(), oak_ctx) if not match_ok then - pdk.response.exit(404, { err_message = "\"URI\" not found" }) + pdk.response.exit(404, { err_message = "\"URI\" Undefined" }) + end + + if oak_ctx.router.is_mock_request then + pdk.response.exit(200, oak_ctx.router.response_success) + end + + sys.router.init_request(oak_ctx) + + local router = oak_ctx.router + local matched = oak_ctx.matched + + local upstream_uri = router.backend_path + for path_key, path_val in pairs(matched.path) do + upstream_uri = pdk.string.replace(upstream_uri, "{" .. path_key .. "}", path_val) end - if env == pdk.admin.ENV_MASTER then - pdk.response.exit(200, oak_ctx.response.success_content) + for header_key, header_val in pairs(matched.header) do + pdk.request.add_header(header_key, header_val) + end + + local query_args = {} + for query_key, query_val in pairs(matched.query) do + pdk.table.insert(query_args, query_key .. "=" .. query_val) + end + if #query_args > 0 then + upstream_uri = upstream_uri .. "?" .. pdk.table.concat(query_args, "&") end - sys.router.analysis_uri(oak_ctx) + ngx.var.upstream_uri = upstream_uri run_plugin("http_access", oak_ctx) end -- Gitee From da7ce77f5ef5a27a71617c29715fd6f293b42724 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 10 Mar 2020 13:16:57 +0800 Subject: [PATCH 059/165] bugfix: add update upstream project id. --- apioak/admin/project.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apioak/admin/project.lua b/apioak/admin/project.lua index 6ddc7b6..faad1d4 100644 --- a/apioak/admin/project.lua +++ b/apioak/admin/project.lua @@ -80,7 +80,8 @@ function project_controller.update(params) end for i = 1, #body.upstreams do - local upstream = body.upstreams[i] + local upstream = body.upstreams[i] + upstream.project_id = params.project_id res, err = db.upstream.update(upstream.id, upstream) if err then pdk.response.exit(500, { err_message = err }) -- Gitee From 5ebf49cbe00ebe0c4876d46bd50932f8a4250c90 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 10 Mar 2020 13:17:39 +0800 Subject: [PATCH 060/165] change: remove db.router timeout. --- apioak/db/router.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apioak/db/router.lua b/apioak/db/router.lua index 86c1e18..1b8fb59 100644 --- a/apioak/db/router.lua +++ b/apioak/db/router.lua @@ -44,7 +44,7 @@ end function _M.query(router_id) local sql = "SELECT id, name, enable_cors, description, request_path, request_method, request_params, " .. - "backend_path, backend_method, backend_timeout, backend_params, constant_params, response_type, " .. + "backend_path, backend_method, backend_params, constant_params, response_type, " .. "response_success, response_failure, response_codes, response_schema, env_prod_config, env_beta_config, " .. "env_test_config, project_id FROM %s WHERE id = %s"; sql = pdk.string.format(sql, table_name, router_id) @@ -88,13 +88,13 @@ end function _M.created(params) local sql = "INSERT INTO %s (name, enable_cors, description, request_path, request_method, request_params, " .. - "backend_path, backend_method, backend_timeout, backend_params, constant_params, response_type, " .. + "backend_path, backend_method, backend_params, constant_params, response_type, " .. "response_success, response_failure, response_codes, response_schema, project_id, user_id) " .. "VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', " .. "'%s', '%s', '%s')" sql = pdk.string.format(sql, table_name, params.name, params.enable_cors, params.description, params.request_path, params.request_method, pdk.json.encode(params.request_params), params.backend_path, params.backend_method, - params.backend_timeout, pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), + pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), params.response_type, params.response_success, params.response_failure, pdk.json.encode(params.response_codes), pdk.json.encode(params.response_schema), params.project_id, params.user_id) local res, err = pdk.mysql.execute(sql) @@ -108,12 +108,12 @@ end function _M.updated(router_id, params) local sql = "UPDATE %s SET name = '%s', enable_cors = '%s', description = '%s', request_path = '%s', " .. "request_method = '%s', request_params = '%s', backend_path = '%s', backend_method = '%s', " .. - "backend_timeout = '%s', backend_params = '%s', constant_params = '%s', response_type = '%s', " .. + "backend_params = '%s', constant_params = '%s', response_type = '%s', " .. "response_success = '%s', response_failure = '%s', response_codes = '%s', response_schema = '%s' ".. "WHERE id = '%s'" sql = pdk.string.format(sql, table_name, params.name, params.enable_cors, params.description, params.request_path, params.request_method, pdk.json.encode(params.request_params), params.backend_path, params.backend_method, - params.backend_timeout, pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), + pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), params.response_type, params.response_success, params.response_failure, pdk.json.encode(params.response_codes), pdk.json.encode(params.response_schema), router_id) local res, err = pdk.mysql.execute(sql) -- Gitee From cfbea374201e96571b4e84fbbd9181972a4a9756 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 10 Mar 2020 13:18:15 +0800 Subject: [PATCH 061/165] change: add db.upstream timeouts. --- apioak/db/upstream.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apioak/db/upstream.lua b/apioak/db/upstream.lua index 3285077..66ee7fc 100644 --- a/apioak/db/upstream.lua +++ b/apioak/db/upstream.lua @@ -7,8 +7,8 @@ local _M = {} _M.table_name = table_name function _M.create(params) - local sql = pdk.string.format("INSERT INTO %s (env, host, type, project_id, nodes) VALUES ('%s', '%s', '%s', '%s', '%s')", - table_name, params.env, params.host, params.type, params.project_id, pdk.json.encode(params.nodes)) + local sql = pdk.string.format("INSERT INTO %s (env, host, type, project_id, enable_retries, timeouts, nodes) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s')", + table_name, params.env, params.host, params.type, params.project_id, params.enable_retries, pdk.json.encode(params.timeouts), pdk.json.encode(params.nodes)) local res, err = pdk.mysql.execute(sql) if err then @@ -18,8 +18,8 @@ function _M.create(params) end function _M.update(upstream_id, params) - local sql = pdk.string.format("UPDATE %s SET env = '%s', host = '%s', type = '%s', project_id = '%s', nodes = '%s' WHERE id = %s", - table_name, params.env, params.host, params.type, params.project_id, pdk.json.encode(params.nodes), upstream_id) + local sql = pdk.string.format("UPDATE %s SET env = '%s', host = '%s', type = '%s', project_id = '%s', enable_retries = '%s', timeouts = '%s', nodes = '%s' WHERE id = %s", + table_name, params.env, params.host, params.type, params.project_id, params.enable_retries, pdk.json.encode(params.timeouts), pdk.json.encode(params.nodes), upstream_id) local res, err = pdk.mysql.execute(sql) if err then @@ -47,7 +47,8 @@ function _M.query_by_pid(project_id) end for i = 1, #res do - res[i].nodes = pdk.json.decode(res[i].nodes) + res[i].nodes = pdk.json.decode(res[i].nodes) + res[i].timeouts = pdk.json.decode(res[i].timeouts) end return res, nil -- Gitee From 882af741dc9dc04d5689d08335a4d62ce330a1de Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 10 Mar 2020 13:19:02 +0800 Subject: [PATCH 062/165] change: add schema.project timeouts valid. --- apioak/schema/project.lua | 53 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/apioak/schema/project.lua b/apioak/schema/project.lua index b1fe638..2406750 100644 --- a/apioak/schema/project.lua +++ b/apioak/schema/project.lua @@ -1,4 +1,3 @@ - local _M = {} _M.list = { @@ -148,6 +147,30 @@ _M.updated = { type = "string", enum = { "PROD", "BETA", "TEST" } }, + enable_retries = { + type = "number", + enum = { 0, 1 } + }, + timeouts = { + type = "object", + properties = { + connect = { + type = "number", + minimum = 0, + maximum = 60000, + }, + send = { + type = "number", + minimum = 0, + maximum = 60000, + }, + read = { + type = "number", + minimum = 0, + maximum = 60000, + } + } + }, nodes = { type = "array", minItems = 1, @@ -174,7 +197,7 @@ _M.updated = { } } }, - required = { "id", "host", "type", "env", "nodes" } + required = { "id", "host", "type", "env", "enable_retries", "timeouts", "nodes" } }, } }, @@ -232,6 +255,30 @@ _M.created = { type = "string", enum = { "PROD", "BETA", "TEST" } }, + enable_retries = { + type = "number", + enum = { 0, 1 } + }, + timeouts = { + type = "object", + properties = { + connect = { + type = "number", + minimum = 0, + maximum = 60000, + }, + send = { + type = "number", + minimum = 0, + maximum = 60000, + }, + read = { + type = "number", + minimum = 0, + maximum = 60000, + } + } + }, nodes = { type = "array", minItems = 1, @@ -258,7 +305,7 @@ _M.created = { } } }, - required = { "host", "type", "env", "nodes" } + required = { "host", "type", "env", "enable_retries", "timeouts", "nodes" } }, } }, -- Gitee From 428b6155c99e6cd7bd6633790708a74b3519f035 Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 10 Mar 2020 13:19:43 +0800 Subject: [PATCH 063/165] change: remove schema.router timeout valid. --- apioak/schema/router.lua | 12 ++---------- apioak/sys/balancer.lua | 12 +++++------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/apioak/schema/router.lua b/apioak/schema/router.lua index 207582e..147b9d0 100644 --- a/apioak/schema/router.lua +++ b/apioak/schema/router.lua @@ -91,10 +91,6 @@ _M.created = { type = "string", enum = { "GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE", "TRACE", "CONNECT" } }, - backend_timeout = { - type = "number", - minimum = 0 - }, backend_params = { type = "array", uniqueItems = true, @@ -226,7 +222,7 @@ _M.created = { } }, required = { "name", "enable_cors", "description", "request_path", "request_method", "request_params", "backend_path", - "backend_method", "backend_timeout", "backend_params", "constant_params", "response_type", + "backend_method", "backend_params", "constant_params", "response_type", "response_success", "response_failure", "response_codes", "response_schema", "project_id" } } @@ -302,10 +298,6 @@ _M.updated = { type = "string", enum = { "GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE", "TRACE", "CONNECT" } }, - backend_timeout = { - type = "number", - minimum = 0 - }, backend_params = { type = "array", uniqueItems = true, @@ -450,7 +442,7 @@ _M.updated = { } }, required = { "name", "enable_cors", "description", "request_path", "request_method", "request_params", "backend_path", - "backend_method", "backend_timeout", "backend_params", "constant_params", "response_type", + "backend_method", "backend_params", "constant_params", "response_type", "response_success", "response_failure", "response_codes", "response_schema", "project_id", "router_id" } } diff --git a/apioak/sys/balancer.lua b/apioak/sys/balancer.lua index 52fccd3..6ab9dec 100644 --- a/apioak/sys/balancer.lua +++ b/apioak/sys/balancer.lua @@ -12,8 +12,6 @@ local _M = {} function _M.go(oak_ctx) local router = oak_ctx.router local upstream = router.upstream - -- default enable tries - upstream.enable_tries = 1 if not upstream then pdk.log.error("[sys.balancer] upstream undefined") @@ -43,14 +41,14 @@ function _M.go(oak_ctx) pdk.response.exit(500) end - upstream.tries = #nodes - 1 - if upstream.enable_tries == 0 or upstream.tries <= 1 then - upstream.tries = 0 + upstream.count_retries = #nodes - 1 + if upstream.enable_retries == 0 or upstream.count_retries <= 1 then + upstream.count_retries = 0 end - set_more_tries(upstream.tries) + set_more_tries(upstream.count_retries) - local timeout = upstream.timeout + local timeout = upstream.timeouts if timeout then local ok, err = set_timeouts(timeout.connect, timeout.send, timeout.read) if not ok then -- Gitee From 814623fb8bab2617d6a561af24e3844ce753adeb Mon Sep 17 00:00:00 2001 From: Janko Date: Tue, 10 Mar 2020 13:25:44 +0800 Subject: [PATCH 064/165] change: upstream table add timeouts field. --- conf/apioak.sql | 194 ++++++++++++++++++++++++++---------------------- 1 file changed, 105 insertions(+), 89 deletions(-) diff --git a/conf/apioak.sql b/conf/apioak.sql index c0c55fc..1fbaacc 100644 --- a/conf/apioak.sql +++ b/conf/apioak.sql @@ -1,145 +1,161 @@ DROP TABLE IF EXISTS `oak_groups`; -CREATE TABLE `oak_groups` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(50) DEFAULT NULL COMMENT '组名称', +CREATE TABLE `oak_groups` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) DEFAULT NULL COMMENT '组名称', `description` text COMMENT '组描述', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `UNIQ_NAME` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目组表'; +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='项目组表'; DROP TABLE IF EXISTS `oak_plugins`; -CREATE TABLE `oak_plugins` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(20) DEFAULT NULL COMMENT '插件名称', - `type` varchar(20) DEFAULT NULL COMMENT '插件类型', +CREATE TABLE `oak_plugins` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(20) DEFAULT NULL COMMENT '插件名称', + `type` varchar(20) DEFAULT NULL COMMENT '插件类型', `description` text COMMENT '插件描述', - `config` json DEFAULT NULL COMMENT '插件配置', - `res_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '资源ID', - `res_type` varchar(20) DEFAULT NULL COMMENT '资源类型(PROJECT/ROUTER)', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `config` json DEFAULT NULL COMMENT '插件配置', + `res_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '资源ID', + `res_type` varchar(20) DEFAULT NULL COMMENT '资源类型(PROJECT/ROUTER)', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_RESOURCES` (`name`,`res_id`,`res_type`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='插件表'; + UNIQUE KEY `UNIQ_RESOURCES` (`name`, `res_id`, `res_type`) USING BTREE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='插件表'; DROP TABLE IF EXISTS `oak_projects`; -CREATE TABLE `oak_projects` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(50) DEFAULT NULL COMMENT '项目名称', +CREATE TABLE `oak_projects` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) DEFAULT NULL COMMENT '项目名称', `description` text COMMENT '项目描述', - `path` varchar(50) DEFAULT NULL COMMENT '项目前缀', - `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', - `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目组ID', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `path` varchar(50) DEFAULT NULL COMMENT '项目前缀', + `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目组ID', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `UNIQ_PATH` (`path`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目表'; +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='项目表'; DROP TABLE IF EXISTS `oak_roles`; -CREATE TABLE `oak_roles` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '组ID', - `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', - `is_admin` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '组管理员', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', +CREATE TABLE `oak_roles` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '组ID', + `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `is_admin` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '组管理员', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_GROUP_USER` (`group_id`,`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; + UNIQUE KEY `UNIQ_GROUP_USER` (`group_id`, `user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='角色表'; DROP TABLE IF EXISTS `oak_routers`; -CREATE TABLE `oak_routers` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(50) DEFAULT NULL COMMENT '接口名称', - `enable_cors` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '开启跨域', - `description` text COMMENT '接口描述', - `request_path` varchar(100) DEFAULT NULL COMMENT '请求路径', - `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式', - `request_params` json DEFAULT NULL COMMENT '请求参数', - `backend_path` varchar(100) DEFAULT NULL COMMENT '后端请求路径', - `backend_method` varchar(10) DEFAULT NULL COMMENT '后端请求方式', - `backend_timeout` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '后端超时时间', - `backend_params` json DEFAULT NULL COMMENT '后端参数', - `constant_params` json DEFAULT NULL COMMENT '常量参数', - `response_type` varchar(50) DEFAULT NULL COMMENT '响应类型', +CREATE TABLE `oak_routers` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) DEFAULT NULL COMMENT '接口名称', + `enable_cors` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '开启跨域', + `description` text COMMENT '接口描述', + `request_path` varchar(100) DEFAULT NULL COMMENT '请求路径', + `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式', + `request_params` json DEFAULT NULL COMMENT '请求参数', + `backend_path` varchar(100) DEFAULT NULL COMMENT '后端请求路径', + `backend_method` varchar(10) DEFAULT NULL COMMENT '后端请求方式', + `backend_params` json DEFAULT NULL COMMENT '后端参数', + `constant_params` json DEFAULT NULL COMMENT '常量参数', + `response_type` varchar(50) DEFAULT NULL COMMENT '响应类型', `response_success` text COMMENT '响应成功消息', `response_failure` text COMMENT '响应失败消息', - `response_codes` json DEFAULT NULL COMMENT '响应错误码', - `response_schema` json DEFAULT NULL COMMENT '响应描述', - `env_prod_config` json DEFAULT NULL COMMENT '生产环境配置', - `env_beta_config` json DEFAULT NULL COMMENT '预发环境配置', - `env_test_config` json DEFAULT NULL COMMENT '测试环境配置', - `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', - `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `response_codes` json DEFAULT NULL COMMENT '响应错误码', + `response_schema` json DEFAULT NULL COMMENT '响应描述', + `env_prod_config` json DEFAULT NULL COMMENT '生产环境配置', + `env_beta_config` json DEFAULT NULL COMMENT '预发环境配置', + `env_test_config` json DEFAULT NULL COMMENT '测试环境配置', + `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', + `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `UNIQ_REQUEST_PATH` (`request_path`), UNIQUE KEY `UNIQ_NAME` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='路由表'; +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='路由表'; DROP TABLE IF EXISTS `oak_tokens`; -CREATE TABLE `oak_tokens` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `token` char(32) DEFAULT NULL COMMENT '登录令牌', - `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID', - `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `expired_at` timestamp NULL DEFAULT NULL COMMENT '超时时间', +CREATE TABLE `oak_tokens` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `token` char(32) DEFAULT NULL COMMENT '登录令牌', + `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID', + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `expired_at` timestamp NULL DEFAULT NULL COMMENT '超时时间', PRIMARY KEY (`id`), UNIQUE KEY `UNIQ_USER` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='令牌表'; +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='令牌表'; DROP TABLE IF EXISTS `oak_upstreams`; -CREATE TABLE `oak_upstreams` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `env` varchar(10) DEFAULT NULL COMMENT '发布环境', - `host` varchar(50) DEFAULT NULL COMMENT '主机地址', - `retries` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '重试次数', - `type` varchar(10) DEFAULT NULL COMMENT '负载均衡算法', - `nodes` json DEFAULT NULL COMMENT '服务节点', - `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', +CREATE TABLE `oak_upstreams` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `env` varchar(10) DEFAULT NULL COMMENT '发布环境', + `host` varchar(50) DEFAULT NULL COMMENT '主机地址', + `enable_retries` tinyint(3) unsigned DEFAULT '0' COMMENT '开启重试', + `type` varchar(10) DEFAULT NULL COMMENT '负载均衡算法', + `timeouts` json DEFAULT NULL COMMENT '超时时间', + `nodes` json DEFAULT NULL COMMENT '服务节点', + `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_ENV_PROJECT` (`env`,`project_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务节点表'; + UNIQUE KEY `UNIQ_ENV_PROJECT` (`env`, `project_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='服务节点表'; DROP TABLE IF EXISTS `oak_users`; -CREATE TABLE `oak_users` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(50) DEFAULT NULL COMMENT '用户名', - `password` char(32) DEFAULT NULL COMMENT '密码', - `email` varchar(50) DEFAULT NULL COMMENT '邮箱', - `is_owner` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '超级管理员', - `is_enable` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否启用', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', +CREATE TABLE `oak_users` +( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) DEFAULT NULL COMMENT '用户名', + `password` char(32) DEFAULT NULL COMMENT '密码', + `email` varchar(50) DEFAULT NULL COMMENT '邮箱', + `is_owner` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '超级管理员', + `is_enable` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否启用', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `UNIQ_EMAIL` (`email`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='用户表'; -- Gitee From 42f80449ccda00c1454225ddf83f3d95c686a037 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 22:54:00 +0800 Subject: [PATCH 065/165] change: remove group auth functions. --- apioak/admin/controller.lua | 17 +++-------------- conf/apioak.yaml | 11 +++-------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/apioak/admin/controller.lua b/apioak/admin/controller.lua index 3dc372c..eef808b 100644 --- a/apioak/admin/controller.lua +++ b/apioak/admin/controller.lua @@ -97,28 +97,17 @@ local controller = function(name) end end - cls.group_authenticate = function(group_id, user_id) - local res, err = db.role.query(group_id, user_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - if #res == 0 then - pdk.response.exit(401, { err_message = "no permission to operate on this group" }) - end - return res[1] - end - cls.project_authenticate = function(project_id, user_id) - local res, err = db.project.query(project_id) + local res, err = db.role.query(project_id, user_id) if err then pdk.response.exit(500, { err_message = err }) end if #res == 0 then - pdk.response.exit(500, { err_message = "project: " .. project_id .. " not exists" }) + pdk.response.exit(401, { err_message = "no permission to operate on this project" }) end - return cls.group_authenticate(res[1].group_id, user_id) + return res[1] end cls.router_authenticate = function(router_id, user_id) diff --git a/conf/apioak.yaml b/conf/apioak.yaml index fc174be..cd1a904 100644 --- a/conf/apioak.yaml +++ b/conf/apioak.yaml @@ -1,14 +1,9 @@ -etcd: - host: "http://127.0.0.1:2379" # etcd address - prefix: "/apioak" # apioak config prefix - timeout: 3 # 3 seconds - mysql: host: 127.0.0.1 - port: 3306, - database: apioak, + port: 3306 + database: apioak user: root - password: 123456 + password: 123000 timeout: 1000 # millisecond pool_size: 100 max_idle_timeout: 10000 # millisecond -- Gitee From 25df5ff703562e076c89f7dd6734c3ca1886f934 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 22:54:27 +0800 Subject: [PATCH 066/165] change: remove admin.group controller. --- apioak/admin/group.lua | 209 ----------------------------------------- 1 file changed, 209 deletions(-) delete mode 100644 apioak/admin/group.lua diff --git a/apioak/admin/group.lua b/apioak/admin/group.lua deleted file mode 100644 index 07ecb34..0000000 --- a/apioak/admin/group.lua +++ /dev/null @@ -1,209 +0,0 @@ -local db = require("apioak.db") -local pdk = require("apioak.pdk") -local schema = require("apioak.schema") -local controller = require("apioak.admin.controller") - -local group_controller = controller.new("group") - -function group_controller.list() - - group_controller.user_authenticate() - - local res, err - if group_controller.is_owner then - res, err = db.group.all(true) - else - res, err = db.group.query_by_uid(group_controller.uid) - end - - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK", groups = res }) -end - -function group_controller.query(params) - - group_controller.check_schema(schema.group.query, params) - - group_controller.user_authenticate() - - local res, err = db.group.query(params.group_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - if #res > 0 then - res = res[1] - end - - pdk.response.exit(200, { err_message = "OK", group = res }) -end - -function group_controller.created() - - group_controller.user_authenticate() - - if not group_controller.is_owner then - pdk.response.exit(401, { err_message = "no permission to create group" }) - end - - local body = group_controller.get_body() - group_controller.check_schema(schema.group.created, body) - - local _, err = db.group.create(body) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - - -function group_controller.updated(params) - - group_controller.user_authenticate() - - if not group_controller.is_owner then - pdk.response.exit(401, { err_message = "no permission to update group" }) - end - - local body = group_controller.get_body() - body.group_id = params.group_id - - group_controller.check_schema(schema.group.updated, body) - - local _, err = db.group.update(body.group_id, body) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function group_controller.deleted(params) - - group_controller.user_authenticate() - - if not group_controller.is_owner then - pdk.response.exit(401, { err_message = "no permission to update group" }) - end - - group_controller.check_schema(schema.group.deleted, params) - - local res, err = db.project.query_by_gid(params.group_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - if #res > 0 then - pdk.response.exit(500, { err_message = "projects in group were not deleted" }) - end - - res, err = db.role.delete_by_gid(params.group_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - res, err = db.group.delete(params.group_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function group_controller.user_list(params) - - group_controller.user_authenticate() - - if not group_controller.is_owner then - group_controller.group_authenticate(params.group_id, group_controller.uid) - end - - group_controller.check_schema(schema.group.user_list, params) - - local res, err = db.user.query_by_gid(params.group_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK", users = res }) -end - -function group_controller.user_create(params) - - group_controller.user_authenticate() - - local body = group_controller.get_body() - body.group_id = params.group_id - - group_controller.check_schema(schema.group.user_created, body) - - if not group_controller.is_owner then - local user_group = group_controller.group_authenticate(params.group_id, group_controller.uid) - if user_group.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create group member" }) - end - end - - local _, err = db.role.create(body.group_id, body.user_id, body.is_admin) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function group_controller.user_update(params) - - group_controller.user_authenticate() - - local body = group_controller.get_body() - body.user_id = params.user_id - body.group_id = params.group_id - - group_controller.check_schema(schema.group.user_updated, body) - - if not group_controller.is_owner then - local user_group = group_controller.group_authenticate(params.group_id, group_controller.uid) - if user_group.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to update group member" }) - end - end - - local _, err = db.role.update(body.group_id, body.user_id, body.is_admin) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function group_controller.user_delete(params) - - group_controller.user_authenticate() - - group_controller.check_schema(schema.group.user_deleted, params) - - if not group_controller.is_owner then - local user_group = group_controller.group_authenticate(params.group_id, group_controller.uid) - if user_group.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to delete group member" }) - end - end - - if params.user_id == group_controller.uid then - pdk.response.exit(401, { err_message = "no permission to delete this member" }) - end - - local _, err = db.role.delete(params.group_id, params.user_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -return group_controller -- Gitee From 2251c0ac814479b3095691f3543c912402b41ad9 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 22:55:30 +0800 Subject: [PATCH 067/165] change: update admin.plugin return code. --- apioak/admin/plugin.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apioak/admin/plugin.lua b/apioak/admin/plugin.lua index d1592e0..e951035 100644 --- a/apioak/admin/plugin.lua +++ b/apioak/admin/plugin.lua @@ -31,10 +31,7 @@ function plugin_controller.project_list(params) plugin_controller.user_authenticate() if not plugin_controller.is_owner then - local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) - if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create group member" }) - end + plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) end local res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id) @@ -57,7 +54,7 @@ function plugin_controller.project_created(params) if not plugin_controller.is_owner then local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create group member" }) + pdk.response.exit(401, { err_message = "no permission to create project plugin" }) end end @@ -82,7 +79,7 @@ function plugin_controller.project_updated(params) if not plugin_controller.is_owner then local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create group member" }) + pdk.response.exit(401, { err_message = "no permission to update project plugin" }) end end @@ -103,7 +100,7 @@ function plugin_controller.project_deleted(params) if not plugin_controller.is_owner then local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create group member" }) + pdk.response.exit(401, { err_message = "no permission to delete project plugin" }) end end -- Gitee From 8012f0978f000e93d8d80f2800f90f74c322e263 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 22:56:14 +0800 Subject: [PATCH 068/165] change: update admin.project functions. --- apioak/admin/project.lua | 166 ++++++++++++++++++++++++++++++++------- 1 file changed, 136 insertions(+), 30 deletions(-) diff --git a/apioak/admin/project.lua b/apioak/admin/project.lua index faad1d4..270fb19 100644 --- a/apioak/admin/project.lua +++ b/apioak/admin/project.lua @@ -5,17 +5,17 @@ local controller = require("apioak.admin.controller") local project_controller = controller.new("project") -function project_controller.list(params) - - project_controller.check_schema(schema.project.list, params) +function project_controller.list() project_controller.user_authenticate() - if not project_controller.is_owner then - project_controller.group_authenticate(params.group_id, project_controller.uid) + local res, err + if project_controller.is_owner then + res, err = db.project.all(true) + else + res, err = db.project.query_by_uid(project_controller.uid) end - local res, err = db.project.query_by_gid(params.group_id) if err then pdk.response.exit(500, { err_message = err }) end @@ -23,31 +23,23 @@ function project_controller.list(params) pdk.response.exit(200, { err_message = "OK", projects = res }) end -function project_controller.create(params) + +function project_controller.created() local body = project_controller.get_body() - body.group_id = params.group_id project_controller.check_schema(schema.project.created, body) project_controller.user_authenticate() - body.user_id = project_controller.uid - - if not project_controller.is_owner then - local user_group = project_controller.group_authenticate(params.group_id, project_controller.uid) - if user_group.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create group member" }) - end - end - local res, err = db.project.create(body) + local res, err = db.project.created(body) if err then pdk.response.exit(500, { err_message = err }) end local project_id = res.insert_id for i = 1, #body.upstreams do - local upstream = body.upstreams[i] + local upstream = body.upstreams[i] upstream.project_id = project_id res, err = db.upstream.create(upstream) if err then @@ -55,26 +47,32 @@ function project_controller.create(params) end end + res, err = db.role.create(project_id, project_controller.uid, 1) + if err then + pdk.response.exit(500, { err_message = err }) + end + pdk.response.exit(200, { err_message = "OK" }) end -function project_controller.update(params) - local body = project_controller.get_body() - body.id = params.project_id +function project_controller.updated(params) + + local body = project_controller.get_body() + body.project_id = params.project_id project_controller.check_schema(schema.project.updated, body) project_controller.user_authenticate() if not project_controller.is_owner then - local user_group = project_controller.group_authenticate(params.group_id, project_controller.uid) - if user_group.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create group member" }) + local role = project_controller.project_authenticate(params.project_id, project_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to update project" }) end end - local res, err = db.project.update(params.project_id, body) + local res, err = db.project.updated(params.project_id, body) if err then pdk.response.exit(500, { err_message = err }) end @@ -91,6 +89,7 @@ function project_controller.update(params) pdk.response.exit(200, { err_message = "OK" }) end + function project_controller.query(params) project_controller.check_schema(schema.project.query, params) @@ -120,16 +119,17 @@ function project_controller.query(params) pdk.response.exit(200, { err_message = "OK", project = project }) end -function project_controller.delete(params) + +function project_controller.deleted(params) project_controller.check_schema(schema.project.deleted, params) project_controller.user_authenticate() if not project_controller.is_owner then - local user_group = project_controller.group_authenticate(params.group_id, project_controller.uid) - if user_group.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create group member" }) + local role = project_controller.project_authenticate(params.project_id, project_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to delete project" }) end end @@ -139,7 +139,7 @@ function project_controller.delete(params) end if #res > 0 then - pdk.response.exit(500, { err_message = "routers in project were not deleted" }) + pdk.response.exit(401, { err_message = "routers in project were not deleted" }) end res, err = db.plugin.delete_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id) @@ -152,6 +152,11 @@ function project_controller.delete(params) pdk.response.exit(500, { err_message = err }) end + res, err = db.role.delete_by_pid(params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + res, err = db.project.delete(params.project_id) if err then pdk.response.exit(500, { err_message = err }) @@ -160,4 +165,105 @@ function project_controller.delete(params) pdk.response.exit(200, { err_message = "OK" }) end + +function project_controller.members(params) + + project_controller.check_schema(schema.project.members, params) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + project_controller.project_authenticate(params.project_id, project_controller.uid) + end + + local res, err = db.user.query_by_pid(params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", users = res }) +end + + +function project_controller.member_created(params) + + local body = project_controller.get_body() + body.project_id = params.project_id + + project_controller.check_schema(schema.project.member_created, body) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + local role = project_controller.project_authenticate(params.project_id, project_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to create project member" }) + end + end + + local _, err = db.role.create(body.project_id, body.user_id, body.is_admin) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + + +function project_controller.member_deleted(params) + + project_controller.check_schema(schema.project.member_deleted, params) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + local user_group = project_controller.project_authenticate(params.project_id, project_controller.uid) + if user_group.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to delete group member" }) + end + end + + if pdk.string.tonumber(params.user_id) == project_controller.uid then + pdk.response.exit(401, { err_message = "no permission to delete this member" }) + end + + local _, err = db.role.delete(params.project_id, params.user_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + + +function project_controller.member_updated(params) + + local body = project_controller.get_body() + body.user_id = params.user_id + body.project_id = params.project_id + + project_controller.check_schema(schema.project.member_updated, body) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + local role = project_controller.project_authenticate(params.project_id, project_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(401, { err_message = "no permission to add project member admin" }) + end + end + + if pdk.string.tonumber(params.user_id) == project_controller.uid then + pdk.response.exit(401, { err_message = "no permission to add project member admin" }) + end + + local _, err = db.role.update(params.project_id, params.user_id, body.is_admin) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + + return project_controller -- Gitee From 467ecf68d2774edf0dbd0a61ad9ee0d895b37bb5 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 22:57:18 +0800 Subject: [PATCH 069/165] change: update admin.router functions. --- apioak/admin/router.lua | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/apioak/admin/router.lua b/apioak/admin/router.lua index 5f517ff..bbb5795 100644 --- a/apioak/admin/router.lua +++ b/apioak/admin/router.lua @@ -23,19 +23,17 @@ function router_controller.list(params) pdk.response.exit(200, { err_message = "OK", routers = res }) end -function router_controller.created(params) +function router_controller.created() - local body = router_controller.get_body() - body.project_id = params.project_id + local body = router_controller.get_body() router_controller.check_schema(schema.router.created, body) router_controller.user_authenticate() if not router_controller.is_owner then - router_controller.project_authenticate(params.project_id, router_controller.uid) + router_controller.project_authenticate(body.project_id, router_controller.uid) end - body.user_id = router_controller.uid local _, err = db.router.created(body) if err then @@ -70,7 +68,6 @@ end function router_controller.updated(params) local body = router_controller.get_body() - body.project_id = params.project_id body.router_id = params.router_id router_controller.check_schema(schema.router.updated, body) @@ -78,7 +75,7 @@ function router_controller.updated(params) router_controller.user_authenticate() if not router_controller.is_owner then - router_controller.project_authenticate(params.project_id, router_controller.uid) + router_controller.router_authenticate(params.router_id, router_controller.uid) end local _, err = db.router.updated(params.router_id, body) @@ -96,7 +93,7 @@ function router_controller.deleted(params) router_controller.user_authenticate() if not router_controller.is_owner then - router_controller.project_authenticate(params.project_id, router_controller.uid) + router_controller.router_authenticate(params.router_id, router_controller.uid) end local _, err = db.router.deleted(params.router_id) @@ -107,7 +104,7 @@ function router_controller.deleted(params) pdk.response.exit(200, { err_message = "OK" }) end -function router_controller.online(params) +function router_controller.env_push(params) router_controller.check_schema(schema.router.online, params) @@ -137,7 +134,7 @@ function router_controller.online(params) end router.plugins = plugins - res, err = db.router.online(params.router_id, params.env, router) + res, err = db.router.online(params.router_id, pdk.string.upper(params.env), router) if err then pdk.response.exit(500, { err_message = err }) end @@ -145,7 +142,7 @@ function router_controller.online(params) pdk.response.exit(200, { err_message = "OK" }) end -function router_controller.offline(params) +function router_controller.env_pull(params) router_controller.check_schema(schema.router.offline, params) @@ -154,7 +151,7 @@ function router_controller.offline(params) router_controller.router_authenticate(params.router_id, router_controller.uid) end - local _, err = db.router.offline(params.router_id, params.env) + local _, err = db.router.offline(params.router_id, pdk.string.upper(params.env)) if err then pdk.response.exit(500, { err_message = err }) end -- Gitee From cdd5639d2421e27108ac562590da3ee2b405c7fd Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 22:57:32 +0800 Subject: [PATCH 070/165] change: update admin.user functions. --- apioak/admin/user.lua | 106 +++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/apioak/admin/user.lua b/apioak/admin/user.lua index d9408ca..ed702f0 100644 --- a/apioak/admin/user.lua +++ b/apioak/admin/user.lua @@ -17,7 +17,7 @@ function user_controller.register() end if #res == 0 then - body.is_owner = 1 + body.is_owner = 1 body.is_enable = 1 end @@ -30,24 +30,6 @@ function user_controller.register() pdk.response.exit(500, { err_message = err }) end - if body.is_owner == 1 then - local user_id = res.insert_id - res, err = db.group.create({ - name = "Default Group", - description = "default project group", - user_id = user_id - }) - if err then - pdk.response.exit(500, { err_message = err }) - end - - local group_id = res.insert_id - res, err = db.role.create(group_id, user_id, true) - if err then - pdk.response.exit(500, { err_message = err }) - end - end - pdk.response.exit(200, { err_message = "OK" }) end @@ -59,7 +41,7 @@ function user_controller.login() local res, err = db.user.query_by_email(body.email) if err then - pdk.response.exit(401, { err_message = err }) + pdk.response.exit(500, { err_message = err }) end if #res == 0 then @@ -80,7 +62,7 @@ function user_controller.login() res, err = db.token.query_by_uid(user.id) if err then - pdk.response.exit(401, { err_message = err }) + pdk.response.exit(500, { err_message = err }) end if #res == 0 then @@ -90,26 +72,15 @@ function user_controller.login() end if err then - pdk.response.exit(401, { err_message = err }) + pdk.response.exit(500, { err_message = err }) end user.token = res.token - if user.is_owner then - res, err = db.group.all(true) - else - res, err = db.group.query_by_uid(user.id) - end - - if err then - pdk.response.exit(401, { err_message = err }) - end - pdk.response.exit(200, { err_message = "OK" , user = { id = user.id, name = user.name, token = user.token, - is_owner = user.is_owner, - groups = res + is_owner = user.is_owner }}) end @@ -162,75 +133,102 @@ function user_controller.created() end -function user_controller.updated_password(params) +function user_controller.enable(params) - user_controller.user_authenticate() + user_controller.check_schema(schema.user.enable, params) - local body = user_controller.get_body() - body.user_id = params.user_id - - user_controller.check_schema(schema.user.updated_password, body) + user_controller.user_authenticate() - if not user_controller.is_owner and params.user_id ~= user_controller.uid then - pdk.response.exit(401, { err_message = "no permission to create user" }) + if not user_controller.is_owner then + pdk.response.exit(401, { err_message = "no permission to enable user" }) end - if body.password ~= body.valid_password then - pdk.response.exit(401, { err_message = "inconsistent password entry" }) + if pdk.string.tonumber(params.user_id) == user_controller.uid then + pdk.response.exit(401, { err_message = "no permission to enable user" }) end - local _, err = db.user.update_password(params.user_id, body.password) + local _, err = db.user.update_status(params.user_id, 1) if err then pdk.response.exit(500, { err_message = err }) end + pdk.response.exit(200, { err_message = "OK" }) end -function user_controller.updated_status(params) +function user_controller.disable(params) + + user_controller.check_schema(schema.user.disable, params) user_controller.user_authenticate() + if not user_controller.is_owner then + pdk.response.exit(401, { err_message = "no permission to disable user" }) + end + + if pdk.string.tonumber(params.user_id) == user_controller.uid then + pdk.response.exit(401, { err_message = "no permission to disable user" }) + end + + local _, err = db.user.update_status(params.user_id, 0) + + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK" }) +end + +function user_controller.password(params) + local body = user_controller.get_body() body.user_id = params.user_id - user_controller.check_schema(schema.user.updated_status, body) + user_controller.check_schema(schema.user.password, body) - if not user_controller.is_owner then - pdk.response.exit(401, { err_message = "no permission to create user" }) + user_controller.user_authenticate() + + if not user_controller.is_owner and params.user_id ~= user_controller.uid then + pdk.response.exit(401, { err_message = "no permission to update user password" }) end - local _, err = db.user.update_status(params.user_id, body.is_enable) + if body.password ~= body.valid_password then + pdk.response.exit(401, { err_message = "inconsistent password entry" }) + end + + local _, err = db.user.update_password(params.user_id, body.password) if err then pdk.response.exit(500, { err_message = err }) end + pdk.response.exit(200, { err_message = "OK" }) end function user_controller.deleted(params) + user_controller.check_schema(schema.user.deleted, params) + user_controller.user_authenticate() if not user_controller.is_owner then pdk.response.exit(401, { err_message = "no permission to delete user" }) end - if params.user_id == user_controller.uid then + if pdk.string.tonumber(params.user_id) == user_controller.uid then pdk.response.exit(401, { err_message = "no permission to delete user" }) end - user_controller.check_schema(schema.user.updated, params) - local res, err = db.role.delete_by_uid(params.user_id) if err then pdk.response.exit(500, { err_message = err }) end - res, err = db.user.create(params.user_id) + res, err = db.user.delete(params.user_id) if err then pdk.response.exit(500, { err_message = err }) end + pdk.response.exit(200, { err_message = "OK" }) end -- Gitee From a80c188439c85803e0819074896ee80efd3f6731 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 22:58:46 +0800 Subject: [PATCH 071/165] change: add pdk.request get method functions. --- apioak/pdk/request.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apioak/pdk/request.lua b/apioak/pdk/request.lua index 3a06f7e..20b0567 100644 --- a/apioak/pdk/request.lua +++ b/apioak/pdk/request.lua @@ -78,4 +78,6 @@ _M.header = _header _M.add_header = ngx.req.set_header +_M.method = ngx.req.get_method + return _M -- Gitee From 0ca7679aa86d48dba3506ef64d7c7ab2ca32f74f Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 22:59:14 +0800 Subject: [PATCH 072/165] change: add pdk.string length functions. --- apioak/pdk/string.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apioak/pdk/string.lua b/apioak/pdk/string.lua index be916d3..609b2c4 100644 --- a/apioak/pdk/string.lua +++ b/apioak/pdk/string.lua @@ -10,6 +10,8 @@ _M.upper = string.upper _M.find = string.find +_M.len = string.len + _M.split = plstring.split _M.replace = plstring.replace -- Gitee From b8aaf2e638c42bb71d656e80bc58d75615963b37 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:00:24 +0800 Subject: [PATCH 073/165] change: remove db.group model. --- apioak/db/group.lua | 89 --------------------------------------------- 1 file changed, 89 deletions(-) delete mode 100644 apioak/db/group.lua diff --git a/apioak/db/group.lua b/apioak/db/group.lua deleted file mode 100644 index 82f3232..0000000 --- a/apioak/db/group.lua +++ /dev/null @@ -1,89 +0,0 @@ -local pdk = require("apioak.pdk") -local role = require("apioak.db.role") - -local table_name = "oak_groups" - -local _M = {} - -function _M.all(is_owner) - local sql - if is_owner then - sql = pdk.string.format("SELECT *, 1 AS is_admin FROM %s", table_name) - else - sql = pdk.string.format("SELECT * FROM %s", table_name) - end - local res, err = pdk.mysql.execute(sql) - - if err then - return nil, err - end - return res, nil -end - -function _M.create(params) - local sql = pdk.string.format("INSERT INTO %s (name, description) VALUES ('%s', '%s')", - table_name, params.name, params.description) - local res, err = pdk.mysql.execute(sql) - - if err then - return nil, err - end - return res, nil -end - -function _M.update(group_id, params) - local sql = pdk.string.format("UPDATE %s SET name = '%s', description = '%s' WHERE id = %s", - table_name, params.name, params.description, group_id) - local res, err = pdk.mysql.execute(sql) - - if err then - return nil, err - end - return res, nil -end - -function _M.delete(group_id) - local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", table_name, group_id) - local res, err = pdk.mysql.execute(sql) - - if err then - return nil, err - end - return res, nil -end - -function _M.query(group_id) - local sql = pdk.string.format("SELECT * FROM %s WHERE id = %s", table_name, group_id) - local res, err = pdk.mysql.execute(sql) - - if err then - return nil, err - end - return res, nil -end - -function _M.query_by_name(name) - local sql = pdk.string.format("SELECT * FROM %s WHERE name = '%s'", - table_name, name) - local res, err = pdk.mysql.execute(sql) - if err then - return nil, err - end - - return res, nil -end - -function _M.query_by_uid(user_id) - local sql = pdk.string.format( - "SELECT groups.*, roles.is_admin FROM %s as roles, %s as groups WHERE roles.group_id = groups.id AND roles.user_id = %s", - role.table_name, table_name, user_id) - - local res, err = pdk.mysql.execute(sql) - if err then - return nil, err - end - - return res, nil -end - -return _M -- Gitee From 3254e79470117a1050a70c25140386fac5c2ae0f Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:01:23 +0800 Subject: [PATCH 074/165] change: update db.plugin model functions. --- apioak/db/plugin.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioak/db/plugin.lua b/apioak/db/plugin.lua index be4abfc..b16b4d4 100644 --- a/apioak/db/plugin.lua +++ b/apioak/db/plugin.lua @@ -11,7 +11,7 @@ _M.RESOURCES_TYPE_ROUTER = "ROUTER" _M.RESOURCES_TYPE_PROJECT = "PROJECT" function _M.query_by_res(res_type, res_id) - local sql = pdk.string.format("SELECT * FROM %s WHERE res_type = '%s' AND res_id = %s", + local sql = pdk.string.format("SELECT id, name, type, description, config FROM %s WHERE res_type = '%s' AND res_id = %s", table_name, res_type, res_id) local res, err = pdk.mysql.execute(sql) -- Gitee From 13b1a66b3a0e81da8001851b48607a7d1bcdda28 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:01:37 +0800 Subject: [PATCH 075/165] change: update db.project model functions. --- apioak/db/project.lua | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/apioak/db/project.lua b/apioak/db/project.lua index c4dd682..79a20b8 100644 --- a/apioak/db/project.lua +++ b/apioak/db/project.lua @@ -1,4 +1,5 @@ local pdk = require("apioak.pdk") +local role = require("apioak.db.role") local plugin = require("apioak.db.plugin") local upstream = require("apioak.db.upstream") @@ -8,29 +9,38 @@ local _M = {} _M.table_name = table_name -function _M.all() - local sql = pdk.string.format("SELECT * FROM %s", table_name) +function _M.all(is_owner) + local sql + if is_owner then + sql = pdk.string.format("SELECT *, 1 AS is_admin FROM %s", table_name) + else + sql = pdk.string.format("SELECT * FROM %s", table_name) + end local res, err = pdk.mysql.execute(sql) if err then return nil, err end + return res, nil end -function _M.query_by_gid(group_id) - local sql = pdk.string.format("SELECT * FROM %s WHERE group_id = %s", table_name, group_id) - local res, err = pdk.mysql.execute(sql) +function _M.query_by_uid(user_id) + local sql = pdk.string.format( + "SELECT projects.*, roles.is_admin FROM %s as roles, %s AS projects WHERE roles.project_id = projects.id AND roles.user_id = %s", + role.table_name, table_name, user_id) + local res, err = pdk.mysql.execute(sql) if err then return nil, err end + return res, nil end -function _M.create(params) - local sql = pdk.string.format("INSERT INTO %s (name, description, path, group_id, user_id) VALUES ('%s', '%s', '%s', '%s', '%s')", - table_name, params.name, params.description, params.path, params.group_id, params.user_id) +function _M.created(params) + local sql = pdk.string.format("INSERT INTO %s (name, description, path) VALUES ('%s', '%s', '%s')", + table_name, params.name, params.description, params.path) local res, err = pdk.mysql.execute(sql) if err then @@ -39,7 +49,7 @@ function _M.create(params) return res, nil end -function _M.update(project_id, params) +function _M.updated(project_id, params) local sql = pdk.string.format("UPDATE %s SET name = '%s', description = '%s', path = '%s' WHERE id = %s", table_name, params.name, params.description, params.path, project_id) local res, err = pdk.mysql.execute(sql) @@ -47,6 +57,7 @@ function _M.update(project_id, params) if err then return nil, err end + return res, nil end @@ -58,6 +69,7 @@ function _M.query(project_id) if err then return nil, err end + return res, nil end -- Gitee From b7bac48bd8ea8ff70066bfce418b52a09c820755 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:01:54 +0800 Subject: [PATCH 076/165] change: update db.role model functions. --- apioak/db/role.lua | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/apioak/db/role.lua b/apioak/db/role.lua index b06ed1b..e106a3f 100644 --- a/apioak/db/role.lua +++ b/apioak/db/role.lua @@ -6,10 +6,11 @@ local _M = {} _M.table_name = table_name -function _M.create(group_id, user_id, is_admin) - local sql = pdk.string.format("INSERT INTO %s (group_id, user_id, is_admin) VALUES ('%s', '%s', '%s')", - table_name, group_id, user_id, is_admin) +function _M.create(project_id, user_id, is_admin) + local sql = pdk.string.format("INSERT INTO %s (project_id, user_id, is_admin) VALUES ('%s', '%s', '%s')", + table_name, project_id, user_id, is_admin) local res, err = pdk.mysql.execute(sql) + if err then return nil, err end @@ -17,10 +18,11 @@ function _M.create(group_id, user_id, is_admin) return res, nil end -function _M.query(group_id, user_id) - local sql = pdk.string.format("SELECT * FROM %s WHERE group_id = %s AND user_id = %s", - table_name, group_id, user_id) +function _M.query(project_id, user_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE project_id = %s AND user_id = %s", + table_name, project_id, user_id) local res, err = pdk.mysql.execute(sql) + if err then return nil, err end @@ -28,10 +30,11 @@ function _M.query(group_id, user_id) return res, nil end -function _M.delete(group_id, user_id) - local sql = pdk.string.format("DELETE FROM %s WHERE group_id = %s AND user_id = %s", - table_name, group_id, user_id) +function _M.delete(project_id, user_id) + local sql = pdk.string.format("DELETE FROM %s WHERE project_id = %s AND user_id = %s", + table_name, project_id, user_id) local res, err = pdk.mysql.execute(sql) + if err then return nil, err end @@ -39,10 +42,11 @@ function _M.delete(group_id, user_id) return res, nil end -function _M.update(group_id, user_id, is_admin) - local sql = pdk.string.format("UPDATE %s SET is_admin = %s WHERE group_id = %s AND user_id = %s", - table_name, is_admin, group_id, user_id) +function _M.update(project_id, user_id, is_admin) + local sql = pdk.string.format("UPDATE %s SET is_admin = %s WHERE project_id = %s AND user_id = %s", + table_name, is_admin, project_id, user_id) local res, err = pdk.mysql.execute(sql) + if err then return nil, err end @@ -53,6 +57,7 @@ end function _M.query_by_uid(user_id) local sql = pdk.string.format("SELECT * FROM %s WHERE user_id = %s", table_name, user_id) local res, err = pdk.mysql.execute(sql) + if err then return nil, err end @@ -60,9 +65,10 @@ function _M.query_by_uid(user_id) return res, nil end -function _M.query_by_gid(group_id) - local sql = pdk.string.format("SELECT * FROM %s WHERE group_id = %s", table_name, group_id) +function _M.query_by_pid(project_id) + local sql = pdk.string.format("SELECT * FROM %s WHERE project_id = %s", table_name, project_id) local res, err = pdk.mysql.execute(sql) + if err then return nil, err end @@ -70,9 +76,10 @@ function _M.query_by_gid(group_id) return res, nil end -function _M.delete_by_gid(group_id) - local sql = pdk.string.format("DELETE FROM %s WHERE group_id = %s", table_name, group_id) +function _M.delete_by_pid(project_id) + local sql = pdk.string.format("DELETE FROM %s WHERE project_id = %s", table_name, project_id) local res, err = pdk.mysql.execute(sql) + if err then return nil, err end -- Gitee From 6b7e9757ccb398b5f671dbb44b804915a3bc82ff Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:02:31 +0800 Subject: [PATCH 077/165] change: update db.router model functions. --- apioak/db/router.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apioak/db/router.lua b/apioak/db/router.lua index 1b8fb59..442075d 100644 --- a/apioak/db/router.lua +++ b/apioak/db/router.lua @@ -89,19 +89,20 @@ end function _M.created(params) local sql = "INSERT INTO %s (name, enable_cors, description, request_path, request_method, request_params, " .. "backend_path, backend_method, backend_params, constant_params, response_type, " .. - "response_success, response_failure, response_codes, response_schema, project_id, user_id) " .. + "response_success, response_failure, response_codes, response_schema, project_id) " .. "VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', " .. - "'%s', '%s', '%s')" + "'%s')" sql = pdk.string.format(sql, table_name, params.name, params.enable_cors, params.description, params.request_path, params.request_method, pdk.json.encode(params.request_params), params.backend_path, params.backend_method, pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), params.response_type, params.response_success, params.response_failure, pdk.json.encode(params.response_codes), - pdk.json.encode(params.response_schema), params.project_id, params.user_id) + pdk.json.encode(params.response_schema), params.project_id) local res, err = pdk.mysql.execute(sql) if err then return nil, err end + return res, nil end @@ -121,6 +122,7 @@ function _M.updated(router_id, params) if err then return nil, err end + return res, nil end -- Gitee From 6f954870826c55a91c4c815c919747604bccc3e6 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:02:42 +0800 Subject: [PATCH 078/165] change: update db.token model functions. --- apioak/db/token.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apioak/db/token.lua b/apioak/db/token.lua index ef2efb8..cb254c6 100644 --- a/apioak/db/token.lua +++ b/apioak/db/token.lua @@ -85,8 +85,7 @@ function _M.continue_by_token(token) end function _M.expire_by_token(token) - local sql = pdk.string.format("UPDATE %s SET expired_at = '%s' WHERE token = '%s'", table_name, - pdk.string.null, token) + local sql = pdk.string.format("UPDATE %s SET expired_at = NULL WHERE token = '%s'", table_name, token) local res, err = pdk.mysql.execute(sql) if err then -- Gitee From 5f46dae91bc276bf8d7856c26f1dca3ca92f2797 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:02:55 +0800 Subject: [PATCH 079/165] change: update db.user model functions. --- apioak/db/user.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apioak/db/user.lua b/apioak/db/user.lua index c0811f4..912e284 100644 --- a/apioak/db/user.lua +++ b/apioak/db/user.lua @@ -8,7 +8,7 @@ local table_name = "oak_users" _M.table_name = table_name function _M.all() - local sql = pdk.string.format("SELECT id, name, email, is_enable FROM %s", table_name) + local sql = pdk.string.format("SELECT id, name, email FROM %s WHERE is_enable = 1", table_name) local res, err = pdk.mysql.execute(sql) if err then return nil, err @@ -91,9 +91,9 @@ function _M.query_by_id(uid) return res, err end -function _M.query_by_gid(gid) +function _M.query_by_pid(gid) local sql = pdk.string.format( - "SELECT users.id, users.name, users.email, roles.is_admin FROM %s AS roles LEFT JOIN %s AS users ON roles.user_id = users.id WHERE roles.group_id = %s", + "SELECT users.id, users.name, users.email, roles.is_admin FROM %s AS roles LEFT JOIN %s AS users ON roles.user_id = users.id WHERE roles.project_id = %s", role.table_name, table_name, gid) local res, err = pdk.mysql.execute(sql) -- Gitee From d4f46b48e0a26e739aeca695eee3ab8de634d753 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:04:05 +0800 Subject: [PATCH 080/165] change: remove schema.group verification module. --- apioak/schema/group.lua | 213 ---------------------------------------- 1 file changed, 213 deletions(-) delete mode 100644 apioak/schema/group.lua diff --git a/apioak/schema/group.lua b/apioak/schema/group.lua deleted file mode 100644 index 2a319c6..0000000 --- a/apioak/schema/group.lua +++ /dev/null @@ -1,213 +0,0 @@ -local _M = {} - -_M.created = { - type = "object", - properties = { - name = { - type = 'string', - minLength = 5, - maxLength = 20, - }, - description = { - type = 'string', - minLength = 5, - maxLength = 50, - } - }, - required = { "name", "description" } -} - -_M.updated = { - type = "object", - properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - name = { - type = 'string', - minLength = 5, - maxLength = 20, - }, - description = { - type = 'string', - minLength = 5, - maxLength = 50, - } - }, - required = { "group_id", "name", "description" } -} - -_M.deleted = { - type = "object", - properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - } - } -} - -_M.query = { - type = "object", - properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - } - } -} - -_M.user_list = { - type = "object", - properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - } - } -} - -_M.user_created = { - type = "object", - properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - user_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - is_admin = { - type = "number", - enum = { 0, 1 } - } - }, - required = { "group_id", "user_id", "is_admin" } -} - -_M.user_deleted = { - type = "object", - properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - user_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - } - } -} - -_M.user_updated = { - type = "object", - properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - user_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - is_admin = { - type = "number", - enum = { 0, 1 } - } - }, - required = { "group_id", "user_id", "is_admin" } -} - -return _M -- Gitee From dcf3f96774279a6cbb166fc0f9396f6c306b4d5e Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:04:59 +0800 Subject: [PATCH 081/165] change: update schema.project verification module rules. --- apioak/schema/project.lua | 191 ++++++++++++++++++++++++++------------ 1 file changed, 130 insertions(+), 61 deletions(-) diff --git a/apioak/schema/project.lua b/apioak/schema/project.lua index 2406750..e78e897 100644 --- a/apioak/schema/project.lua +++ b/apioak/schema/project.lua @@ -1,40 +1,8 @@ local _M = {} -_M.list = { - type = "object", - properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - } - } -} - _M.deleted = { type = "object", properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, project_id = { anyOf = { { @@ -54,19 +22,6 @@ _M.deleted = { _M.query = { type = "object", properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, project_id = { anyOf = { { @@ -86,7 +41,7 @@ _M.query = { _M.updated = { type = "object", properties = { - id = { + project_id = { anyOf = { { type = "string", @@ -201,25 +156,12 @@ _M.updated = { }, } }, - required = { "id", "name", "path", "upstreams", "description" } + required = { "project_id", "name", "path", "upstreams", "description" } } _M.created = { type = "object", properties = { - group_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, name = { type = 'string', minLength = 1, @@ -309,7 +251,7 @@ _M.created = { }, } }, - required = { "group_id", "name", "path", "upstreams", "description" } + required = { "name", "path", "upstreams", "description" } } _M.plugin_list = { @@ -452,4 +394,131 @@ _M.plugin_deleted = { } } +_M.members = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + }, + required = { "project_id" } +} + +_M.member_created = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + is_admin = { + type = "number", + enum = { 0, 1 } + } + }, + required = { "project_id", "user_id", "is_admin" } +} + +_M.member_deleted = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + }, + required = { "project_id", "user_id" } +} + +_M.member_updated = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + is_admin = { + type = "number", + enum = { 0, 1 } + } + }, + required = { "project_id", "user_id", "is_admin" } +} + return _M -- Gitee From 39fdcab58737c0aaddd33cac824279b3d96dfa0f Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:05:34 +0800 Subject: [PATCH 082/165] change: update schema.router verification module rules. --- apioak/schema/router.lua | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/apioak/schema/router.lua b/apioak/schema/router.lua index 147b9d0..c2d2acd 100644 --- a/apioak/schema/router.lua +++ b/apioak/schema/router.lua @@ -414,19 +414,6 @@ _M.updated = { response_schema = { type = "array", }, - project_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, router_id = { anyOf = { { @@ -443,7 +430,7 @@ _M.updated = { }, required = { "name", "enable_cors", "description", "request_path", "request_method", "request_params", "backend_path", "backend_method", "backend_params", "constant_params", "response_type", - "response_success", "response_failure", "response_codes", "response_schema", "project_id", "router_id" } + "response_success", "response_failure", "response_codes", "response_schema", "router_id" } } _M.deleted = { @@ -532,6 +519,9 @@ _M.online = { "PROD", "BETA", "TEST", + "prod", + "beta", + "test", } } } @@ -559,6 +549,9 @@ _M.offline = { "PROD", "BETA", "TEST", + "prod", + "beta", + "test", } } } -- Gitee From 7d526eeb744a2a2b28e25b20eed1dac189091e9d Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:05:50 +0800 Subject: [PATCH 083/165] change: update schema.user verification module rules. --- apioak/schema/user.lua | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apioak/schema/user.lua b/apioak/schema/user.lua index 31a1dd0..92ae7f5 100644 --- a/apioak/schema/user.lua +++ b/apioak/schema/user.lua @@ -72,7 +72,7 @@ _M.created = { required = { "name", "password", "valid_password", "email", "is_enable" } } -_M.updated_password = { +_M.password = { type = "object", properties = { user_id = { @@ -101,7 +101,7 @@ _M.updated_password = { } } -_M.updated_status = { +_M.enable = { type = "object", properties = { user_id = { @@ -116,13 +116,27 @@ _M.updated_status = { minimum = 1 } } - }, - is_enable = { - type = 'number', - enum = { 0, 1 } } } +} +_M.disable = { + type = "object", + properties = { + user_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } } _M.deleted = { -- Gitee From 6aa0cd9f5ac623a541ecc5a3c8521e03a99e8cbd Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:06:47 +0800 Subject: [PATCH 084/165] change: update sys.admin routers. --- apioak/sys/admin.lua | 97 +++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/apioak/sys/admin.lua b/apioak/sys/admin.lua index 46afd8e..96092ad 100644 --- a/apioak/sys/admin.lua +++ b/apioak/sys/admin.lua @@ -8,103 +8,90 @@ function _M.init_worker() router = r3route.new() - -- Plugin Manager URI + -- Common Service Related APIs router:insert_route("/apioak/admin/plugins", admin.plugin.plugin_list, { method = { "GET" } }) - router:insert_route("/apioak/admin/project/{project_id}/plugins", admin.plugin.project_list, + router:insert_route("/apioak/admin/users", admin.user.list, { method = { "GET" } }) - router:insert_route("/apioak/admin/project/{project_id}/plugin", admin.plugin.project_created, - { method = { "POST" } }) - - router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.plugin.project_updated, - { method = { "PUT" } }) - - router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.plugin.project_deleted, - { method = { "DELETE" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugins", admin.plugin.router_list, + -- Project Related APIs + router:insert_route("/apioak/admin/projects", admin.project.list, { method = { "GET" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugin", admin.plugin.router_created, + router:insert_route("/apioak/admin/project", admin.project.created, { method = { "POST" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.plugin.router_updated, + router:insert_route("/apioak/admin/project/{project_id}", admin.project.updated, { method = { "PUT" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.plugin.router_deleted, + router:insert_route("/apioak/admin/project/{project_id}", admin.project.query, + { method = { "GET" } }) + + router:insert_route("/apioak/admin/project/{project_id}", admin.project.deleted, { method = { "DELETE" } }) - -- Group Manager URI - router:insert_route("/apioak/admin/groups", admin.group.list, + router:insert_route("/apioak/admin/project/{project_id}/plugins", admin.plugin.project_list, { method = { "GET" } }) - router:insert_route("/apioak/admin/group", admin.group.created, + router:insert_route("/apioak/admin/project/{project_id}/plugin", admin.plugin.project_created, { method = { "POST" } }) - router:insert_route("/apioak/admin/group/{group_id}", admin.group.query, - { method = { "GET" } }) - - router:insert_route("/apioak/admin/group/{group_id}", admin.group.updated, + router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.plugin.project_updated, { method = { "PUT" } }) - router:insert_route("/apioak/admin/group/{group_id}", admin.group.deleted, + router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.plugin.project_deleted, { method = { "DELETE" } }) - -- Group User Manager URI - router:insert_route("/apioak/admin/group/{group_id}/users", admin.group.user_list, + router:insert_route("/apioak/admin/project/{project_id}/routers", admin.router.list, { method = { "GET" } }) - router:insert_route("/apioak/admin/group/{group_id}/user", admin.group.user_create, - { method = { "POST" } }) + router:insert_route("/apioak/admin/project/{project_id}/members", admin.project.members, + { method = { "GET" } }) - router:insert_route("/apioak/admin/group/{group_id}/user/{user_id}", admin.group.user_update, - { method = { "PUT" } }) + router:insert_route("/apioak/admin/project/{project_id}/member", admin.project.member_created, + { method = { "POST" } }) - router:insert_route("/apioak/admin/group/{group_id}/user/{user_id}", admin.group.user_delete, + router:insert_route("/apioak/admin/project/{project_id}/member/{user_id}", admin.project.member_deleted, { method = { "DELETE" } }) - -- Group Project Manager URI - router:insert_route("/apioak/admin/group/{group_id}/projects", admin.project.list, - { method = { "GET" } }) + router:insert_route("/apioak/admin/project/{project_id}/member/{user_id}", admin.project.member_updated, + { method = { "PUT" } }) + - router:insert_route("/apioak/admin/group/{group_id}/project", admin.project.create, + -- Router Related APIs + router:insert_route("/apioak/admin/router", admin.router.created, { method = { "POST" } }) - router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.query, + router:insert_route("/apioak/admin/router/{router_id}", admin.router.query, { method = { "GET" } }) - router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.update, + router:insert_route("/apioak/admin/router/{router_id}", admin.router.updated, { method = { "PUT" } }) - router:insert_route("/apioak/admin/group/{group_id}/project/{project_id}", admin.project.delete, + router:insert_route("/apioak/admin/router/{router_id}", admin.router.deleted, { method = { "DELETE" } }) - - -- Project Router Manager URI - router:insert_route("/apioak/admin/project/{project_id}/routers", admin.router.list, + router:insert_route("/apioak/admin/router/{router_id}/plugins", admin.plugin.router_list, { method = { "GET" } }) - router:insert_route("/apioak/admin/project/{project_id}/router", admin.router.created, + router:insert_route("/apioak/admin/router/{router_id}/plugin", admin.plugin.router_created, { method = { "POST" } }) - router:insert_route("/apioak/admin/project/{project_id}/router/{router_id}", admin.router.query, - { method = { "GET" } }) - - router:insert_route("/apioak/admin/project/{project_id}/router/{router_id}", admin.router.updated, + router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.plugin.router_updated, { method = { "PUT" } }) - router:insert_route("/apioak/admin/project/{project_id}/router/{router_id}", admin.router.deleted, + router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.plugin.router_deleted, { method = { "DELETE" } }) - -- Router Publish Manager URI - router:insert_route("/apioak/admin/router/{router_id}/online/{env}", admin.router.online, + router:insert_route("/apioak/admin/router/{router_id}/env/{env}", admin.router.env_push, { method = { "POST" } }) - router:insert_route("/apioak/admin/router/{router_id}/offline/{env}", admin.router.offline, + router:insert_route("/apioak/admin/router/{router_id}/env/{env}", admin.router.env_pull, { method = { "DELETE" } }) + -- Account Manager API router:insert_route("/apioak/admin/account/register", admin.user.register, { method = { "POST" } }) @@ -116,20 +103,20 @@ function _M.init_worker() { method = { "GET" } }) -- User Manager API - router:insert_route("/apioak/admin/users", admin.user.list, - { method = { "GET" } }) - router:insert_route("/apioak/admin/user", admin.user.created, { method = { "POST" } }) - router:insert_route("/apioak/admin/user/{user_id}/password", admin.user.updated_password, + router:insert_route("/apioak/admin/user/{user_id}", admin.user.deleted, + { method = { "DELETE" } }) + + router:insert_route("/apioak/admin/user/{user_id}/password", admin.user.password, { method = { "PUT" } }) - router:insert_route("/apioak/admin/user/{user_id}/status", admin.user.updated_status, + router:insert_route("/apioak/admin/user/{user_id}/enable", admin.user.enable, { method = { "PUT" } }) - router:insert_route("/apioak/admin/user/{user_id}", admin.user.deleted, - { method = { "DELETE" } }) + router:insert_route("/apioak/admin/user/{user_id}/disable", admin.user.disable, + { method = { "PUT" } }) router:compile() -- Gitee From b48ad04b62f0cebf04f6812b6135e2ff69d9189b Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:07:46 +0800 Subject: [PATCH 085/165] change: update sys.balancer rules. --- apioak/sys/balancer.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apioak/sys/balancer.lua b/apioak/sys/balancer.lua index 6ab9dec..393ca1e 100644 --- a/apioak/sys/balancer.lua +++ b/apioak/sys/balancer.lua @@ -50,7 +50,10 @@ function _M.go(oak_ctx) local timeout = upstream.timeouts if timeout then - local ok, err = set_timeouts(timeout.connect, timeout.send, timeout.read) + local connect_timout = timeout.connect or 0 + local send_timeout = timeout.send or 0 + local read_timeout = timeout.read or 0 + local ok, err = set_timeouts(connect_timout / 1000, send_timeout / 1000, read_timeout / 1000) if not ok then pdk.log.error("[sys.balancer] could not set upstream timeouts: ", err) end -- Gitee From 890b082ab96f972566b049037b377ff36a471fb5 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:10:04 +0800 Subject: [PATCH 086/165] change: update sys.router loading rules. --- apioak/sys/router.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apioak/sys/router.lua b/apioak/sys/router.lua index c8b1607..e742b55 100644 --- a/apioak/sys/router.lua +++ b/apioak/sys/router.lua @@ -92,10 +92,11 @@ function _M.init_worker() return end - while not ngx_worker_exiting() do - loading_routers() - ngx_sleep(30) - end + while not ngx_worker_exiting() do + loading_routers() + -- automatically update configuration once every 30s + ngx_sleep(30) + end end) end -- Gitee From f9f35e257444c56d225bfbd76b1f7466d817b1fd Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:10:55 +0800 Subject: [PATCH 087/165] change: remove apioak.admin group module import. --- apioak/admin.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/apioak/admin.lua b/apioak/admin.lua index 5e6eaa5..03fc2d9 100644 --- a/apioak/admin.lua +++ b/apioak/admin.lua @@ -1,6 +1,5 @@ return { user = require("apioak.admin.user"), - group = require("apioak.admin.group"), router = require("apioak.admin.router"), project = require("apioak.admin.project"), plugin = require("apioak.admin.plugin"), -- Gitee From cd2f33e28d925cefe5d968a1022fae491dbb61dd Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:12:07 +0800 Subject: [PATCH 088/165] change: remove apioak.db group module import. --- apioak/db.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/apioak/db.lua b/apioak/db.lua index 7e3e000..d14a118 100644 --- a/apioak/db.lua +++ b/apioak/db.lua @@ -1,7 +1,6 @@ return { user = require("apioak.db.user"), role = require("apioak.db.role"), - group = require("apioak.db.group"), token = require("apioak.db.token"), router = require("apioak.db.router"), plugin = require("apioak.db.plugin"), -- Gitee From 86fbfb44ec33449f9086be7545806193846f6cd7 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:13:19 +0800 Subject: [PATCH 089/165] change: remove apioak.schema group module import. --- apioak/schema.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/apioak/schema.lua b/apioak/schema.lua index 3c9da5b..56b427d 100644 --- a/apioak/schema.lua +++ b/apioak/schema.lua @@ -1,6 +1,5 @@ return { user = require("apioak.schema.user"), - group = require("apioak.schema.group"), router = require("apioak.schema.router"), plugin = require("apioak.schema.plugin"), project = require("apioak.schema.project"), -- Gitee From bd629f413defa68e25354b44f4d90103fab4dd2d Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:14:27 +0800 Subject: [PATCH 090/165] change: remove apioak.pdk etcd module import. --- apioak/pdk.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/apioak/pdk.lua b/apioak/pdk.lua index 4d9ca10..efe4ffe 100644 --- a/apioak/pdk.lua +++ b/apioak/pdk.lua @@ -3,7 +3,6 @@ return { ctx = require("apioak.pdk.ctx"), json = require("apioak.pdk.json"), time = require("apioak.pdk.time"), - etcd = require("apioak.pdk.etcd"), config = require("apioak.pdk.config"), shared = require("apioak.pdk.shared"), table = require("apioak.pdk.table"), -- Gitee From 6524292c6e431211a4dc76cd11fb10abe2e5fce6 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:15:10 +0800 Subject: [PATCH 091/165] change: remove pdk.etcd module. --- apioak/pdk/etcd.lua | 96 --------------------------------------------- 1 file changed, 96 deletions(-) delete mode 100644 apioak/pdk/etcd.lua diff --git a/apioak/pdk/etcd.lua b/apioak/pdk/etcd.lua deleted file mode 100644 index f07ab3f..0000000 --- a/apioak/pdk/etcd.lua +++ /dev/null @@ -1,96 +0,0 @@ -local config = require("apioak.pdk.config") -local etcd = require("resty.etcd") - -local _M = {} - -local function get_cli() - local oak_conf = config.all() - local etcd_conf = oak_conf.etcd - - local etcd_options = {} - etcd_options.http_host = etcd_conf.host - etcd_options.timeout = etcd_conf.timeout - etcd_options.protocol = "v2" - etcd_options.key_prefix = "/v2/keys" - local prefix = etcd_conf.prefix or "" - - local cli, err = etcd:new(etcd_options) - - if err then - return nil, prefix, err - end - - return cli, prefix, nil -end - -function _M.query(key) - local cli, prefix, cli_err = get_cli() - if not cli then - return nil, 500, cli_err - end - local res, err = cli:get(prefix .. key) - if err then - return nil, 500, err - end - - if res.status ~= 200 then - return nil, res.status, res.reason - end - - return res.body.node, res.status, nil -end - -function _M.update(key, value) - local cli, prefix, cli_err = get_cli() - if not cli then - return nil, 500, cli_err - end - local res, err = cli:set(prefix .. key, value) - if err then - return nil, 500, err - end - - if res.status ~= 200 and res.status ~= 201 then - return nil, res.status, res.reason - end - - return res.body.node, res.status, nil -end - -function _M.create(key, value) - local cli, prefix, cli_err = get_cli() - if not cli then - return nil, 500, cli_err - end - - local res, err = cli:push(prefix .. key, value) - if err then - return nil, 500, err - end - - if res.status ~= 201 then - return nil, res.status, res.reason - end - - return res.body.node, res.status, nil -end - -function _M.delete(key) - local cli, prefix, cli_err = get_cli() - if not cli then - return nil, 500, cli_err - end - - local res, err = cli:delete(prefix .. key) - if err then - return nil, 500, err - end - - if res.status ~= 200 then - return nil, res.status, res.reason - end - - return res.body.node, res.status, nil -end - -return _M -- Gitee From 4c620d3f066b41d463b518d911b0e0cac4614b3a Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:16:01 +0800 Subject: [PATCH 092/165] change: remove LuaRocks etcd module import. --- rockspec/apioak-master-0.rockspec | 1 - 1 file changed, 1 deletion(-) diff --git a/rockspec/apioak-master-0.rockspec b/rockspec/apioak-master-0.rockspec index 7284bdd..c4faf55 100644 --- a/rockspec/apioak-master-0.rockspec +++ b/rockspec/apioak-master-0.rockspec @@ -16,7 +16,6 @@ description = { dependencies = { "lua-resty-template = 1.9", - "lua-resty-etcd = 0.7", "lua-resty-balancer = 0.02rc5", "lua-resty-ngxvar = 0.4", "lua-resty-jit-uuid = 0.0.7", -- Gitee From cdb71ff230fa375d5f03255f46dcd8cd52d2faa6 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:36:42 +0800 Subject: [PATCH 093/165] change: update nginx.conf default server. --- conf/nginx.conf | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/conf/nginx.conf b/conf/nginx.conf index 4c4e252..1f3daa6 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -62,19 +62,33 @@ http { server { listen 10111; - listen 10222; - listen 10333; - listen 10444; location / { content_by_lua_block { local json = require "cjson" ngx.status = 200 - ngx.say("Host: "..ngx.var.host .. " URI: " .. ngx.var.uri) - ngx.say("[Query Params]", json.encode(ngx.req.get_uri_args())) + ngx.header["Content-Type"] = "application/json" + + local response = {} + response["host"] = ngx.var.host + response["uri"] = ngx.var.uri + response["query"] = ngx.req.get_uri_args() ngx.req.read_body() - ngx.say("[Post Params]", json.encode(ngx.req.get_post_args())) - ngx.say("[Header Params]", json.encode(ngx.req.get_headers())) + response["body"] = ngx.req.get_post_args() + response["header"] = ngx.req.get_headers() + ngx.say(json.encode(response)) + } + } + } + + server { + listen 10222; + + location / { + content_by_lua_block { + ngx.status = 200 + ngx.header["Content-Type"] = "text/html" + ngx.say("Welcome to APIOAK") } } } -- Gitee From c0f1625ddba2f0afec5ee5fdf193afea1a8973e5 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:37:05 +0800 Subject: [PATCH 094/165] change: pdk.string add char function. --- apioak/pdk/string.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apioak/pdk/string.lua b/apioak/pdk/string.lua index 609b2c4..f9bd995 100644 --- a/apioak/pdk/string.lua +++ b/apioak/pdk/string.lua @@ -12,6 +12,8 @@ _M.find = string.find _M.len = string.len +_M.char = string.char + _M.split = plstring.split _M.replace = plstring.replace -- Gitee From 31a57f0111fa67d68d8e4086e46c94813358ace7 Mon Sep 17 00:00:00 2001 From: Janko Date: Thu, 12 Mar 2020 23:39:08 +0800 Subject: [PATCH 095/165] change: remove LuaRocks resty.template import. --- rockspec/apioak-master-0.rockspec | 1 - 1 file changed, 1 deletion(-) diff --git a/rockspec/apioak-master-0.rockspec b/rockspec/apioak-master-0.rockspec index c4faf55..f5775f7 100644 --- a/rockspec/apioak-master-0.rockspec +++ b/rockspec/apioak-master-0.rockspec @@ -15,7 +15,6 @@ description = { } dependencies = { - "lua-resty-template = 1.9", "lua-resty-balancer = 0.02rc5", "lua-resty-ngxvar = 0.4", "lua-resty-jit-uuid = 0.0.7", -- Gitee From fe5adae2eba9b7fe6f6076ebcbcd9cd6a7c6d49b Mon Sep 17 00:00:00 2001 From: Janko Date: Fri, 13 Mar 2020 15:59:29 +0800 Subject: [PATCH 096/165] bugfix: pdk.log output blocking question. --- apioak/pdk/log.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioak/pdk/log.lua b/apioak/pdk/log.lua index 7d781a5..80a5f5e 100644 --- a/apioak/pdk/log.lua +++ b/apioak/pdk/log.lua @@ -15,7 +15,7 @@ local _M = {} for log_name, log_level in pairs(_NGX_LOG_LEVELS) do _M[log_name] = function(...) - ngx_log(log_level, ...) + return ngx_log(log_level, ...) end end -- Gitee From b0d7c10df4d293de5a046bf808c78687efefd654 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:34:18 +0800 Subject: [PATCH 097/165] feature: add admin.account module. --- apioak/admin/account.lua | 115 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 apioak/admin/account.lua diff --git a/apioak/admin/account.lua b/apioak/admin/account.lua new file mode 100644 index 0000000..21a3b37 --- /dev/null +++ b/apioak/admin/account.lua @@ -0,0 +1,115 @@ +local db = require("apioak.db") +local pdk = require("apioak.pdk") +local schema = require("apioak.schema") +local controller = require("apioak.admin.controller") + +local account_controller = controller.new("account") + +function account_controller.register() + + local body = account_controller.get_body() + + account_controller.check_schema(schema.account.register, body) + + local res, err = db.user.all() + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + body.is_owner = 1 + end + body.is_enable = 1 + + if body.password ~= body.valid_password then + pdk.response.exit(403, { err_message = "[account.authenticate] inconsistent password entry" }) + end + + res, err = db.user.create(body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.insert_id == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function account_controller.login() + + local body = account_controller.get_body() + + account_controller.check_schema(schema.account.login, body) + + local res, err = db.user.query_by_email(body.email) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + pdk.response.exit(403, { err_message = "[account.authenticate] account not exists" }) + end + + local user = res[1] + if pdk.string.md5(body.password) ~= user.password then + pdk.response.exit(403, { err_message = "[account.authenticate] password validation failure" }) + end + + if user.is_enable == 0 then + pdk.response.exit(403, { err_message = "[account.authenticate] account is disabled" }) + end + + res, err = db.token.query_by_uid(user.id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if #res == 0 then + res, err = db.token.create_by_uid(user.id) + else + res, err = db.token.update_by_uid(user.id) + end + + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + user = pdk.table.del(user, "password") + user.token = res.token + + pdk.response.exit(200, { err_message = "OK", user = user }) + end +end + +function account_controller.logout() + + account_controller.user_authenticate() + + local res, err = db.token.expire_by_token(account_controller.token) + + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function account_controller.status() + + local user = account_controller.user_authenticate() + + user = pdk.table.del(user, "password") + + pdk.response.exit(200, { err_message = "OK" , user = user}) +end + +return account_controller -- Gitee From a9e7eae951ce93092a277ba1544db45cb0f6e9c1 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:34:39 +0800 Subject: [PATCH 098/165] feature: add admin.common module. --- apioak/admin/common.lua | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 apioak/admin/common.lua diff --git a/apioak/admin/common.lua b/apioak/admin/common.lua new file mode 100644 index 0000000..07782e9 --- /dev/null +++ b/apioak/admin/common.lua @@ -0,0 +1,89 @@ +local db = require("apioak.db") +local pdk = require("apioak.pdk") +local schema = require("apioak.schema") +local controller = require("apioak.admin.controller") + +local common_controller = controller.new("common") + +function common_controller.users() + + common_controller.user_authenticate() + + local res, err = db.user.all() + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", users = res }) +end + +function common_controller.plugins() + + common_controller.user_authenticate() + + local plugins = pdk.plugin.loading() + + local res = {} + for i = 1, #plugins do + pdk.table.insert(res, { + name = plugins[i].name, + type = plugins[i].type, + description = plugins[i].description, + config = plugins[i].config + }) + end + + pdk.response.exit(200, { err_message = "OK", plugins = res }) +end + +function common_controller.projects() + + local query = pdk.request.query() + + common_controller.check_schema(schema.common.projects, query) + + common_controller.user_authenticate() + + local res, err + if common_controller.is_owner then + res, err = db.project.all(query.q) + else + res, err = db.project.query_by_uid(common_controller.uid, query.q) + end + + if err then + pdk.response.exit(500, { err_message = err }) + end + + if common_controller.is_owner then + for i = 1, #res do + res[i].is_admin = 1 + end + end + + pdk.response.exit(200, { err_message = "OK", projects = res }) +end + +function common_controller.routers() + + local query = pdk.request.query() + + common_controller.check_schema(schema.common.routers, query) + + common_controller.user_authenticate() + + local res, err + if common_controller.is_owner then + res, err = db.router.all(query.q) + else + res, err = db.router.query_by_uid(common_controller.uid, query.q) + end + + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", routers = res }) +end + +return common_controller -- Gitee From 19ac5bef7aa065ab79c2e5cccec3ee27cb377aa4 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:35:37 +0800 Subject: [PATCH 099/165] change: remove redundant functions. --- apioak/admin/controller.lua | 65 +++++++++++++++---------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/apioak/admin/controller.lua b/apioak/admin/controller.lua index eef808b..5ebf07f 100644 --- a/apioak/admin/controller.lua +++ b/apioak/admin/controller.lua @@ -24,12 +24,10 @@ local controller = function(name) return body end - cls.get_header = function(key) return pdk.request.header(key) end - cls.check_schema = function(schema, body) local _, err = pdk.schema.check(schema, body) if err then @@ -37,64 +35,55 @@ local controller = function(name) end end - - cls.get_header_token = function() + cls.user_authenticate = function() local token = cls.get_header(pdk.const.REQUEST_ADMIN_TOKEN_KEY) if not token then - pdk.response.exit(401, { err_message = pdk.string.format( - "property header \"%s\" is required", cls.header_token_key) }) + pdk.response.exit(401, { err_message = "[account.authenticate] property header \"" .. + pdk.const.REQUEST_ADMIN_TOKEN_KEY .. "\" is required" }) end - return token - end - cls.get_account_token = function(token) local res, err = db.token.query_by_token(token) if err then pdk.response.exit(500, { err_message = err }) end if #res == 0 then - pdk.response.exit(401, { err_message = "login account token invalid" }) + pdk.response.exit(401, { err_message = "[account.authenticate] token \"" .. + token .. "\" invalid" }) end - return res[1] - end + local exp_at = pdk.time.strtotime(res[1].expired_at) + local now_at = pdk.time.time() + if exp_at < now_at then + pdk.response.exit(401, { err_message = "[account.authenticate] token \"" .. + token .. "\" expired" }) + end + + if (exp_at - now_at) < 3600 then + db.token.continue_by_token(token) + end - cls.get_account_info = function(uid) - local res, err = db.user.query_by_id(uid) + res, err = db.user.query_by_id(res[1].user_id) if err then pdk.response.exit(500, { err_message = err }) end if #res == 0 then - pdk.response.exit(401, { err_message = "login account not exists" }) + pdk.response.exit(401, { err_message = "[account.authenticate] account not exists" }) end - return res[1] - end + local user = res[1] - cls.user_authenticate = function() - local header_token = cls.get_header_token() - local account_token = cls.get_account_token(header_token) - - local expired = pdk.time.strtotime(account_token.expired_at) - local now_time = pdk.time.time() - if expired < now_time then - pdk.response.exit(401, { err_message = "login status expired" }) - end + cls.uid = user.id + cls.token = token - local diff_time = expired - now_time - if diff_time < 3600 then - db.token.continue_by_token(cls.token) - end - - local account_info = cls.get_account_info(account_token.user_id) - cls.uid = account_info.id - cls.token = header_token - - if account_info.is_owner == 1 then + if user.is_owner == 1 then cls.is_owner = true + else + cls.is_owner = false end + + return user end cls.project_authenticate = function(project_id, user_id) @@ -104,7 +93,7 @@ local controller = function(name) end if #res == 0 then - pdk.response.exit(401, { err_message = "no permission to operate on this project" }) + pdk.response.exit(403, { err_message = "[project.authenticate] no project permissions" }) end return res[1] @@ -117,7 +106,7 @@ local controller = function(name) end if #res == 0 then - pdk.response.exit(500, { err_message = "router: " .. router_id .. " not exists" }) + pdk.response.exit(403, { err_message = "[router.authenticate] no router permissions" }) end return cls.project_authenticate(res[1].project_id, user_id) -- Gitee From 102abbf8a3efc97631d4a57f5d9cad62f11ad7fa Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:36:28 +0800 Subject: [PATCH 100/165] change: remove admin.plugin module. --- apioak/admin/plugin.lua | 194 ---------------------------------------- 1 file changed, 194 deletions(-) delete mode 100644 apioak/admin/plugin.lua diff --git a/apioak/admin/plugin.lua b/apioak/admin/plugin.lua deleted file mode 100644 index e951035..0000000 --- a/apioak/admin/plugin.lua +++ /dev/null @@ -1,194 +0,0 @@ -local db = require("apioak.db") -local pdk = require("apioak.pdk") -local schema = require("apioak.schema") -local controller = require("apioak.admin.controller") - -local plugin_controller = controller.new("plugin") - -function plugin_controller.plugin_list() - - plugin_controller.user_authenticate() - - local plugins = pdk.plugin.loading() - - local res = {} - for i = 1, #plugins do - pdk.table.insert(res, { - name = plugins[i].name, - type = plugins[i].type, - description = plugins[i].description, - config = plugins[i].config - }) - end - - pdk.response.exit(200, { err_message = "OK", plugins = res }) -end - -function plugin_controller.project_list(params) - - plugin_controller.check_schema(schema.plugin.project_list, params) - - plugin_controller.user_authenticate() - - if not plugin_controller.is_owner then - plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) - end - - local res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK", plugins = res }) -end - -function plugin_controller.project_created(params) - - local body = plugin_controller.get_body() - body.project_id = params.project_id - - plugin_controller.check_schema(schema.plugin.project_created, body) - - plugin_controller.user_authenticate() - - if not plugin_controller.is_owner then - local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) - if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create project plugin" }) - end - end - - local _, err = db.plugin.create_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id, body) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function plugin_controller.project_updated(params) - - local body = plugin_controller.get_body() - body.project_id = params.project_id - body.plugin_id = params.plugin_id - - plugin_controller.check_schema(schema.plugin.project_updated, body) - - plugin_controller.user_authenticate() - - if not plugin_controller.is_owner then - local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) - if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to update project plugin" }) - end - end - - local _, err = db.plugin.update(params.plugin_id, body) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function plugin_controller.project_deleted(params) - - plugin_controller.check_schema(schema.plugin.project_deleted, params) - - plugin_controller.user_authenticate() - - if not plugin_controller.is_owner then - local role = plugin_controller.project_authenticate(params.project_id, plugin_controller.uid) - if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to delete project plugin" }) - end - end - - local _, err = db.plugin.delete(params.plugin_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function plugin_controller.router_list(params) - - plugin_controller.check_schema(schema.plugin.router_list, params) - - plugin_controller.user_authenticate() - - if not plugin_controller.is_owner then - plugin_controller.router_authenticate(params.router_id, plugin_controller.uid) - end - - local res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK", plugins = res }) -end - -function plugin_controller.router_created(params) - - local body = plugin_controller.get_body() - body.router_id = params.router_id - - plugin_controller.check_schema(schema.plugin.router_created, body) - - plugin_controller.user_authenticate() - - if not plugin_controller.is_owner then - plugin_controller.router_authenticate(params.router_id, plugin_controller.uid) - end - - local _, err = db.plugin.create_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id, body) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function plugin_controller.router_updated(params) - - local body = plugin_controller.get_body() - body.router_id = params.router_id - body.plugin_id = params.plugin_id - - plugin_controller.check_schema(schema.plugin.router_updated, body) - - plugin_controller.user_authenticate() - - if not plugin_controller.is_owner then - plugin_controller.router_authenticate(params.router_id, plugin_controller.uid) - end - - local _, err = db.plugin.update(params.plugin_id, body) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function plugin_controller.router_deleted(params) - - plugin_controller.check_schema(schema.plugin.router_deleted, params) - - plugin_controller.user_authenticate() - - if not plugin_controller.is_owner then - plugin_controller.router_authenticate(params.router_id, plugin_controller.uid) - end - - local _, err = db.plugin.delete(params.plugin_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -return plugin_controller -- Gitee From 052afb70fdb23e9d706460c49afa466c914bb53a Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:37:11 +0800 Subject: [PATCH 101/165] change: update admin.project functions. --- apioak/admin/project.lua | 222 +++++++++++++++++++++++++++++++-------- 1 file changed, 177 insertions(+), 45 deletions(-) diff --git a/apioak/admin/project.lua b/apioak/admin/project.lua index 270fb19..a1d4f49 100644 --- a/apioak/admin/project.lua +++ b/apioak/admin/project.lua @@ -5,25 +5,6 @@ local controller = require("apioak.admin.controller") local project_controller = controller.new("project") -function project_controller.list() - - project_controller.user_authenticate() - - local res, err - if project_controller.is_owner then - res, err = db.project.all(true) - else - res, err = db.project.query_by_uid(project_controller.uid) - end - - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK", projects = res }) -end - - function project_controller.created() local body = project_controller.get_body() @@ -36,26 +17,42 @@ function project_controller.created() if err then pdk.response.exit(500, { err_message = err }) end + local project_id = res.insert_id + if project_id == 0 then + pdk.response.exit(200, { err_message = "FAIL" }) + end for i = 1, #body.upstreams do local upstream = body.upstreams[i] upstream.project_id = project_id res, err = db.upstream.create(upstream) if err then + db.project.delete(project_id) + + db.upstream.delete_by_pid(project_id) + pdk.response.exit(500, { err_message = err }) end end res, err = db.role.create(project_id, project_controller.uid, 1) if err then + + db.project.delete(project_id) + + db.upstream.delete_by_pid(project_id) + pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + if res.insert_id == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end - function project_controller.updated(params) local body = project_controller.get_body() @@ -68,7 +65,7 @@ function project_controller.updated(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to update project" }) + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) end end @@ -86,11 +83,14 @@ function project_controller.updated(params) end end - pdk.response.exit(200, { err_message = "OK" }) + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end - -function project_controller.query(params) +function project_controller.selected(params) project_controller.check_schema(schema.project.query, params) @@ -106,7 +106,7 @@ function project_controller.query(params) end if #res == 0 then - pdk.response.exit(500, { err_message = "project: " .. params.project_id .. "not exists" }) + pdk.response.exit(403, { err_message = "[project.authenticate] project not exists" }) end local project = res[1] @@ -119,7 +119,6 @@ function project_controller.query(params) pdk.response.exit(200, { err_message = "OK", project = project }) end - function project_controller.deleted(params) project_controller.check_schema(schema.project.deleted, params) @@ -129,7 +128,7 @@ function project_controller.deleted(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to delete project" }) + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) end end @@ -139,7 +138,7 @@ function project_controller.deleted(params) end if #res > 0 then - pdk.response.exit(401, { err_message = "routers in project were not deleted" }) + pdk.response.exit(403, { err_message = "[project.authenticate] routers in project were not deleted" }) end res, err = db.plugin.delete_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id) @@ -162,10 +161,13 @@ function project_controller.deleted(params) pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end - function project_controller.members(params) project_controller.check_schema(schema.project.members, params) @@ -184,7 +186,6 @@ function project_controller.members(params) pdk.response.exit(200, { err_message = "OK", users = res }) end - function project_controller.member_created(params) local body = project_controller.get_body() @@ -197,19 +198,26 @@ function project_controller.member_created(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to create project member" }) + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) end end - local _, err = db.role.create(body.project_id, body.user_id, body.is_admin) + if pdk.string.tonumber(body.user_id) == project_controller.uid then + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + end + + local res, err = db.role.create(body.project_id, body.user_id, body.is_admin) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + if res.insert_id == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end - function project_controller.member_deleted(params) project_controller.check_schema(schema.project.member_deleted, params) @@ -219,23 +227,26 @@ function project_controller.member_deleted(params) if not project_controller.is_owner then local user_group = project_controller.project_authenticate(params.project_id, project_controller.uid) if user_group.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to delete group member" }) + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) end end if pdk.string.tonumber(params.user_id) == project_controller.uid then - pdk.response.exit(401, { err_message = "no permission to delete this member" }) + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) end - local _, err = db.role.delete(params.project_id, params.user_id) + local res, err = db.role.delete(params.project_id, params.user_id) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end - function project_controller.member_updated(params) local body = project_controller.get_body() @@ -249,21 +260,142 @@ function project_controller.member_updated(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(401, { err_message = "no permission to add project member admin" }) + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) end end if pdk.string.tonumber(params.user_id) == project_controller.uid then - pdk.response.exit(401, { err_message = "no permission to add project member admin" }) + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + end + + local res, err = db.role.update(params.project_id, params.user_id, body.is_admin) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function project_controller.plugins(params) + + project_controller.check_schema(schema.project.plugins, params) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + project_controller.project_authenticate(params.project_id, project_controller.uid) end - local _, err = db.role.update(params.project_id, params.user_id, body.is_admin) + local res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + pdk.response.exit(200, { err_message = "OK", plugins = res }) end +function project_controller.plugin_created(params) + + local body = project_controller.get_body() + body.project_id = params.project_id + + project_controller.check_schema(schema.project.plugin_created, body) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + local role = project_controller.project_authenticate(params.project_id, project_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + end + end + + local res, err = db.plugin.create_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.insert_id == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function project_controller.plugin_updated(params) + + local body = project_controller.get_body() + body.project_id = params.project_id + body.plugin_id = params.plugin_id + + project_controller.check_schema(schema.project.plugin_updated, body) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + local role = project_controller.project_authenticate(params.project_id, project_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + end + end + + local res, err = db.plugin.update_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id, params.plugin_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function project_controller.plugin_deleted(params) + + project_controller.check_schema(schema.project.plugin_deleted, params) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + local role = project_controller.project_authenticate(params.project_id, project_controller.uid) + if role.is_admin ~= 1 then + pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + end + end + + local res, err = db.plugin.delete_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id, params.plugin_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function project_controller.routers(params) + + project_controller.check_schema(schema.project.routers, params) + + project_controller.user_authenticate() + + if not project_controller.is_owner then + project_controller.project_authenticate(params.project_id, project_controller.uid) + end + + local res, err = db.router.query_by_pid(params.project_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", routers = res }) +end return project_controller -- Gitee From 0b0b5bb649a5f5b20b10ce28db6d94ccce9c51b0 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:37:48 +0800 Subject: [PATCH 102/165] change: update admin.router functions. --- apioak/admin/router.lua | 153 ++++++++++++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 30 deletions(-) diff --git a/apioak/admin/router.lua b/apioak/admin/router.lua index bbb5795..29a5de8 100644 --- a/apioak/admin/router.lua +++ b/apioak/admin/router.lua @@ -5,24 +5,6 @@ local controller = require("apioak.admin.controller") local router_controller = controller.new("router") -function router_controller.list(params) - - router_controller.check_schema(schema.router.list, params) - - router_controller.user_authenticate() - - if not router_controller.is_owner then - router_controller.project_authenticate(params.project_id, router_controller.uid) - end - - local res, err = db.router.query_by_pid(params.project_id) - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK", routers = res }) -end - function router_controller.created() local body = router_controller.get_body() @@ -35,12 +17,16 @@ function router_controller.created() router_controller.project_authenticate(body.project_id, router_controller.uid) end - local _, err = db.router.created(body) + local res, err = db.router.created(body) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + if res.insert_id == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end function router_controller.query(params) @@ -78,12 +64,16 @@ function router_controller.updated(params) router_controller.router_authenticate(params.router_id, router_controller.uid) end - local _, err = db.router.updated(params.router_id, body) + local res, err = db.router.updated(params.router_id, body) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end function router_controller.deleted(params) @@ -96,17 +86,21 @@ function router_controller.deleted(params) router_controller.router_authenticate(params.router_id, router_controller.uid) end - local _, err = db.router.deleted(params.router_id) + local res, err = db.router.deleted(params.router_id) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end function router_controller.env_push(params) - router_controller.check_schema(schema.router.online, params) + router_controller.check_schema(schema.router.env_push, params) router_controller.user_authenticate() if not router_controller.is_owner then @@ -134,29 +128,128 @@ function router_controller.env_push(params) end router.plugins = plugins - res, err = db.router.online(params.router_id, pdk.string.upper(params.env), router) + res, err = db.router.env_push(params.router_id, pdk.string.upper(params.env), router) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end function router_controller.env_pull(params) - router_controller.check_schema(schema.router.offline, params) + router_controller.check_schema(schema.router.env_pull, params) + + router_controller.user_authenticate() + if not router_controller.is_owner then + router_controller.router_authenticate(params.router_id, router_controller.uid) + end + + local res, err = db.router.env_pull(params.router_id, pdk.string.upper(params.env)) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function router_controller.plugins(params) + + router_controller.check_schema(schema.router.plugins, params) router_controller.user_authenticate() + if not router_controller.is_owner then router_controller.router_authenticate(params.router_id, router_controller.uid) end - local _, err = db.router.offline(params.router_id, pdk.string.upper(params.env)) + local res, err = db.plugin.query_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + pdk.response.exit(200, { err_message = "OK", plugins = res }) +end + +function router_controller.plugin_created(params) + + local body = router_controller.get_body() + body.router_id = params.router_id + + router_controller.check_schema(schema.router.plugin_created, body) + + router_controller.user_authenticate() + + if not router_controller.is_owner then + router_controller.router_authenticate(params.router_id, router_controller.uid) + end + + local res, err = db.plugin.create_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.insert_id == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function router_controller.plugin_updated(params) + + local body = router_controller.get_body() + body.router_id = params.router_id + body.plugin_id = params.plugin_id + + router_controller.check_schema(schema.router.plugin_updated, body) + + router_controller.user_authenticate() + + if not router_controller.is_owner then + router_controller.router_authenticate(params.router_id, router_controller.uid) + end + + local res, err = db.plugin.update_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id, params.plugin_id, body) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end +end + +function router_controller.plugin_deleted(params) + + router_controller.check_schema(schema.router.plugin_deleted, params) + + router_controller.user_authenticate() + + if not router_controller.is_owner then + router_controller.router_authenticate(params.router_id, router_controller.uid) + end + + local res, err = db.plugin.delete_by_res(db.plugin.RESOURCES_TYPE_ROUTER, params.router_id, params.plugin_id) + if err then + pdk.response.exit(500, { err_message = err }) + end + + if res.affected_rows == 0 then + pdk.response.exit(403, { err_message = "FAIL" }) + else + pdk.response.exit(200, { err_message = "OK" }) + end end return router_controller -- Gitee From 8cc57769608a37bb3cad9adf511688810e48a0d8 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:47:13 +0800 Subject: [PATCH 103/165] change: update admin.user functions. --- apioak/admin/user.lua | 169 +++++++++--------------------------------- 1 file changed, 35 insertions(+), 134 deletions(-) diff --git a/apioak/admin/user.lua b/apioak/admin/user.lua index ed702f0..9887875 100644 --- a/apioak/admin/user.lua +++ b/apioak/admin/user.lua @@ -5,134 +5,58 @@ local controller = require("apioak.admin.controller") local user_controller = controller.new("user") -function user_controller.register() +function user_controller.created() + + user_controller.user_authenticate() local body = user_controller.get_body() - user_controller.check_schema(schema.user.register, body) + user_controller.check_schema(schema.user.created, body) - local res, err = db.user.all() - if err then - pdk.response.exit(500, { err_message = err }) + if body.password ~= body.valid_password then + pdk.response.exit(403, { err_message = "[user.authenticate] inconsistent password entry" }) end - if #res == 0 then - body.is_owner = 1 - body.is_enable = 1 + if not user_controller.is_owner then + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end - if body.password ~= body.valid_password then - pdk.response.exit(401, { err_message = "inconsistent password entry" }) - end + local res, err = db.user.create(body) - res, err = db.user.create(body) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + pdk.response.exit(200, { err_message = "OK", res = res }) end -function user_controller.login() - - local body = user_controller.get_body() - - user_controller.check_schema(schema.user.login, body) - - local res, err = db.user.query_by_email(body.email) - if err then - pdk.response.exit(500, { err_message = err }) - end - - if #res == 0 then - pdk.response.exit(401, { err_message = pdk.string.format( - "request login user \"%s\" not exists", body.email) }) - end +function user_controller.deleted(params) - local user = res[1] - if pdk.string.md5(body.password) ~= user.password then - pdk.response.exit(401, { err_message = pdk.string.format( - "request login user \"%s\" password error", body.email) }) - end + user_controller.check_schema(schema.user.deleted, params) - if user.is_enable == 0 then - pdk.response.exit(401, { err_message = - "user account is not enabled, please contact the administrator" }) - end + user_controller.user_authenticate() - res, err = db.token.query_by_uid(user.id) - if err then - pdk.response.exit(500, { err_message = err }) + if not user_controller.is_owner then + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end - if #res == 0 then - res, err = db.token.create_by_uid(user.id) - else - res, err = db.token.update_by_uid(user.id) + if pdk.string.tonumber(params.user_id) == user_controller.uid then + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end + local res, err = db.role.delete_by_uid(params.user_id) if err then pdk.response.exit(500, { err_message = err }) end - user.token = res.token - - pdk.response.exit(200, { err_message = "OK" , user = { - id = user.id, - name = user.name, - token = user.token, - is_owner = user.is_owner - }}) -end - -function user_controller.logout() - - user_controller.user_authenticate() - - local _, err = db.token.expire_by_token(user_controller.token) + res, err = db.user.delete(params.user_id) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) -end - -function user_controller.list() - - user_controller.user_authenticate() - local res, err = db.user.all() - - if err then - pdk.response.exit(500, { err_message = err }) - end - pdk.response.exit(200, { err_message = "OK", users = res }) + pdk.response.exit(200, { err_message = "OK", res = res }) end -function user_controller.created() - - user_controller.user_authenticate() - - local body = user_controller.get_body() - - user_controller.check_schema(schema.user.created, body) - - if body.password ~= body.valid_password then - pdk.response.exit(401, { err_message = "inconsistent password entry" }) - end - - if not user_controller.is_owner then - pdk.response.exit(401, { err_message = "no permission to create user" }) - end - - local _, err = db.user.create(body) - - if err then - pdk.response.exit(500, { err_message = err }) - end - pdk.response.exit(200, { err_message = "OK" }) -end - - function user_controller.enable(params) user_controller.check_schema(schema.user.enable, params) @@ -140,20 +64,20 @@ function user_controller.enable(params) user_controller.user_authenticate() if not user_controller.is_owner then - pdk.response.exit(401, { err_message = "no permission to enable user" }) + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end if pdk.string.tonumber(params.user_id) == user_controller.uid then - pdk.response.exit(401, { err_message = "no permission to enable user" }) + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end - local _, err = db.user.update_status(params.user_id, 1) + local res, err = db.user.update_status(params.user_id, 1) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + pdk.response.exit(200, { err_message = "OK", res = res }) end function user_controller.disable(params) @@ -163,20 +87,20 @@ function user_controller.disable(params) user_controller.user_authenticate() if not user_controller.is_owner then - pdk.response.exit(401, { err_message = "no permission to disable user" }) + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end if pdk.string.tonumber(params.user_id) == user_controller.uid then - pdk.response.exit(401, { err_message = "no permission to disable user" }) + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end - local _, err = db.user.update_status(params.user_id, 0) + local res, err = db.user.update_status(params.user_id, 0) if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + pdk.response.exit(200, { err_message = "OK", res = res }) end function user_controller.password(params) @@ -188,48 +112,25 @@ function user_controller.password(params) user_controller.user_authenticate() - if not user_controller.is_owner and params.user_id ~= user_controller.uid then - pdk.response.exit(401, { err_message = "no permission to update user password" }) - end - - if body.password ~= body.valid_password then - pdk.response.exit(401, { err_message = "inconsistent password entry" }) - end - - local _, err = db.user.update_password(params.user_id, body.password) - - if err then - pdk.response.exit(500, { err_message = err }) - end - - pdk.response.exit(200, { err_message = "OK" }) -end - -function user_controller.deleted(params) - - user_controller.check_schema(schema.user.deleted, params) - - user_controller.user_authenticate() - if not user_controller.is_owner then - pdk.response.exit(401, { err_message = "no permission to delete user" }) + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end if pdk.string.tonumber(params.user_id) == user_controller.uid then - pdk.response.exit(401, { err_message = "no permission to delete user" }) + pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) end - local res, err = db.role.delete_by_uid(params.user_id) - if err then - pdk.response.exit(500, { err_message = err }) + if body.password ~= body.valid_password then + pdk.response.exit(403, { err_message = "[user.authenticate] inconsistent password entry" }) end - res, err = db.user.delete(params.user_id) + local res, err = db.user.update_password(params.user_id, body.password) + if err then pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK" }) + pdk.response.exit(200, { err_message = "OK", res = res }) end return user_controller -- Gitee From da4577e711fc3893fbd611d0e08380e3781de97c Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:47:48 +0800 Subject: [PATCH 104/165] change: update db.plugin functions. --- apioak/db/plugin.lua | 58 ++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/apioak/db/plugin.lua b/apioak/db/plugin.lua index b16b4d4..c6b5451 100644 --- a/apioak/db/plugin.lua +++ b/apioak/db/plugin.lua @@ -26,17 +26,6 @@ function _M.query_by_res(res_type, res_id) return res, nil end -function _M.delete_by_res(res_type, res_id) - local sql = pdk.string.format("DELETE FROM %s WHERE res_type = '%s' AND res_id = %s", - table_name, res_type, res_id) - local res, err = pdk.mysql.execute(sql) - - if err then - return nil, err - end - return res, nil -end - function _M.create_by_res(res_type, res_id, params) local sql = pdk.string.format("INSERT INTO %s (name, type, description, config, res_type, res_id) VALUES ('%s', '%s', '%s', '%s', '%s', '%s')", table_name, params.name, params.type, params.description, pdk.json.encode(params.config), res_type, res_id) @@ -48,20 +37,53 @@ function _M.create_by_res(res_type, res_id, params) return res, nil end -function _M.delete(plugin_id) - local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", - table_name, plugin_id) - local res, err = pdk.mysql.execute(sql) +function _M.delete_by_res(res_type, res_id, plugin_id) + local sql + if plugin_id then + sql = pdk.string.format([[ + DELETE + FROM + %s + WHERE + id = %s AND res_id = %s AND res_type = '%s' + ]], table_name, plugin_id, res_id, res_type) + else + sql = pdk.string.format([[ + DELETE + FROM + %s + WHERE + res_id = %s AND res_type = '%s' + ]], table_name, res_id, res_type) + end + local res, err = pdk.mysql.execute(sql) if err then return nil, err end + return res, nil end -function _M.update(plugin_id, params) - local sql = pdk.string.format("UPDATE %s SET name = '%s', type = '%s', description = '%s', config = '%s' WHERE id = %s", - table_name, params.name, params.type, params.description, pdk.json.encode(params.config), plugin_id) +function _M.update_by_res(res_type, res_id, plugin_id, params) + local sql = pdk.string.format([[ + UPDATE + %s + SET + name= '%s', + type = '%s', + description = '%s', + config = '%s' + WHERE + id = %s AND res_id = %s AND res_type = '%s' + ]], table_name, + params.name, + params.type, + params.description, + pdk.json.encode(params.config), + plugin_id, + res_id, + res_type) local res, err = pdk.mysql.execute(sql) if err then -- Gitee From dca4bc6d4f0edb990d62b6fa06a14f6aa82384be Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:48:05 +0800 Subject: [PATCH 105/165] change: update db.project functions. --- apioak/db/project.lua | 96 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 9 deletions(-) diff --git a/apioak/db/project.lua b/apioak/db/project.lua index 79a20b8..c7e3a8e 100644 --- a/apioak/db/project.lua +++ b/apioak/db/project.lua @@ -9,15 +9,39 @@ local _M = {} _M.table_name = table_name -function _M.all(is_owner) +function _M.all(project_name) local sql - if is_owner then - sql = pdk.string.format("SELECT *, 1 AS is_admin FROM %s", table_name) + if project_name and pdk.string.len(project_name) >= 1 then + sql = pdk.string.format([[ + SELECT + id, + name, + path, + description + FROM + %s + WHERE + name LIKE '%%%s%%' + ORDER BY + id + DESC + ]], table_name, project_name) else - sql = pdk.string.format("SELECT * FROM %s", table_name) + sql = pdk.string.format([[ + SELECT + id, + name, + path, + description + FROM + %s + ORDER BY + id + DESC + ]], table_name) end - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.mysql.execute(sql) if err then return nil, err end @@ -25,10 +49,49 @@ function _M.all(is_owner) return res, nil end -function _M.query_by_uid(user_id) - local sql = pdk.string.format( - "SELECT projects.*, roles.is_admin FROM %s as roles, %s AS projects WHERE roles.project_id = projects.id AND roles.user_id = %s", - role.table_name, table_name, user_id) +function _M.query_by_uid(user_id, project_name) + local sql + if project_name and pdk.string.len(project_name) >= 1 then + sql = pdk.string.format([[ + SELECT + projects.id, + projects.name, + projects.path, + projects.description, + roles.is_admin + FROM + %s AS projects + LEFT JOIN + %s AS roles + ON + projects.id = roles.project_id + WHERE + roles.user_id = %s AND projects.name LIKE '%%%s%%' + ORDER BY + projects.id + DESC + ]], table_name, role.table_name, user_id, project_name) + else + sql = pdk.string.format([[ + SELECT + projects.id, + projects.name, + projects.path, + projects.description, + roles.is_admin + FROM + %s AS projects + LEFT JOIN + %s AS roles + ON + projects.id = roles.project_id + WHERE + roles.user_id = %s + ORDER BY + projects.id + DESC + ]], table_name, role.table_name, user_id) + end local res, err = pdk.mysql.execute(sql) if err then @@ -121,4 +184,19 @@ function _M.query_env_all() return projects, nil end +function _M.query_last_updated_hid() + local sql = pdk.string.format( + "SELECT MD5(updated_at) AS hash_id FROM %s ORDER BY updated_at DESC LIMIT 1", table_name) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + if #res == 0 then + return res, nil + end + + return res[1], nil +end + return _M -- Gitee From 835b147309ca4b2274119682eff82f6ea3da6fca Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:48:22 +0800 Subject: [PATCH 106/165] change: update db.router functions. --- apioak/db/router.lua | 314 +++++++++++++++++++++++++++++++++---------- 1 file changed, 241 insertions(+), 73 deletions(-) diff --git a/apioak/db/router.lua b/apioak/db/router.lua index 442075d..74eef23 100644 --- a/apioak/db/router.lua +++ b/apioak/db/router.lua @@ -1,4 +1,7 @@ -local pdk = require("apioak.pdk") +local pdk = require("apioak.pdk") +local role = require("apioak.db.role") +local project = require("apioak.db.project") + local table_name = "oak_routers" @@ -6,48 +9,184 @@ local _M = {} _M.table_name = table_name -function _M.query_by_pid(project_id) - local sql = "SELECT id, name, enable_cors, description, request_path, request_method, env_prod_config, " .. - "env_beta_config, env_test_config FROM %s WHERE project_id = %s" - sql = pdk.string.format(sql, table_name, project_id) +function _M.all(router_name) + local sql + if router_name and pdk.string.len(router_name) >= 1 then + sql = pdk.string.format([[ + SELECT + routers.id, + routers.name, + routers.description, + routers.request_path, + routers.request_method, + projects.path AS project_path, + projects.name AS project_name, + projects.id AS project_id, + !ISNULL(routers.env_prod_config) AS env_prod_publish, + !ISNULL(routers.env_beta_config) AS env_beta_publish, + !ISNULL(routers.env_test_config) AS env_test_publish + FROM %s routers + LEFT JOIN %s projects ON routers.project_id = projects.id + WHERE + routers.name LIKE '%%%s%%' + ORDER BY + routers.id + DESC + ]], table_name, project.table_name, router_name) + else + sql = pdk.string.format([[ + SELECT + routers.id, + routers.name, + routers.description, + routers.request_path, + routers.request_method, + projects.path AS project_path, + projects.name AS project_name, + projects.id AS project_id, + !ISNULL(routers.env_prod_config) AS env_prod_publish, + !ISNULL(routers.env_beta_config) AS env_beta_publish, + !ISNULL(routers.env_test_config) AS env_test_publish + FROM %s routers + LEFT JOIN %s projects ON routers.project_id = projects.id + ORDER BY + routers.id + DESC + ]], table_name, project.table_name) + end + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + return res, nil +end +function _M.query_by_uid(user_id, router_name) + local res, err = role.query_by_uid(user_id) if err then return nil, err end + local project_ids = pdk.table.new(50, 0) for i = 1, #res do - if res[i].env_prod_config == pdk.string.null then - res[i].env_prod_publish = 0 - else - res[i].env_prod_publish = 1 - end - res[i].env_prod_config = nil - - if res[i].env_beta_config == pdk.string.null then - res[i].env_beta_publish = 0 - else - res[i].env_beta_publish = 1 - end - res[i].env_beta_config = nil - - if res[i].env_test_config == pdk.string.null then - res[i].env_test_publish = 0 - else - res[i].env_test_publish = 1 - end - res[i].env_test_config = nil + pdk.table.insert(project_ids, res[i].project_id) + end + + if #project_ids == 0 then + return pdk.table.new(0, 0), nil + end + + local sql + if router_name and pdk.string.len(router_name) >= 1 then + sql = pdk.string.format([[ + SELECT + routers.id, + routers.name, + routers.description, + routers.request_path, + routers.request_method, + projects.path AS project_path, + projects.name AS project_name, + projects.id AS project_id, + !ISNULL(routers.env_prod_config) AS env_prod_publish, + !ISNULL(routers.env_beta_config) AS env_beta_publish, + !ISNULL(routers.env_test_config) AS env_test_publish + FROM + %s AS routers + LEFT JOIN + %s AS projects + ON + routers.project_id = projects.id + WHERE + projects.id IN (%s) AND routers.name LIKE '%%%s%%' + ORDER BY + routers.id + DESC + ]], table_name, project.table_name, pdk.table.concat(project_ids, ","), router_name) + else + sql = pdk.string.format([[ + SELECT + routers.id, + routers.name, + routers.description, + routers.request_path, + routers.request_method, + projects.path AS project_path, + projects.name AS project_name, + projects.id AS project_id, + !ISNULL(routers.env_prod_config) AS env_prod_publish, + !ISNULL(routers.env_beta_config) AS env_beta_publish, + !ISNULL(routers.env_test_config) AS env_test_publish + FROM + %s AS routers + LEFT JOIN + %s AS projects + ON + routers.project_id = projects.id + WHERE + projects.id IN (%s) + ORDER BY + routers.id + DESC + ]], table_name, project.table_name, pdk.table.concat(project_ids, ",")) + end + + res, err = pdk.mysql.execute(sql) + + if err then + return nil, err + end + + return res, nil +end + +function _M.query_by_pid(project_id) + local sql = pdk.string.format([[ + SELECT + routers.id, + routers.name, + routers.description, + routers.request_path, + routers.request_method, + projects.path AS project_path, + projects.name AS project_name, + projects.id AS project_id, + !ISNULL(routers.env_prod_config) AS env_prod_publish, + !ISNULL(routers.env_beta_config) AS env_beta_publish, + !ISNULL(routers.env_test_config) AS env_test_publish + FROM %s routers + LEFT JOIN %s projects ON routers.project_id = projects.id + WHERE + projects.id = %s + ORDER BY + routers.id + DESC + ]], table_name, project.table_name, project_id) + local res, err = pdk.mysql.execute(sql) + + if err then + return nil, err end return res, nil end function _M.query(router_id) - local sql = "SELECT id, name, enable_cors, description, request_path, request_method, request_params, " .. - "backend_path, backend_method, backend_params, constant_params, response_type, " .. - "response_success, response_failure, response_codes, response_schema, env_prod_config, env_beta_config, " .. - "env_test_config, project_id FROM %s WHERE id = %s"; - sql = pdk.string.format(sql, table_name, router_id) + local sql = pdk.string.format([[ + SELECT + id, name, enable_cors, description, constant_params, project_id, + request_path, request_method, request_params, + backend_path, backend_method, backend_params, + response_type, response_success, response_failure, response_codes, response_schema, + !ISNULL(env_prod_config) AS env_prod_publish, + !ISNULL(env_beta_config) AS env_beta_publish, + !ISNULL(env_test_config) AS env_test_publish + FROM %s + WHERE + id = %s + ]], table_name, router_id) local res, err = pdk.mysql.execute(sql) if err then @@ -60,43 +199,40 @@ function _M.query(router_id) res[i].constant_params = pdk.json.decode(res[i].constant_params) res[i].response_codes = pdk.json.decode(res[i].response_codes) res[i].response_schema = pdk.json.decode(res[i].response_schema) - - if res[i].env_prod_config == pdk.string.null then - res[i].env_prod_publish = 0 - else - res[i].env_prod_publish = 1 - end - res[i].env_prod_config = nil - - if res[i].env_beta_config == pdk.string.null then - res[i].env_beta_publish = 0 - else - res[i].env_beta_publish = 1 - end - res[i].env_beta_config = nil - - if res[i].env_test_config == pdk.string.null then - res[i].env_test_publish = 0 - else - res[i].env_test_publish = 1 - end - res[i].env_test_config = nil end return res, nil end function _M.created(params) - local sql = "INSERT INTO %s (name, enable_cors, description, request_path, request_method, request_params, " .. - "backend_path, backend_method, backend_params, constant_params, response_type, " .. - "response_success, response_failure, response_codes, response_schema, project_id) " .. - "VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', " .. - "'%s')" - sql = pdk.string.format(sql, table_name, params.name, params.enable_cors, params.description, params.request_path, - params.request_method, pdk.json.encode(params.request_params), params.backend_path, params.backend_method, - pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), - params.response_type, params.response_success, params.response_failure, pdk.json.encode(params.response_codes), - pdk.json.encode(params.response_schema), params.project_id) + local sql = pdk.string.format([[ + INSERT INTO %s ( + name, enable_cors, description, + request_path, request_method, request_params, backend_path, + backend_method, backend_params, constant_params, + response_type, response_success, response_failure, response_codes, response_schema, + project_id + ) + VALUES( + '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' + ) + ]], table_name, + params.name, + params.enable_cors, + params.description, + params.request_path, + params.request_method, + pdk.json.encode(params.request_params), + params.backend_path, + params.backend_method, + pdk.json.encode(params.backend_params), + pdk.json.encode(params.constant_params), + params.response_type, + params.response_success, + params.response_failure, + pdk.json.encode(params.response_codes), + pdk.json.encode(params.response_schema), + params.project_id) local res, err = pdk.mysql.execute(sql) if err then @@ -107,16 +243,33 @@ function _M.created(params) end function _M.updated(router_id, params) - local sql = "UPDATE %s SET name = '%s', enable_cors = '%s', description = '%s', request_path = '%s', " .. - "request_method = '%s', request_params = '%s', backend_path = '%s', backend_method = '%s', " .. - "backend_params = '%s', constant_params = '%s', response_type = '%s', " .. - "response_success = '%s', response_failure = '%s', response_codes = '%s', response_schema = '%s' ".. - "WHERE id = '%s'" - sql = pdk.string.format(sql, table_name, params.name, params.enable_cors, params.description, params.request_path, - params.request_method, pdk.json.encode(params.request_params), params.backend_path, params.backend_method, - pdk.json.encode(params.backend_params), pdk.json.encode(params.constant_params), - params.response_type, params.response_success, params.response_failure, pdk.json.encode(params.response_codes), - pdk.json.encode(params.response_schema), router_id) + local sql = pdk.string.format([[ + UPDATE %s + SET + name = '%s', enable_cors = '%s', description = '%s', + request_path = '%s', request_method = '%s', request_params = '%s', + backend_path = '%s', backend_method = '%s', backend_params = '%s', + constant_params = '%s', response_type = '%s', response_success = '%s', response_failure = '%s', + response_codes = '%s', response_schema = '%s' + WHERE + id = %s; + ]], table_name, + params.name, + params.enable_cors, + params.description, + params.request_path, + params.request_method, + pdk.json.encode(params.request_params), + params.backend_path, + params.backend_method, + pdk.json.encode(params.backend_params), + pdk.json.encode(params.constant_params), + params.response_type, + params.response_success, + params.response_failure, + pdk.json.encode(params.response_codes), + pdk.json.encode(params.response_schema), + router_id) local res, err = pdk.mysql.execute(sql) if err then @@ -136,7 +289,7 @@ function _M.deleted(router_id) return res, nil end -function _M.online(router_id, env, router_info) +function _M.env_push(router_id, env, router_info) router_info = pdk.json.encode(router_info) local sql = pdk.string.format("UPDATE %s SET env_%s_config = '%s' WHERE id = %s", table_name, pdk.string.lower(env), router_info, router_id) @@ -148,7 +301,7 @@ function _M.online(router_id, env, router_info) return res, nil end -function _M.offline(router_id, env) +function _M.env_pull(router_id, env) local sql = pdk.string.format("UPDATE %s SET env_%s_config = NULL WHERE id = %s", table_name, pdk.string.lower(env), router_id) local res, err = pdk.mysql.execute(sql) @@ -177,4 +330,19 @@ function _M.query_env_by_pid(project_id) return res, nil end +function _M.query_last_updated_hid() + local sql = pdk.string.format( + "SELECT MD5(updated_at) AS hash_id FROM %s ORDER BY updated_at DESC LIMIT 1", table_name) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + if #res == 0 then + return res, nil + end + + return res[1], nil +end + return _M -- Gitee From e5f59ec8abbf5820ecb76c4f6bc71fa9bfb3ca37 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:48:42 +0800 Subject: [PATCH 107/165] change: update db.upstream functions. --- apioak/db/upstream.lua | 64 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/apioak/db/upstream.lua b/apioak/db/upstream.lua index 66ee7fc..8cd12f6 100644 --- a/apioak/db/upstream.lua +++ b/apioak/db/upstream.lua @@ -6,20 +6,59 @@ local _M = {} _M.table_name = table_name -function _M.create(params) - local sql = pdk.string.format("INSERT INTO %s (env, host, type, project_id, enable_retries, timeouts, nodes) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s')", - table_name, params.env, params.host, params.type, params.project_id, params.enable_retries, pdk.json.encode(params.timeouts), pdk.json.encode(params.nodes)) +function _M.all() + local sql = pdk.string.format("SELECT * FROM %s", table_name) local res, err = pdk.mysql.execute(sql) if err then return nil, err end + + for i = 1, #res do + res[i].nodes = pdk.json.decode(res[i].nodes) + res[i].timeouts = pdk.json.decode(res[i].timeouts) + end + + return res, nil +end + +function _M.create(params) + local sql = pdk.string.format([[ + INSERT INTO %s ( + env, + host, + type, + project_id, + timeouts, + nodes + ) + VALUES ('%s', '%s', '%s', '%s', '%s', '%s') + ]], table_name, + params.env, + params.host, + params.type, + params.project_id, + pdk.json.encode(params.timeouts), + pdk.json.encode(params.nodes)) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + return res, nil end function _M.update(upstream_id, params) - local sql = pdk.string.format("UPDATE %s SET env = '%s', host = '%s', type = '%s', project_id = '%s', enable_retries = '%s', timeouts = '%s', nodes = '%s' WHERE id = %s", - table_name, params.env, params.host, params.type, params.project_id, params.enable_retries, pdk.json.encode(params.timeouts), pdk.json.encode(params.nodes), upstream_id) + local sql = pdk.string.format([[ + UPDATE + %s + SET + host = '%s', type = '%s', timeouts = '%s', nodes = '%s' + WHERE + id = %s + ]], table_name, params.host, params.type, + pdk.json.encode(params.timeouts), + pdk.json.encode(params.nodes), upstream_id) local res, err = pdk.mysql.execute(sql) if err then @@ -65,4 +104,19 @@ function _M.delete_by_pid(project_id) return res, nil end +function _M.query_last_updated_hid() + local sql = pdk.string.format( + "SELECT MD5(updated_at) AS hash_id FROM %s ORDER BY updated_at DESC LIMIT 1", table_name) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + if #res == 0 then + return res, nil + end + + return res[1], nil +end + return _M -- Gitee From 9487a51fac3a9d75eff980217270a95d9612ac7b Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:49:29 +0800 Subject: [PATCH 108/165] change: update pdk.const functions. --- apioak/pdk/const.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apioak/pdk/const.lua b/apioak/pdk/const.lua index 45925f8..3df874d 100644 --- a/apioak/pdk/const.lua +++ b/apioak/pdk/const.lua @@ -14,9 +14,11 @@ _M.ENVIRONMENT_BETA = "BETA" _M.ENVIRONMENT_TEST = "TEST" -_M.REQUEST_API_ENV_KEY = "APIOAK-API-ENV" +_M.REQUEST_API_ENV_KEY = "APIOAK-API-ENV" -_M.REQUEST_ADMIN_TOKEN_KEY = "APIOAK-ADMIN-TOKEN" +_M.REQUEST_ADMIN_TOKEN_KEY = "APIOAK-ADMIN-TOKEN" + +_M.RESPONSE_MOCK_REQUEST_KEY = "APIOAK-MOCK-REQUEST" _M.REQUEST_PARAM_POS_QUERY = "QUERY" @@ -26,6 +28,8 @@ _M.REQUEST_PARAM_POS_HEADER = "HEADER" _M.REQUEST_PARAM_NGX_VARIABLE = "VARIABLE" +_M.CONTENT_TYPE = "Content-Type" + _M.CONTENT_TYPE_JSON = "application/json" _M.CONTENT_TYPE_HTML = "text/html" -- Gitee From ce9c898b4cbcd4a5f601bc7752c331e685fcf4a2 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:50:09 +0800 Subject: [PATCH 109/165] change: remove pdk.plugin functions. --- apioak/pdk/config.lua | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 apioak/pdk/config.lua diff --git a/apioak/pdk/config.lua b/apioak/pdk/config.lua deleted file mode 100644 index cdb0459..0000000 --- a/apioak/pdk/config.lua +++ /dev/null @@ -1,25 +0,0 @@ -local ngx = ngx -local yaml = require("tinyyaml") -local io_open = io.open -local conf_path = ngx.config.prefix() .. "conf/apioak.yaml" - -local _M = {} - -function _M.all() - local file, err = io_open(conf_path, "r") - if err then - return nil, err - end - - local content = file:read("*a") - file:close() - - local config = yaml.parse(content) - if not config then - return nil, "config format failure" - end - - return config, nil -end - -return _M -- Gitee From 5dcc3bdf6cc9cb3906f1b9c6ddb8669a733e232a Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:50:37 +0800 Subject: [PATCH 110/165] change: update pdk.mysql functions. --- apioak/pdk/mysql.lua | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/apioak/pdk/mysql.lua b/apioak/pdk/mysql.lua index d0ffdd6..4ca755e 100644 --- a/apioak/pdk/mysql.lua +++ b/apioak/pdk/mysql.lua @@ -1,35 +1,36 @@ -local config = require("apioak.pdk.config") +local config = require("apioak.sys.config") local mysql = require("resty.mysql") -local oak_conf = config.all() -local my_conf = oak_conf.mysql - local _M = {} function _M.new() - local db - local ok - local err + local res, err = mysql:new() + if not res then + return nil, err + end + local db = res - db, err = mysql:new() - if not db then + res, err = config.query("mysql") + if err then return nil, err end + local conf = res - db:set_timeout(my_conf.timeout or 1000) + db:set_timeout(conf.timeout or 1000) - ok, err = db:connect({ - host = my_conf.host or "127.0.0.1", - port = my_conf.port or 3306, - database = my_conf.database or "apioak", - user = my_conf.user or "apioak", - password = my_conf.password or "" + res, err = db:connect({ + host = conf.host or "127.0.0.1", + port = conf.port or 3306, + database = conf.database or "apioak", + user = conf.user or "apioak", + password = conf.password or "" }) - if not ok then + if not res then return nil, err end + db.conf = conf db.close = close return db, nil end @@ -42,7 +43,7 @@ function close(self) if self.subscribed then return nil, "subscribed state" end - return sock:setkeepalive(my_conf.max_idle_timeout, my_conf.pool_size) + return sock:setkeepalive(self.conf.max_idle_timeout, self.conf.pool_size) end -- Gitee From 16102907758350b3b5f8a8f74dc818001ee323d4 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:51:22 +0800 Subject: [PATCH 111/165] change: update pdk.plugin functions. --- apioak/pdk/plugin.lua | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apioak/pdk/plugin.lua b/apioak/pdk/plugin.lua index c9eba4b..4bf59e0 100644 --- a/apioak/pdk/plugin.lua +++ b/apioak/pdk/plugin.lua @@ -1,23 +1,26 @@ -local pcall = pcall -local ipairs = ipairs -local config = require("apioak.pdk.config") -local table_insert = table.insert -local string_format = string.format +local pcall = pcall +local config = require("apioak.sys.config") +local stringx = require("apioak.pdk.string") +local tablex = require("apioak.pdk.table") local _M = {} function _M.loading() - local config_all = config.all() - local config_plugins = config_all.plugins + local res, err = config.query("plugins") + if err then + return nil, err + end + local plugins = {} - for _, config_plugin in ipairs(config_plugins) do - local plugin_path = string_format("apioak.plugin.%s", config_plugin) + for i = 1, #res do + local plugin_path = stringx.format("apioak.plugin.%s", res[i]) local ok, plugin = pcall(require, plugin_path) if ok then - table_insert(plugins, plugin) + tablex.insert(plugins, plugin) end end - return plugins + + return plugins, nil end return _M -- Gitee From 0712e96b8368f5aa8dc6c7a55419ec9008501a02 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:51:40 +0800 Subject: [PATCH 112/165] change: update pdk.response functions. --- apioak/pdk/response.lua | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/apioak/pdk/response.lua b/apioak/pdk/response.lua index b4a798a..31931a7 100644 --- a/apioak/pdk/response.lua +++ b/apioak/pdk/response.lua @@ -3,16 +3,12 @@ local type = type local ngx_say = ngx.say local ngx_exit = ngx.exit local ngx_header = ngx.header -local json_encode = require("cjson.safe").encode - -local CONTENT_TYPE = "Content-Type" - -local CONTENT_TYPE_JSON = "application/json" -local CONTENT_TYPE_HTML = "text/html" +local const = require("apioak.pdk.const") +local json = require("apioak.pdk.json") local _M = {} -function _M.exit(code, body) +function _M.exit(code, body, content_type) if code and type(code) == "number" then ngx.status = code else @@ -21,16 +17,16 @@ function _M.exit(code, body) if body then if type(body) == "table" then - local json_body, err = json_encode(body) + local res, err = json.encode(body) if err then - ngx_header[CONTENT_TYPE] = CONTENT_TYPE_HTML + ngx_header[const.CONTENT_TYPE] = const.CONTENT_TYPE_HTML ngx_say(err) else - ngx_header[CONTENT_TYPE] = CONTENT_TYPE_JSON - ngx_say(json_body) + ngx_header[const.CONTENT_TYPE] = content_type or const.CONTENT_TYPE_JSON + ngx_say(res) end else - ngx_header[CONTENT_TYPE] = CONTENT_TYPE_HTML + ngx_header[const.CONTENT_TYPE] = content_type or const.CONTENT_TYPE_HTML ngx_say(body) end end @@ -46,7 +42,7 @@ function _M.say(code, body) else code = nil end - ngx_header[CONTENT_TYPE] = CONTENT_TYPE_HTML + ngx_header[const.CONTENT_TYPE] = const.CONTENT_TYPE_HTML ngx_say(body) end -- Gitee From d5299bc885724ab0058f9e03f42e39ac84f838bb Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:52:05 +0800 Subject: [PATCH 113/165] change: update pdk.table functions. --- apioak/pdk/table.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apioak/pdk/table.lua b/apioak/pdk/table.lua index e6f9b46..90daf02 100644 --- a/apioak/pdk/table.lua +++ b/apioak/pdk/table.lua @@ -1,3 +1,4 @@ +local pairs = pairs local ipairs = ipairs local _M = {} @@ -21,4 +22,13 @@ _M.has = function(val, tab) return false end +_M.del = function(tab, key) + for tab_key, _ in pairs(tab) do + if tab_key == key then + tab[tab_key] = nil + end + end + return tab +end + return _M -- Gitee From ebe6f916186f2f03108f93a0c47b69812f0fda84 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:52:19 +0800 Subject: [PATCH 114/165] change: update pdk.time functions. --- apioak/pdk/time.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apioak/pdk/time.lua b/apioak/pdk/time.lua index 3cabeb1..f4d1a36 100644 --- a/apioak/pdk/time.lua +++ b/apioak/pdk/time.lua @@ -4,12 +4,17 @@ _M.time = os.time _M.date = os.date -_M.strtotime = function(date_str) - local _, _, year, month, day, hour, min, sec = string.find(date_str, +_M.strtotime = function(date) + if not date or date == ngx.null then + return 0, "params \"date\" invalid" + end + + local _, _, year, month, day, hour, min, sec = string.find(date, "(%d+)-(%d+)-(%d+)%s*(%d+):(%d+):(%d+)") if not year or not month or not day or not hour or not min or not sec then - return nil, "params \"date_str\" invalid" + return 0, "params \"date\" invalid" end + return os.time({ year = year, month = month, day = day, hour = hour, min = min, sec = sec }) end -- Gitee From 5efef878c8aa59696491048dc440b7fdb320486c Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:53:53 +0800 Subject: [PATCH 115/165] change: update jwt auth plugin format. --- apioak/plugin/jwt-auth.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apioak/plugin/jwt-auth.lua b/apioak/plugin/jwt-auth.lua index ea4c73c..42270d9 100644 --- a/apioak/plugin/jwt-auth.lua +++ b/apioak/plugin/jwt-auth.lua @@ -25,6 +25,7 @@ local schema = { }, required = { "secret" } } + local function jwt_auth(secret, credential) local obj = jwt.verify(_M.key, secret, credential) if obj.verified then @@ -68,6 +69,4 @@ function _M.http_access(oak_ctx) end end - - return _M -- Gitee From f1a9720e97ad608cb22e5d31c95a8c4785054128 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:54:05 +0800 Subject: [PATCH 116/165] change: update key auth plugin format. --- apioak/plugin/key-auth.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apioak/plugin/key-auth.lua b/apioak/plugin/key-auth.lua index c1a8318..7f2fbf4 100644 --- a/apioak/plugin/key-auth.lua +++ b/apioak/plugin/key-auth.lua @@ -32,12 +32,12 @@ function _M.http_access(oak_ctx) if plugin_conf.secret then local secret_key = pdk.request.header('Authentication') if not secret_key then - pdk.response.exit(401, { err_message = "Missing Authentication found in request" }) + pdk.response.exit(403, { err_message = "Missing Authentication found in request" }) end local verify = key_verify(secret_key, plugin_conf.secret) if not verify then - pdk.response.exit(401, { err_message = "Invalid Authentication in request" }) + pdk.response.exit(403, { err_message = "Invalid Authentication in request" }) end end end -- Gitee From 1baed126967d59d45ec9d51eac10d72c119e912d Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:55:02 +0800 Subject: [PATCH 117/165] change: update schema.account check rule. --- apioak/schema/account.lua | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 apioak/schema/account.lua diff --git a/apioak/schema/account.lua b/apioak/schema/account.lua new file mode 100644 index 0000000..33d16d2 --- /dev/null +++ b/apioak/schema/account.lua @@ -0,0 +1,45 @@ +local _M = {} + +_M.register = { + type = "object", + properties = { + name = { + type = 'string', + minLength = 4, + maxLength = 16, + }, + password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + valid_password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + email = { + type = 'string', + pattern = "^[a-zA-Z0-9\\_\\-\\.]+\\@[a-zA-Z0-9_-]+\\.[a-zA-Z\\.]+$" + } + }, + required = { "name", "password", "valid_password", "email" } +} + +_M.login = { + type = "object", + properties = { + password = { + type = 'string', + minLength = 6, + maxLength = 20, + }, + email = { + type = 'string', + pattern = "^[a-zA-Z0-9\\_\\-\\.]+\\@[a-zA-Z0-9_-]+\\.[a-zA-Z\\.]+$" + } + }, + required = { "password", "email" } +} + +return _M -- Gitee From f45a655e5032e4102452d935121434eb8d05033e Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:55:42 +0800 Subject: [PATCH 118/165] change: update schema.common check rule. --- apioak/schema/common.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 apioak/schema/common.lua diff --git a/apioak/schema/common.lua b/apioak/schema/common.lua new file mode 100644 index 0000000..771dd69 --- /dev/null +++ b/apioak/schema/common.lua @@ -0,0 +1,19 @@ +local _M = {} + +_M.projects = { + q = { + type = "string", + minLength = 1, + maxLength = 50, + } +} + +_M.routers = { + q = { + type = "string", + minLength = 1, + maxLength = 50, + } +} + +return _M -- Gitee From 969847f1e657e21738b92fe3fa7b0d13a5c1a4b2 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:55:59 +0800 Subject: [PATCH 119/165] change: update schema.project check rule. --- apioak/schema/project.lua | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apioak/schema/project.lua b/apioak/schema/project.lua index e78e897..932cdb9 100644 --- a/apioak/schema/project.lua +++ b/apioak/schema/project.lua @@ -102,10 +102,6 @@ _M.updated = { type = "string", enum = { "PROD", "BETA", "TEST" } }, - enable_retries = { - type = "number", - enum = { 0, 1 } - }, timeouts = { type = "object", properties = { @@ -152,7 +148,7 @@ _M.updated = { } } }, - required = { "id", "host", "type", "env", "enable_retries", "timeouts", "nodes" } + required = { "id", "host", "type", "env", "timeouts", "nodes" } }, } }, @@ -197,10 +193,6 @@ _M.created = { type = "string", enum = { "PROD", "BETA", "TEST" } }, - enable_retries = { - type = "number", - enum = { 0, 1 } - }, timeouts = { type = "object", properties = { @@ -247,14 +239,14 @@ _M.created = { } } }, - required = { "host", "type", "env", "enable_retries", "timeouts", "nodes" } + required = { "host", "type", "env", "timeouts", "nodes" } }, } }, required = { "name", "path", "upstreams", "description" } } -_M.plugin_list = { +_M.plugins = { type = "object", properties = { project_id = { @@ -521,4 +513,23 @@ _M.member_updated = { required = { "project_id", "user_id", "is_admin" } } +_M.routers = { + type = "object", + properties = { + project_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + return _M -- Gitee From 6149f444a8d046caf2618ac235a467fbb07b0153 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:56:20 +0800 Subject: [PATCH 120/165] change: update schema.router check rule. --- apioak/schema/router.lua | 163 ++++++++++++++++++++++++++++++++++----- 1 file changed, 142 insertions(+), 21 deletions(-) diff --git a/apioak/schema/router.lua b/apioak/schema/router.lua index c2d2acd..7bcef4a 100644 --- a/apioak/schema/router.lua +++ b/apioak/schema/router.lua @@ -1,24 +1,5 @@ local _M = {} -_M.list = { - type = "object", - properties = { - project_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - } - } -} - _M.created = { type = "object", properties = { @@ -497,7 +478,7 @@ _M.query = { } } -_M.online = { +_M.env_push = { type = "object", properties = { router_id = { @@ -527,7 +508,7 @@ _M.online = { } } -_M.offline = { +_M.env_pull = { type = "object", properties = { router_id = { @@ -557,4 +538,144 @@ _M.offline = { } } +_M.plugins = { + type = "object", + properties = { + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + } + } +} + +_M.plugin_created = { + type = "object", + properties = { + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + type = { + type = "string", + minLength = 5, + maxLength = 20, + }, + description = { + type = "string", + minLength = 5, + maxLength = 100, + }, + config = { + type = "object" + } + }, + required = { "router_id", "name", "type", "config", "description" } +} + +_M.plugin_updated = { + type = "object", + properties = { + plugin_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + name = { + type = "string", + minLength = 5, + maxLength = 20, + }, + type = { + type = "string", + minLength = 5, + maxLength = 20, + }, + description = { + type = "string", + minLength = 5, + maxLength = 100, + }, + config = { + type = "object" + } + }, + required = { "plugin_id", "name", "type", "config", "description" } +} + +_M.plugin_deleted = { + type = "object", + properties = { + router_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + plugin_id = { + anyOf = { + { + type = "string", + minLength = 1, + pattern = [[^[0-9]+$]] + }, + { + type = "number", + minimum = 1 + } + } + }, + } +} + return _M -- Gitee From 3c930ed2bea829590ff64b5ac6b83a54b4cd5f2b Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:56:41 +0800 Subject: [PATCH 121/165] change: update schema.user check rule. --- apioak/schema/user.lua | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/apioak/schema/user.lua b/apioak/schema/user.lua index 92ae7f5..7d89bb4 100644 --- a/apioak/schema/user.lua +++ b/apioak/schema/user.lua @@ -1,47 +1,5 @@ local _M = {} -_M.register = { - type = "object", - properties = { - name = { - type = 'string', - minLength = 4, - maxLength = 16, - }, - password = { - type = 'string', - minLength = 6, - maxLength = 20, - }, - valid_password = { - type = 'string', - minLength = 6, - maxLength = 20, - }, - email = { - type = 'string', - pattern = "^[a-zA-Z0-9\\_\\-\\.]+\\@[a-zA-Z0-9_-]+\\.[a-zA-Z\\.]+$" - } - }, - required = { "name", "password", "valid_password", "email" } -} - -_M.login = { - type = "object", - properties = { - password = { - type = 'string', - minLength = 6, - maxLength = 20, - }, - email = { - type = 'string', - pattern = "^[a-zA-Z0-9\\_\\-\\.]+\\@[a-zA-Z0-9_-]+\\.[a-zA-Z\\.]+$" - } - }, - required = { "password", "email" } -} - _M.created = { type = "object", properties = { -- Gitee From 4547a061764489e74501a79aa189fd75211f50a6 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:57:07 +0800 Subject: [PATCH 122/165] change: remove schema.plugin check rule. --- apioak/schema/plugin.lua | 284 --------------------------------------- 1 file changed, 284 deletions(-) delete mode 100644 apioak/schema/plugin.lua diff --git a/apioak/schema/plugin.lua b/apioak/schema/plugin.lua deleted file mode 100644 index 12bbe11..0000000 --- a/apioak/schema/plugin.lua +++ /dev/null @@ -1,284 +0,0 @@ -local _M = {} - -_M.project_list = { - type = "object", - properties = { - project_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - } - } -} - -_M.project_created = { - type = "object", - properties = { - project_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - name = { - type = "string", - minLength = 5, - maxLength = 20, - }, - type = { - type = "string", - minLength = 5, - maxLength = 20, - }, - description = { - type = "string", - minLength = 5, - maxLength = 100, - }, - config = { - type = "object" - } - }, - required = { "project_id", "name", "type", "config", "description" } -} - -_M.project_updated = { - type = "object", - properties = { - plugin_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - project_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - name = { - type = "string", - minLength = 5, - maxLength = 20, - }, - type = { - type = "string", - minLength = 5, - maxLength = 20, - }, - description = { - type = "string", - minLength = 5, - maxLength = 100, - }, - config = { - type = "object" - } - }, - required = { "plugin_id", "name", "type", "config", "description" } -} - -_M.project_deleted = { - type = "object", - properties = { - project_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - plugin_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - } -} - -_M.router_list = { - type = "object", - properties = { - router_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - } - } -} - -_M.router_created = { - type = "object", - properties = { - router_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - name = { - type = "string", - minLength = 5, - maxLength = 20, - }, - type = { - type = "string", - minLength = 5, - maxLength = 20, - }, - description = { - type = "string", - minLength = 5, - maxLength = 100, - }, - config = { - type = "object" - } - }, - required = { "router_id", "name", "type", "config", "description" } -} - -_M.router_updated = { - type = "object", - properties = { - plugin_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - router_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - name = { - type = "string", - minLength = 5, - maxLength = 20, - }, - type = { - type = "string", - minLength = 5, - maxLength = 20, - }, - description = { - type = "string", - minLength = 5, - maxLength = 100, - }, - config = { - type = "object" - } - }, - required = { "plugin_id", "name", "type", "config", "description" } -} - -_M.router_deleted = { - type = "object", - properties = { - router_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - plugin_id = { - anyOf = { - { - type = "string", - minLength = 1, - pattern = [[^[0-9]+$]] - }, - { - type = "number", - minimum = 1 - } - } - }, - } -} - - -return _M -- Gitee From 2abdbf5a9446c688cd2a2e9e65f17f0b4be90e50 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 22:59:05 +0800 Subject: [PATCH 123/165] change: update sys.admin routers. --- apioak/sys/admin.lua | 59 +++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/apioak/sys/admin.lua b/apioak/sys/admin.lua index 96092ad..cc03529 100644 --- a/apioak/sys/admin.lua +++ b/apioak/sys/admin.lua @@ -9,44 +9,61 @@ function _M.init_worker() router = r3route.new() -- Common Service Related APIs - router:insert_route("/apioak/admin/plugins", admin.plugin.plugin_list, + router:insert_route("/apioak/admin/plugins", admin.common.plugins, { method = { "GET" } }) - router:insert_route("/apioak/admin/users", admin.user.list, + router:insert_route("/apioak/admin/users", admin.common.users, { method = { "GET" } }) + router:insert_route("/apioak/admin/projects", admin.common.projects, + { method = { "GET" } }) - -- Project Related APIs - router:insert_route("/apioak/admin/projects", admin.project.list, + router:insert_route("/apioak/admin/routers", admin.common.routers, + { method = { "GET" } }) + + + -- Account Related APIs + router:insert_route("/apioak/admin/account/register", admin.account.register, + { method = { "POST" } }) + + router:insert_route("/apioak/admin/account/login", admin.account.login, + { method = { "PUT" } }) + + router:insert_route("/apioak/admin/account/logout", admin.account.logout, + { method = { "DELETE" } }) + + router:insert_route("/apioak/admin/account/status", admin.account.status, { method = { "GET" } }) + + -- Project Related APIs router:insert_route("/apioak/admin/project", admin.project.created, { method = { "POST" } }) router:insert_route("/apioak/admin/project/{project_id}", admin.project.updated, { method = { "PUT" } }) - router:insert_route("/apioak/admin/project/{project_id}", admin.project.query, + router:insert_route("/apioak/admin/project/{project_id}", admin.project.selected, { method = { "GET" } }) router:insert_route("/apioak/admin/project/{project_id}", admin.project.deleted, { method = { "DELETE" } }) - router:insert_route("/apioak/admin/project/{project_id}/plugins", admin.plugin.project_list, + router:insert_route("/apioak/admin/project/{project_id}/routers", admin.project.routers, + { method = { "GET" } }) + + router:insert_route("/apioak/admin/project/{project_id}/plugins", admin.project.plugins, { method = { "GET" } }) - router:insert_route("/apioak/admin/project/{project_id}/plugin", admin.plugin.project_created, + router:insert_route("/apioak/admin/project/{project_id}/plugin", admin.project.plugin_created, { method = { "POST" } }) - router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.plugin.project_updated, + router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.project.plugin_updated, { method = { "PUT" } }) - router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.plugin.project_deleted, + router:insert_route("/apioak/admin/project/{project_id}/plugin/{plugin_id}", admin.project.plugin_deleted, { method = { "DELETE" } }) - router:insert_route("/apioak/admin/project/{project_id}/routers", admin.router.list, - { method = { "GET" } }) - router:insert_route("/apioak/admin/project/{project_id}/members", admin.project.members, { method = { "GET" } }) @@ -73,16 +90,16 @@ function _M.init_worker() router:insert_route("/apioak/admin/router/{router_id}", admin.router.deleted, { method = { "DELETE" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugins", admin.plugin.router_list, + router:insert_route("/apioak/admin/router/{router_id}/plugins", admin.router.plugins, { method = { "GET" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugin", admin.plugin.router_created, + router:insert_route("/apioak/admin/router/{router_id}/plugin", admin.router.plugin_created, { method = { "POST" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.plugin.router_updated, + router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.router.plugin_updated, { method = { "PUT" } }) - router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.plugin.router_deleted, + router:insert_route("/apioak/admin/router/{router_id}/plugin/{plugin_id}", admin.router.plugin_deleted, { method = { "DELETE" } }) router:insert_route("/apioak/admin/router/{router_id}/env/{env}", admin.router.env_push, @@ -92,16 +109,6 @@ function _M.init_worker() { method = { "DELETE" } }) - -- Account Manager API - router:insert_route("/apioak/admin/account/register", admin.user.register, - { method = { "POST" } }) - - router:insert_route("/apioak/admin/account/login", admin.user.login, - { method = { "POST" } }) - - router:insert_route("/apioak/admin/account/logout", admin.user.logout, - { method = { "GET" } }) - -- User Manager API router:insert_route("/apioak/admin/user", admin.user.created, { method = { "POST" } }) -- Gitee From f2b08cbe29978fdec2fbaa4417a27db6be70affe Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:00:28 +0800 Subject: [PATCH 124/165] change: update sys.balancer async loading engine. --- apioak/sys/balancer.lua | 140 +++++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 39 deletions(-) diff --git a/apioak/sys/balancer.lua b/apioak/sys/balancer.lua index 393ca1e..b66835a 100644 --- a/apioak/sys/balancer.lua +++ b/apioak/sys/balancer.lua @@ -1,52 +1,117 @@ -local pdk = require("apioak.pdk") -local balancer = require("ngx.balancer") -local balancer_chash = require('resty.chash') -local balancer_round = require('resty.roundrobin') -local set_current_peer = balancer.set_current_peer -local get_last_failure = balancer.get_last_failure -local set_more_tries = balancer.set_more_tries -local set_timeouts = balancer.set_timeouts +local pdk = require("apioak.pdk") +local db = require("apioak.db") +local ngx_sleep = ngx.sleep +local ngx_timer_at = ngx.timer.at +local ngx_worker_exiting = ngx.worker.exiting +local ngx_var = require("resty.ngxvar") +local balancer = require("ngx.balancer") +local balancer_chash = require('resty.chash') +local balancer_round = require('resty.roundrobin') +local set_current_peer = balancer.set_current_peer +local get_last_failure = balancer.get_last_failure +local set_more_tries = balancer.set_more_tries +local set_timeouts = balancer.set_timeouts + +local upstream_objects = {} +local upstream_latest_hash_id +local upstream_cached_hash_id local _M = {} -function _M.go(oak_ctx) - local router = oak_ctx.router - local upstream = router.upstream +local function automatic_sync_hash_id(premature) + if premature then + return + end - if not upstream then - pdk.log.error("[sys.balancer] upstream undefined") - pdk.response.exit(500) + local i = 1 + while not ngx_worker_exiting() and i <= 10 do + i = i + 1 + + local res, err = db.upstream.query_last_updated_hid() + if err then + pdk.log.error("[sys.balancer] automatic sync upstreams last updated timestamp reading failure, ", err) + break + end + + upstream_latest_hash_id = res.hash_id + + ngx_sleep(10) end - local try_nodes = upstream.try_nodes or pdk.table.new(10, 0) + if not ngx_worker_exiting() then + ngx_timer_at(0, automatic_sync_hash_id) + end +end - local state, code = get_last_failure() - if state == "failed" then - pdk.log.error("[sys.balancer] connection " .. try_nodes[#try_nodes], " state: ", state, " code: ", code) +function _M.init_worker() + ngx_timer_at(0, automatic_sync_hash_id) +end + + +local function loading_upstreams() + local res, err = db.upstream.all() + if err then + pdk.log.error("[sys.balancer] loading upstreams failure, ", err) end - local nodes = upstream.nodes - local servers = pdk.table.new(10, 0) - for i = 1, #nodes do - local node = pdk.string.format("%s:%s", nodes[i].ip, nodes[i].port) - if pdk.table.has(node, try_nodes) then - pdk.table.remove(nodes, i) - else - servers[node] = nodes[i].weight + for i = 1, #res do + local nodes = res[i].nodes + local type = res[i].type + + local servers = pdk.table.new(10, 0) + for s = 1, #nodes do + local node = pdk.string.format("%s:%s", nodes[s].ip, nodes[s].port) + servers[node] = nodes[s].weight + end + + local balancer_handle + if type == pdk.const.BALANCER_ROUNDROBIN then + balancer_handle = balancer_round:new(servers) end + + if type == pdk.const.BALANCER_CHASH then + balancer_handle = balancer_chash:new(servers) + end + + local upstream_id = tonumber(res[i].id) + upstream_objects[upstream_id] = { + handler = balancer_handle, + timeouts = res[i].timeouts, + type = type + } + end +end + +function _M.loading() + if not upstream_cached_hash_id or upstream_cached_hash_id ~= upstream_latest_hash_id then + loading_upstreams() + upstream_cached_hash_id = upstream_latest_hash_id + end +end + +function _M.gogogo(oak_ctx) + local router = oak_ctx.router + local upstream = router.upstream + + if not upstream then + pdk.log.error("[sys.balancer] upstream undefined") + pdk.response.exit(500) end - if not servers or #nodes == 0 then - pdk.log.error("[sys.balancer] upstream.nodes undefined") + local upstream_id = upstream.id or nil + if not upstream_id then + pdk.log.error("[sys.balancer] upstream undefined") pdk.response.exit(500) end - upstream.count_retries = #nodes - 1 - if upstream.enable_retries == 0 or upstream.count_retries <= 1 then - upstream.count_retries = 0 + upstream = upstream_objects[upstream_id] + + local state, code = get_last_failure() + if state == "failed" then + pdk.log.error("[sys.balancer] connection failure state: " .. state .. " code: " .. code) end - set_more_tries(upstream.count_retries) + set_more_tries(0) local timeout = upstream.timeouts if timeout then @@ -59,15 +124,15 @@ function _M.go(oak_ctx) end end + local handler = upstream.handler local address if upstream.type == pdk.const.BALANCER_CHASH then - local chash_up = balancer_chash:new(servers) - address = chash_up:find(oak_ctx.matched.variable.remote_addr or pdk.const.LOCAL_IP) + local request_address = ngx_var.fetch("remote_addr") + address = handler:find(request_address) end if upstream.type == pdk.const.BALANCER_ROUNDROBIN then - local round_up = balancer_round:new(servers) - address = round_up:find() + address = handler:find() end if not address then @@ -81,9 +146,6 @@ function _M.go(oak_ctx) pdk.log.error("[sys.balancer] failed to set the current peer: ", err) pdk.response.exit(500) end - - pdk.table.insert(try_nodes, address) - oak_ctx.router.upstream.try_nodes = try_nodes end return _M -- Gitee From 63db7e54d12e5bc443ec16bd706310c8b8897839 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:00:46 +0800 Subject: [PATCH 125/165] change: update sys.config async loading engine. --- apioak/sys/config.lua | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 apioak/sys/config.lua diff --git a/apioak/sys/config.lua b/apioak/sys/config.lua new file mode 100644 index 0000000..3cdd614 --- /dev/null +++ b/apioak/sys/config.lua @@ -0,0 +1,50 @@ +local pdk = require("apioak.pdk") +local yaml = require("tinyyaml") +local io_open = io.open +local ngx_timer_at = ngx.timer.at +local ngx_config_prefix = ngx.config.prefix + +local config_objects + +local _M = {} + +local function loading_configs(premature) + if premature then + return + end + + local file, err = io_open(ngx_config_prefix() .. "conf/apioak.yaml", "r") + if err then + pdk.log.error("[sys.config] failed to open configuration file, ", err) + return + end + + local content = file:read("*a") + file:close() + + local config = yaml.parse(content) + if not config then + pdk.log.error("[sys.config] failed to parse configuration file") + return + end + + config_objects = config +end + +function _M.init_worker() + ngx_timer_at(0, loading_configs) +end + +function _M.query(key) + if not config_objects then + loading_configs() + end + + if not config_objects[key] then + return nil, key .. " is not set in the configuration file" + end + + return config_objects[key], nil +end + +return _M -- Gitee From 9e64ea6b3ec81a30fc100934ca8665d36931b23b Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:01:01 +0800 Subject: [PATCH 126/165] change: update sys.router async loading engine. --- apioak/sys/router.lua | 145 +++++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 44 deletions(-) diff --git a/apioak/sys/router.lua b/apioak/sys/router.lua index e742b55..6bf9ae8 100644 --- a/apioak/sys/router.lua +++ b/apioak/sys/router.lua @@ -1,12 +1,15 @@ -local pdk = require("apioak.pdk") -local db = require("apioak.db") -local pairs = pairs -local r3route = require("resty.r3") +local pdk = require("apioak.pdk") +local db = require("apioak.db") +local pairs = pairs +local r3route = require("resty.r3") +local ngx_var = require("resty.ngxvar") local ngx_sleep = ngx.sleep local ngx_timer_at = ngx.timer.at local ngx_worker_exiting = ngx.worker.exiting local router_objects +local router_latest_hash_id +local router_cached_hash_id local _M = {} @@ -38,14 +41,8 @@ local create_routers = function(project, router, env_router, environment) path = env_router.request_path, method = env_router.request_method, handler = function(params, oak_ctx) - oak_ctx.router = env_router - oak_ctx.matched = {} - oak_ctx.matched.path = params - oak_ctx.matched.query = pdk.request.query() - oak_ctx.matched.header = pdk.request.header() - oak_ctx.matched.variable = { - remote_addr = ngx.var.remote_addr - } + oak_ctx.router = env_router + oak_ctx.matched.path = params end } end @@ -86,19 +83,43 @@ local loading_routers = function() router_objects:compile() end -function _M.init_worker() - ngx_timer_at(0, function (premature) - if premature then - return +local function automatic_sync_hash_id(premature) + if premature then + return + end + + local i = 0 + while not ngx_worker_exiting() and i <= 10 do + i = i + 1 + + local res, err = db.router.query_last_updated_hid() + if err then + pdk.log.error("[sys.router] automatic sync routers last updated timestamp reading failure, ", err) + break + end + local router_hash_id = res.hash_id + + res, err = db.project.query_last_updated_hid() + if err then + pdk.log.error("[sys.router] automatic sync projects last updated timestamp reading failure, ", err) + break + end + local project_hash_id = res.hash_id + + if router_hash_id and project_hash_id then + router_latest_hash_id = pdk.string.md5(router_hash_id .. project_hash_id) end - while not ngx_worker_exiting() do - loading_routers() - -- automatically update configuration once every 30s - ngx_sleep(30) - end + ngx_sleep(10) + end + + if not ngx_worker_exiting() then + ngx_timer_at(0, automatic_sync_hash_id) + end +end - end) +function _M.init_worker() + ngx_timer_at(0, automatic_sync_hash_id) end local checked_request_params = function(rule, params) @@ -116,48 +137,84 @@ local checked_request_params = function(rule, params) return query_val, nil end -function _M.init_request(oak_ctx) +function _M.parameter(oak_ctx) + local env = pdk.request.header(pdk.const.REQUEST_API_ENV_KEY) + if env then + env = pdk.string.upper(env) + else + env = pdk.const.ENVIRONMENT_PROD + end + + oak_ctx.matched = {} + oak_ctx.matched.query = pdk.request.query() + oak_ctx.matched.header = pdk.request.header() + + oak_ctx.matched.header[pdk.const.REQUEST_API_ENV_KEY] = env +end + +function _M.matched(oak_ctx) + if not router_cached_hash_id or router_cached_hash_id ~= router_latest_hash_id then + loading_routers() + router_cached_hash_id = router_latest_hash_id + end + + local match_uri = pdk.string.format("/%s%s", oak_ctx.matched.header[pdk.const.REQUEST_API_ENV_KEY], + ngx_var.fetch("uri")) + local match_ok = router_objects:dispatch(match_uri, pdk.request.method(), oak_ctx) + if not match_ok then + return false + end + return true +end + +function _M.mapping(oak_ctx) local router = oak_ctx.router - local params = router.backend_params - for i = 1, #params do - local param = params[i] - local backend_params_position = pdk.string.lower(param.position) - local request_param_position = pdk.string.lower(param.request_param_position) + local backend_param_rules = router.backend_params + for b = 1, #backend_param_rules do + local backend_param_rule = backend_param_rules[b] + local backend_param_position = pdk.string.lower(backend_param_rule.position) + local request_param_position = pdk.string.lower(backend_param_rule.request_param_position) - if backend_params_position == request_param_position then + if backend_param_position == request_param_position then local request_params = oak_ctx.matched[request_param_position] - local request_value, err = checked_request_params(param, request_params) + local request_value, err = checked_request_params(backend_param_rule, request_params) if err then - pdk.response.exit(401, { err_message = err }) + pdk.response.exit(403, { err_message = err }) end - if param.name ~= param.request_param_name then - request_params[param.request_param_name] = nil - request_params[param.name] = request_value + if backend_param_rule.name ~= backend_param_rule.request_param_name then + request_params[backend_param_rule.request_param_name] = nil + request_params[backend_param_rule.name] = request_value end oak_ctx.matched[request_param_position] = request_params else local request_params = oak_ctx.matched[request_param_position] - local backend_params = oak_ctx.matched[backend_params_position] + local backend_params = oak_ctx.matched[backend_param_position] - local request_value, err = checked_request_params(param, request_params) + local request_value, err = checked_request_params(backend_param_rule, request_params) if err then - pdk.response.exit(401, { err_message = err }) + pdk.response.exit(403, { err_message = err }) end - request_params[param.request_param_name] = nil - backend_params[param.name] = request_value + request_params[backend_param_rule.request_param_name] = nil + backend_params[backend_param_rule.name] = request_value - oak_ctx.matched[request_param_position] = request_params - oak_ctx.matched[backend_params_position] = backend_params + oak_ctx.matched[request_param_position] = request_params + oak_ctx.matched[backend_param_position] = backend_params end end -end -function _M.get() - return router_objects + local constant_param_rules = router.constant_params + for c = 1, #constant_param_rules do + local constant_param_rule = constant_param_rules[c] + local constant_param_position = pdk.string.lower(constant_param_rule.position) + local constant_param_value = constant_param_rule.value + local constant_param_name = constant_param_rule.name + + oak_ctx.matched[constant_param_position][constant_param_name] = constant_param_value + end end return _M -- Gitee From 0113aeae1b74b2450df735ceb97917828c10c9b2 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:02:03 +0800 Subject: [PATCH 127/165] change: apioak.admin add common and account module import. --- apioak/admin.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apioak/admin.lua b/apioak/admin.lua index 03fc2d9..d3f7ea6 100644 --- a/apioak/admin.lua +++ b/apioak/admin.lua @@ -2,5 +2,6 @@ return { user = require("apioak.admin.user"), router = require("apioak.admin.router"), project = require("apioak.admin.project"), - plugin = require("apioak.admin.plugin"), + account = require("apioak.admin.account"), + common = require("apioak.admin.common"), } -- Gitee From 10d81e80b16dfeb0b0d5a3a102df2d7df18c9a80 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:03:05 +0800 Subject: [PATCH 128/165] change: apioak.apioak add cors functions. --- apioak/apioak.lua | 74 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/apioak/apioak.lua b/apioak/apioak.lua index 1bfc4a7..a4a6650 100644 --- a/apioak/apioak.lua +++ b/apioak/apioak.lua @@ -1,18 +1,38 @@ local ngx = ngx -local ipairs = ipairs local pairs = pairs local pdk = require("apioak.pdk") local sys = require("apioak.sys") local function run_plugin(phase, oak_ctx) - local plugins = pdk.plugin.loading() - for _, plugin in ipairs(plugins) do + local plugins, err = pdk.plugin.loading() + if err then + pdk.log.error("failure to loading plugins, ", err) + plugins = {} + end + + for i = 1, #plugins do + local plugin = plugins[i] if plugin[phase] then plugin[phase](oak_ctx) end end end +local function options_request_handle() + if pdk.request.method() == "OPTIONS" then + pdk.response.exit(200, { + err_message = "Welcome to APIOAK" + }) + end +end + +local function enable_cors_handle() + pdk.response.set_header("Access-Control-Allow-Origin", "*") + pdk.response.set_header("Access-Control-Allow-Credentials", "true") + pdk.response.set_header("Access-Control-Expose-Headers", "*") + pdk.response.set_header("Access-Control-Max-Age", "3600") +end + local APIOAK = {} function APIOAK.init() @@ -29,16 +49,20 @@ end function APIOAK.init_worker() + sys.config.init_worker() + sys.admin.init_worker() sys.router.init_worker() - sys.plugin.init_worker() + sys.balancer.init_worker() end function APIOAK.http_access() + options_request_handle() + local ngx_ctx = ngx.ctx local oak_ctx = ngx_ctx.oak_ctx if not oak_ctx then @@ -46,24 +70,23 @@ function APIOAK.http_access() ngx_ctx.oak_ctx = oak_ctx end - local env = pdk.request.header(pdk.const.REQUEST_API_ENV_KEY) - if env then - env = pdk.string.upper(env) - else - env = pdk.const.ENVIRONMENT_PROD - end + sys.router.parameter(oak_ctx) - local routers = sys.router.get() - local match_ok = routers:dispatch("/" .. env .. ngx.var.uri, ngx.req.get_method(), oak_ctx) - if not match_ok then + local match_succeed = sys.router.matched(oak_ctx) + if not match_succeed then pdk.response.exit(404, { err_message = "\"URI\" Undefined" }) end + if oak_ctx.router.enable_cors == 1 then + enable_cors_handle() + end + if oak_ctx.router.is_mock_request then - pdk.response.exit(200, oak_ctx.router.response_success) + pdk.response.set_header(pdk.const.RESPONSE_MOCK_REQUEST_KEY, true) + pdk.response.exit(200, oak_ctx.router.response_success, oak_ctx.router.response_type) end - sys.router.init_request(oak_ctx) + sys.router.mapping(oak_ctx) local router = oak_ctx.router local matched = oak_ctx.matched @@ -87,34 +110,37 @@ function APIOAK.http_access() ngx.var.upstream_uri = upstream_uri + sys.balancer.loading() + run_plugin("http_access", oak_ctx) end function APIOAK.http_balancer() - local ngx_ctx = ngx.ctx - local oak_ctx = ngx_ctx.oak_ctx - sys.balancer.go(oak_ctx) + local oak_ctx = ngx.ctx.oak_ctx + sys.balancer.gogogo(oak_ctx) end function APIOAK.http_header_filter() - local ngx_ctx = ngx.ctx - local oak_ctx = ngx_ctx.oak_ctx + local oak_ctx = ngx.ctx.oak_ctx run_plugin("http_header_filter", oak_ctx) end function APIOAK.http_body_filter() - local ngx_ctx = ngx.ctx - local oak_ctx = ngx_ctx.oak_ctx + local oak_ctx = ngx.ctx.oak_ctx run_plugin("http_body_filter", oak_ctx) end function APIOAK.http_log() - local ngx_ctx = ngx.ctx - local oak_ctx = ngx_ctx.oak_ctx + local oak_ctx = ngx.ctx.oak_ctx run_plugin("http_log", oak_ctx) end function APIOAK.http_admin() + + options_request_handle() + + enable_cors_handle() + local admin_routers = sys.admin.routers() local ok = admin_routers:dispatch(ngx.var.uri, ngx.req.get_method()) if not ok then -- Gitee From e319c68eb4b330a5b07b95ddcb0774e4303c90d4 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:03:34 +0800 Subject: [PATCH 129/165] change: apioak.pdk remove plugin import. --- apioak/pdk.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/apioak/pdk.lua b/apioak/pdk.lua index efe4ffe..fc51929 100644 --- a/apioak/pdk.lua +++ b/apioak/pdk.lua @@ -3,7 +3,6 @@ return { ctx = require("apioak.pdk.ctx"), json = require("apioak.pdk.json"), time = require("apioak.pdk.time"), - config = require("apioak.pdk.config"), shared = require("apioak.pdk.shared"), table = require("apioak.pdk.table"), string = require("apioak.pdk.string"), -- Gitee From 8e1c2c377bd6898e56917aff0c05374e24025fcc Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:04:10 +0800 Subject: [PATCH 130/165] change: apioak.schema add account and common import. --- apioak/schema.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apioak/schema.lua b/apioak/schema.lua index 56b427d..d415908 100644 --- a/apioak/schema.lua +++ b/apioak/schema.lua @@ -1,6 +1,7 @@ return { user = require("apioak.schema.user"), router = require("apioak.schema.router"), - plugin = require("apioak.schema.plugin"), project = require("apioak.schema.project"), + account = require("apioak.schema.account"), + common = require("apioak.schema.common"), } -- Gitee From a6efea830775753b49c81526718e069c502363e4 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:05:00 +0800 Subject: [PATCH 131/165] change: apioak.sys remove plugin import. --- apioak/sys.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apioak/sys.lua b/apioak/sys.lua index 223237a..185c780 100644 --- a/apioak/sys.lua +++ b/apioak/sys.lua @@ -2,6 +2,7 @@ return { meta = require("apioak.sys.meta"), admin = require("apioak.sys.admin"), router = require("apioak.sys.router"), - plugin = require('apioak.sys.plugin'), + plugin = require("apioak.sys.plugin"), + config = require("apioak.sys.config"), balancer = require("apioak.sys.balancer"), } -- Gitee From 40b36a6e5e20b17be9ab3489a7cd10f5bad2ec11 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:05:26 +0800 Subject: [PATCH 132/165] change: update nginx config file. --- conf/nginx.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conf/nginx.conf b/conf/nginx.conf index 1f3daa6..d4086ac 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -1,8 +1,7 @@ master_process on; -user root root; - worker_processes auto; +worker_cpu_affinity auto; error_log logs/error.log error; -- Gitee From a99816d67688023dc633acc4ca5a37fbd7432697 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:06:10 +0800 Subject: [PATCH 133/165] change: remove lua-resty-jit-uuid import. --- rockspec/apioak-master-0.rockspec | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rockspec/apioak-master-0.rockspec b/rockspec/apioak-master-0.rockspec index f5775f7..1109be4 100644 --- a/rockspec/apioak-master-0.rockspec +++ b/rockspec/apioak-master-0.rockspec @@ -16,8 +16,7 @@ description = { dependencies = { "lua-resty-balancer = 0.02rc5", - "lua-resty-ngxvar = 0.4", - "lua-resty-jit-uuid = 0.0.7", + "lua-resty-ngxvar = 0.5", "lua-resty-jwt = 0.2.0", "lua-resty-libr3 = 1.2-0", "lua-resty-http = 0.15-0", -- Gitee From 5fe55f1cfe24053677d814b8919fa05a05124939 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:16:48 +0800 Subject: [PATCH 134/165] change: update apioak database config file. --- conf/apioak.sql | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/conf/apioak.sql b/conf/apioak.sql index 1fbaacc..60e8241 100644 --- a/conf/apioak.sql +++ b/conf/apioak.sql @@ -42,12 +42,12 @@ CREATE TABLE `oak_projects` `name` varchar(50) DEFAULT NULL COMMENT '项目名称', `description` text COMMENT '项目描述', `path` varchar(50) DEFAULT NULL COMMENT '项目前缀', - `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', - `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目组ID', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_PATH` (`path`) USING BTREE + UNIQUE KEY `UNIQ_PATH` (`path`) USING BTREE, + KEY `IDX_NAME` (`name`), + KEY `IDX_UPDATED_AT` (`updated_at`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='项目表'; @@ -58,13 +58,13 @@ DROP TABLE IF EXISTS `oak_roles`; CREATE TABLE `oak_roles` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '组ID', + `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', `is_admin` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '组管理员', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_GROUP_USER` (`group_id`, `user_id`) + UNIQUE KEY `UNIQ_PROJECT_USER` (`project_id`, `user_id`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='角色表'; @@ -94,12 +94,12 @@ CREATE TABLE `oak_routers` `env_beta_config` json DEFAULT NULL COMMENT '预发环境配置', `env_test_config` json DEFAULT NULL COMMENT '测试环境配置', `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', - `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_REQUEST_PATH` (`request_path`), - UNIQUE KEY `UNIQ_NAME` (`name`) + UNIQUE KEY `UNIQ_REQUEST_PATH` (`request_path`, `project_id`) USING BTREE, + KEY `IDX_UPDATED_AT` (`updated_at`), + KEY `IDX_NAME` (`name`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='路由表'; @@ -126,18 +126,18 @@ DROP TABLE IF EXISTS `oak_upstreams`; CREATE TABLE `oak_upstreams` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `env` varchar(10) DEFAULT NULL COMMENT '发布环境', - `host` varchar(50) DEFAULT NULL COMMENT '主机地址', - `enable_retries` tinyint(3) unsigned DEFAULT '0' COMMENT '开启重试', - `type` varchar(10) DEFAULT NULL COMMENT '负载均衡算法', - `timeouts` json DEFAULT NULL COMMENT '超时时间', - `nodes` json DEFAULT NULL COMMENT '服务节点', - `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `env` varchar(10) DEFAULT NULL COMMENT '发布环境', + `host` varchar(50) DEFAULT NULL COMMENT '主机地址', + `type` varchar(10) DEFAULT NULL COMMENT '负载均衡算法', + `timeouts` json DEFAULT NULL COMMENT '超时时间', + `nodes` json DEFAULT NULL COMMENT '服务节点', + `project_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '项目ID', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_ENV_PROJECT` (`env`, `project_id`) + UNIQUE KEY `UNIQ_ENV_PROJECT` (`env`, `project_id`), + KEY `IDX_UPDATED_AT` (`updated_at`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='服务节点表'; -- Gitee From 0b21d94214702d5bb7cf3c69ffb9345fa2f52f8f Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 16 Mar 2020 23:18:24 +0800 Subject: [PATCH 135/165] change: remove group table from database config file. --- conf/apioak.sql | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/conf/apioak.sql b/conf/apioak.sql index 60e8241..483756a 100644 --- a/conf/apioak.sql +++ b/conf/apioak.sql @@ -1,19 +1,3 @@ -DROP TABLE IF EXISTS `oak_groups`; - -CREATE TABLE `oak_groups` -( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `name` varchar(50) DEFAULT NULL COMMENT '组名称', - `description` text COMMENT '组描述', - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_NAME` (`name`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 COMMENT ='项目组表'; - - - DROP TABLE IF EXISTS `oak_plugins`; CREATE TABLE `oak_plugins` -- Gitee From b1a9f04f4c12c3873a68dd67a96d20bf487aeca4 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:46:02 +0800 Subject: [PATCH 136/165] change: remove redundant parameters. --- apioak/admin/project.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apioak/admin/project.lua b/apioak/admin/project.lua index a1d4f49..6c11db1 100644 --- a/apioak/admin/project.lua +++ b/apioak/admin/project.lua @@ -75,9 +75,8 @@ function project_controller.updated(params) end for i = 1, #body.upstreams do - local upstream = body.upstreams[i] - upstream.project_id = params.project_id - res, err = db.upstream.update(upstream.id, upstream) + local upstream = body.upstreams[i] + res, err = db.upstream.update_by_pid(params.project_id, upstream) if err then pdk.response.exit(500, { err_message = err }) end -- Gitee From 0db9c2f295c169b1ddb4c63b67a8c5a4f5d978d5 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:48:18 +0800 Subject: [PATCH 137/165] change: add project plugin last updated hash ID. --- apioak/db/plugin.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apioak/db/plugin.lua b/apioak/db/plugin.lua index c6b5451..53e864f 100644 --- a/apioak/db/plugin.lua +++ b/apioak/db/plugin.lua @@ -92,4 +92,29 @@ function _M.update_by_res(res_type, res_id, plugin_id, params) return res, nil end +function _M.query_project_last_updated_hid() + local sql = pdk.string.format([[ + SELECT + MD5(updated_at) AS hash_id + FROM + %s + WHERE + res_type = '%s' + ORDER BY + updated_at + DESC + LIMIT 1 + ]], table_name, _M.RESOURCES_TYPE_PROJECT) + local res, err = pdk.mysql.execute(sql) + if err then + return nil, err + end + + if #res == 0 then + return res, nil + end + + return res[1], nil +end + return _M -- Gitee From d8be7b8ddfebc124b743ed830e07a6d95efd292e Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:52:11 +0800 Subject: [PATCH 138/165] change: updated get project upstream functions. --- apioak/db/upstream.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apioak/db/upstream.lua b/apioak/db/upstream.lua index 8cd12f6..2cfc78e 100644 --- a/apioak/db/upstream.lua +++ b/apioak/db/upstream.lua @@ -48,17 +48,17 @@ function _M.create(params) return res, nil end -function _M.update(upstream_id, params) +function _M.update_by_pid(pid, upstream) local sql = pdk.string.format([[ UPDATE %s SET host = '%s', type = '%s', timeouts = '%s', nodes = '%s' WHERE - id = %s - ]], table_name, params.host, params.type, - pdk.json.encode(params.timeouts), - pdk.json.encode(params.nodes), upstream_id) + id = %s AND project_id = %s + ]], table_name, upstream.host, upstream.type, + pdk.json.encode(upstream.timeouts), + pdk.json.encode(upstream.nodes), upstream.id, pid) local res, err = pdk.mysql.execute(sql) if err then -- Gitee From b408cadd0182253b8b438b3bef1db00c5b911845 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:53:04 +0800 Subject: [PATCH 139/165] change: remove redundant variables. --- apioak/pdk/const.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/apioak/pdk/const.lua b/apioak/pdk/const.lua index 3df874d..9c957e4 100644 --- a/apioak/pdk/const.lua +++ b/apioak/pdk/const.lua @@ -26,8 +26,6 @@ _M.REQUEST_PARAM_POS_PATH = "PATH" _M.REQUEST_PARAM_POS_HEADER = "HEADER" -_M.REQUEST_PARAM_NGX_VARIABLE = "VARIABLE" - _M.CONTENT_TYPE = "Content-Type" _M.CONTENT_TYPE_JSON = "application/json" -- Gitee From 33189e76528a662cd5736f832a36b5f253045f64 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:53:58 +0800 Subject: [PATCH 140/165] change: add get_method and set_method functions. --- apioak/pdk/request.lua | 19 ++++++++++++++++++- conf/apioak.sql | 2 +- conf/nginx.conf | 7 +++---- rockspec/apioak-master-0.rockspec | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/apioak/pdk/request.lua b/apioak/pdk/request.lua index 20b0567..bcb3ac6 100644 --- a/apioak/pdk/request.lua +++ b/apioak/pdk/request.lua @@ -11,6 +11,16 @@ local CONTENT_TYPE_POST = "application/x-www-form-urlencoded" local CONTENT_TYPE_JSON = "application/json" local CONTENT_TYPE_FORM_DATA = "multipart/form-data" +local methods = { + ["GET"] = ngx.HTTP_GET, + ["POST"] = ngx.HTTP_POST, + ["PUT"] = ngx.HTTP_PUT, + ["DELETE"] = ngx.HTTP_DELETE, + ["OPTIONS"] = ngx.HTTP_OPTIONS, + ["PATCH"] = ngx.HTTP_PATCH, + ["TRACE"] = ngx.HTTP_TRACE, +} + local _M = {} local function _header(key) @@ -78,6 +88,13 @@ _M.header = _header _M.add_header = ngx.req.set_header -_M.method = ngx.req.get_method +_M.get_method = ngx.req.get_method + +_M.set_method = function(method) + local method_id = methods[method] + if method_id then + ngx.req.set_method(method_id) + end +end return _M diff --git a/conf/apioak.sql b/conf/apioak.sql index 483756a..e7ba1d2 100644 --- a/conf/apioak.sql +++ b/conf/apioak.sql @@ -81,7 +81,7 @@ CREATE TABLE `oak_routers` `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `UNIQ_REQUEST_PATH` (`request_path`, `project_id`) USING BTREE, + UNIQUE KEY `UNIQ_REQUEST_PATH` (`request_path`, `request_method`, `project_id`) USING BTREE, KEY `IDX_UPDATED_AT` (`updated_at`), KEY `IDX_NAME` (`name`) ) ENGINE = InnoDB diff --git a/conf/nginx.conf b/conf/nginx.conf index d4086ac..3ff2adf 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -37,10 +37,9 @@ http { lua_code_cache on; - lua_shared_dict apioak 100m; - lua_shared_dict plugin_limit_conn 10m; - lua_shared_dict plugin_limit_req 10m; - lua_shared_dict plugin_limit_count 10m; + lua_shared_dict plugin_limit_conn 50m; + lua_shared_dict plugin_limit_req 50m; + lua_shared_dict plugin_limit_count 50m; upstream apioak_backend { server 0.0.0.1; diff --git a/rockspec/apioak-master-0.rockspec b/rockspec/apioak-master-0.rockspec index 1109be4..2527ead 100644 --- a/rockspec/apioak-master-0.rockspec +++ b/rockspec/apioak-master-0.rockspec @@ -16,11 +16,11 @@ description = { dependencies = { "lua-resty-balancer = 0.02rc5", - "lua-resty-ngxvar = 0.5", "lua-resty-jwt = 0.2.0", "lua-resty-libr3 = 1.2-0", "lua-resty-http = 0.15-0", "lua-resty-mysql = 0.15-0", + "lua-resty-lrucache = 0.09-2", "jsonschema = 0.4", "luasocket = 3.0rc1-2", "luafilesystem = 1.7.0-2", -- Gitee From 0cd88d216efca43b225a1b09152b8e0c60116f75 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:55:08 +0800 Subject: [PATCH 141/165] change: canonical plugins code format. --- apioak/plugin/jwt-auth.lua | 67 ++++++++-------- apioak/plugin/key-auth.lua | 59 ++++++++++----- apioak/plugin/limit-conn.lua | 139 +++++++++++++++++++++------------- apioak/plugin/limit-count.lua | 92 ++++++++++++---------- apioak/plugin/limit-req.lua | 88 ++++++++++++--------- 5 files changed, 257 insertions(+), 188 deletions(-) diff --git a/apioak/plugin/jwt-auth.lua b/apioak/plugin/jwt-auth.lua index 42270d9..f85a87e 100644 --- a/apioak/plugin/jwt-auth.lua +++ b/apioak/plugin/jwt-auth.lua @@ -1,8 +1,10 @@ -local jwt = require("resty.jwt") -local pdk = require("apioak.pdk") +local jwt = require("resty.jwt") +local pdk = require("apioak.pdk") + +local plugin_name = "jwt-auth" local _M = { - name = "jwt-auth", + name = plugin_name, type = "Authentication", description = "Lua module for JWT Authentication.", config = { @@ -13,59 +15,50 @@ local _M = { maxLength = 32, description = "signature secret key", } - } + }, } -local schema = { +local config_schema = { type = "object", properties = { secret = { - type = "string", + type = "string", + minLength = 10, + maxLength = 32, } }, required = { "secret" } } -local function jwt_auth(secret, credential) - local obj = jwt.verify(_M.key, secret, credential) - if obj.verified then - return true - end - return false -end +function _M.http_access(oak_ctx) + local router = oak_ctx.router or {} + local plugins = router.plugins -local function is_authorized(secret, header_credential, query_credential) - if not secret then return false end - local authorized = false - if (not header_credential) and (not query_credential) then - return authorized + if not plugins then + return end - if header_credential then - authorized = jwt_auth(secret, pdk.string.split(header_credential, " ")[2]) - elseif query_credential then - authorized = jwt_auth(secret, query_credential) - end - return authorized -end -function _M.http_access(oak_ctx) - - if not oak_ctx.plugins or not oak_ctx.plugins[_M.name] then - return false, nil + local router_plugin = plugins[plugin_name] + if not router_plugin then + return end - local plugin_conf = oak_ctx.plugins[_M.name] - local _, err = pdk.schema.check(schema, plugin_conf) + local plugin_config = router_plugin.config or {} + local _, err = pdk.schema.check(config_schema, plugin_config) if err then - return false, nil + pdk.log.error("[Jwt-Auth] Authorization FAIL, backend config error, " .. err) + pdk.response.exit(500) end - local header_credential = pdk.request.header("Authorization") - local query_credential = pdk.request.query('token') + local certificate = pdk.request.header("APIOAK-JWT-AUTH") + if not certificate then + pdk.response.exit(401, + { err_message = "[Jwt-Auth] Authorization FAIL, property header \"APIOAK-JWT-AUTH\" is required" }) + end - local is_success = is_authorized(plugin_conf.secret, header_credential, query_credential) - if not is_success then - pdk.response.exit(403, { err_message = "Authorization Required" }) + local res = jwt:verify(plugin_config.secret, certificate) + if not res.verified then + pdk.response.exit(401, { err_message = "[Jwt-Auth] Authorization FAIL" }) end end diff --git a/apioak/plugin/key-auth.lua b/apioak/plugin/key-auth.lua index 7f2fbf4..0b83795 100644 --- a/apioak/plugin/key-auth.lua +++ b/apioak/plugin/key-auth.lua @@ -1,7 +1,9 @@ -local pdk = require("apioak.pdk") +local pdk = require("apioak.pdk") + +local plugin_name = "key-auth" local _M = { - name = "key-auth", + name = plugin_name, type = "Authentication", description = "Lua module for Key Authentication.", config = { @@ -15,31 +17,46 @@ local _M = { } } -local function key_verify(secret_key, secret) - if secret_key ~= secret then - return false - end - - return true -end +local config_schema = { + type = "object", + properties = { + secret = { + type = "string", + minLength = 10, + maxLength = 32, + } + }, + required = { "secret" } +} function _M.http_access(oak_ctx) + local router = oak_ctx.router or {} + local plugins = router.plugins + + if not plugins then + return + end - if oak_ctx.plugins and oak_ctx.plugins[_M.name] then + local router_plugin = plugins[plugin_name] + if not router_plugin then + return + end - local plugin_conf = oak_ctx.plugins[_M.name] + local plugin_config = router_plugin.config or {} + local _, err = pdk.schema.check(config_schema, plugin_config) + if err then + pdk.log.error("[Key-Auth] Authorization FAIL, backend config error, " .. err) + pdk.response.exit(500) + end - if plugin_conf.secret then - local secret_key = pdk.request.header('Authentication') - if not secret_key then - pdk.response.exit(403, { err_message = "Missing Authentication found in request" }) - end + local certificate = pdk.request.header("APIOAK-KEY-AUTH") + if not certificate then + pdk.response.exit(401, + { err_message = "[Key-Auth] Authorization FAIL, property header \"APIOAK-KEY-AUTH\" is required" }) + end - local verify = key_verify(secret_key, plugin_conf.secret) - if not verify then - pdk.response.exit(403, { err_message = "Invalid Authentication in request" }) - end - end + if plugin_config.secret ~= certificate then + pdk.response.exit(401, { err_message = "[Key-Auth] Authorization FAIL" }) end end diff --git a/apioak/plugin/limit-conn.lua b/apioak/plugin/limit-conn.lua index 7e438ab..719f81b 100644 --- a/apioak/plugin/limit-conn.lua +++ b/apioak/plugin/limit-conn.lua @@ -1,9 +1,13 @@ -local limit_conn_new = require("resty.limit.conn").new -local ngx_var = ngx.var local pdk = require("apioak.pdk") +local sys = require("apioak.sys") +local limit_conn = require("resty.limit.conn") +local ngx_var = ngx.var +local ngx_sleep = ngx.sleep + +local plugin_name = "limit-conn" local _M = { - name = "limit-conn", + name = plugin_name, type = "Traffic Control", description = "Lua module for limiting request concurrency (or concurrent connections).", config = { @@ -16,109 +20,136 @@ local _M = { }, burst = { type = "number", - minimum = 1, + minimum = 0, maximum = 50000, default = 100, description = "the number of excessive concurrent requests (or connections) allowed to be delayed." }, default_conn_delay = { type = "number", - minimum = 1, + minimum = 0, maximum = 60, default = 1, description = "the default processing latency of a typical connection (or request)." } - }, - conf = {} + } } -local schema = { +local config_schema = { type = "object", properties = { rate = { - type = "integer", - minLength = 1 + type = "number", + minimum = 1, + maximum = 100000, }, burst = { - type = "integer", - minLength = 1 - }, - key = { - type = "string", + type = "number", + minimum = 1, + maximum = 50000, }, default_conn_delay = { - type = "number", + type = "number", + minimum = 1, + maximum = 60, } }, - required = { "rate", "burst", "key", "default_conn_delay" } + required = { "rate", "burst", "default_conn_delay" } } -local function create_limit_obj(conf) - local limit, err = pdk.shared.get(_M.key) - if not err then - return limit, nil - end +local function create_limit_object(router_id, config) + local cache_key = pdk.string.format("%s:ROUTER:%s", plugin_name, router_id) - limit, err = limit_conn_new("plugin_limit_conn", conf.rate, conf.burst, conf.default_conn_delay) + local limit = sys.cache.get(cache_key) if not limit then - return nil, err + local err + limit, err = limit_conn.new("plugin_limit_conn", config.rate, config.burst, config.default_conn_delay) + if not limit then + pdk.log.error("[Limit-Conn] failed to instantiate a resty.limit.conn object: ", err) + else + sys.cache.set(cache_key, limit, 86400) + end end - pdk.shared.set(_M.key, limit) - return limit, nil + + return limit end function _M.http_access(oak_ctx) - if not oak_ctx.plugins or not oak_ctx.plugins[_M.name] then - return false, nil + local router = oak_ctx.router or {} + local plugins = router.plugins + + if not plugins then + return + end + + local router_plugin = plugins[plugin_name] + if not router_plugin then + return end - local plugin_conf = oak_ctx.plugins[_M.name] - local _, err = pdk.schema.check(schema, plugin_conf) - if err then - return false, nil + local plugin_config = router_plugin.config or {} + local _, err_message = pdk.schema.check(config_schema, plugin_config) + if err_message then + pdk.log.error("[Limit-Conn] Authorization FAIL, backend config error, " .. err_message) + pdk.response.exit(500) end - local limit, err = create_limit_obj(plugin_conf) + local limit = create_limit_object(router.id, plugin_config) if not limit then - pdk.response.exit(500, { err_message = "failed to instantiate a resty.limit.conn object: " .. err }) + pdk.response.exit(500, { err_message = "[Limit-Conn] Failed to instantiate a Limit-Conn object" }) end - _M.conf = plugin_conf - local key = ngx_var[plugin_conf.key] or "0.0.0.0" - local delay, err = limit:incoming(key, true) + local unique_key = ngx_var.remote_addr + local delay, err = limit:incoming(unique_key, true) if not delay then if err == "rejected" then - pdk.response.exit(503, { err_message = err }) - else - pdk.response.exit(500, { err_message = err }) + pdk.response.exit(503, { err_message = "[Limit-Conn] Access denied" }) end + pdk.response.exit(500, { err_message = "[Limit-Conn] Failed to limit request, " .. err }) end if limit:is_committed() then - oak_ctx.limit_conn_key = key - oak_ctx.limit_conn_delay = delay + router_plugin.res = pdk.table.new(0, 3) + router_plugin.res.limit = limit + router_plugin.res.key = unique_key + router_plugin.res.delay = delay end if delay >= 0.001 then - ngx.sleep(delay) + ngx_sleep(delay) end + + plugins[plugin_name] = router_plugin + oak_ctx.router.plugins = plugins end function _M.http_log(oak_ctx) - if next(_M.conf) == nil then + local router = oak_ctx.router or {} + local plugins = router.plugins + + if not plugins then return end - local limit, err = create_limit_obj(_M.conf) - if not limit then - pdk.log.error("failed to instantiate a resty.limit.conn object: ", err) + + local router_plugin = plugins[plugin_name] + if not router_plugin then + return end - local key = oak_ctx.limit_conn_key - if key then - local latency = tonumber(ngx_var.request_time) - oak_ctx.limit_conn_delay - local conn, err = limit:leaving(key, latency) - if not conn then - pdk.log.error("failed to record the connection leaving ", "request: ", err) - end + + local limit_conn_res = router_plugin.res + if not limit_conn_res then + return + end + + local key = limit_conn_res.key + local limit = limit_conn_res.limit + local delay = limit_conn_res.delay + + local request_time = ngx_var.request_time + local latency = pdk.string.tonumber(request_time) - delay + local conn, err = limit:leaving(key, latency) + if not conn then + pdk.log.error("failed to record the connection leaving request: ", err) end end diff --git a/apioak/plugin/limit-count.lua b/apioak/plugin/limit-count.lua index 661d06d..413b588 100644 --- a/apioak/plugin/limit-count.lua +++ b/apioak/plugin/limit-count.lua @@ -1,9 +1,13 @@ -local limit_count_new = require("resty.limit.count").new -local ngx_var = ngx.var local pdk = require("apioak.pdk") +local sys = require("apioak.sys") +local limit_count = require("resty.limit.count") +local ngx_var = ngx.var + + +local plugin_name = "limit-count" local _M = { - name = "limit-count", + name = plugin_name, type = "Traffic Control", description = "Lua module for limiting request counts.", config = { @@ -24,69 +28,79 @@ local _M = { } } -local schema = { +local config_schema = { type = "object", properties = { count = { - type = "integer", - minLength = 1 + type = "integer", + minimum = 1, + maximum = 100000000, }, time_window = { - type = "integer", - minLength = 1 - }, - key = { - type = "string", + type = "integer", + minimum = 1, + maximum = 86400, } }, - required = { "count", "time_window", "key" } + required = { "count", "time_window" } } -local function create_limit_obj(conf) - local limit, err = pdk.shared.get(_M.key) - if not err then - return limit, nil - end +local function create_limit_object(router_id, config) + local cache_key = pdk.string.format("%s:ROUTER:%s", plugin_name, router_id) - limit, err = limit_count_new("plugin_limit_count", conf.count, conf.time_window) + local limit = sys.cache.get(cache_key) if not limit then - return nil, err + local err + limit, err = limit_count.new("plugin_limit_count", config.count, config.time_window) + if not limit then + pdk.log.error("[Limit-Count] failed to instantiate a resty.limit.count object: ", err) + else + sys.cache.set(cache_key, limit, 86400) + end end - pdk.shared.set(_M.key, limit) - return limit, nil + + return limit end function _M.http_access(oak_ctx) - if not oak_ctx.plugins or not oak_ctx.plugins[_M.name] then - return false, nil + local router = oak_ctx.router or {} + local plugins = router.plugins + + if not plugins then + return end - local plugin_conf = oak_ctx.plugins[_M.name] - local _, err = pdk.schema.check(schema, plugin_conf) - if err then - return false, nil + local router_plugin = plugins[plugin_name] + if not router_plugin then + return end - local limit, err = create_limit_obj(plugin_conf) + local plugin_config = router_plugin.config or {} + local _, err_message = pdk.schema.check(config_schema, plugin_config) + if err_message then + pdk.log.error("[Limit-Count] Authorization FAIL, backend config error, " .. err_message) + pdk.response.exit(500) + end + + local limit = create_limit_object(router.id, plugin_config) if not limit then - pdk.response.exit(500, - { err_message = "failed to instantiate a resty.limit.count object: " .. err }) + pdk.response.exit(500, { err_message = "[Limit-Count] Failed to instantiate a Limit-Count object" }) end - local key = ngx_var[plugin_conf.key] or "0.0.0.0" - local delay, err = limit:incoming(key, true) + local unique_key = ngx_var.remote_addr + local delay, err = limit:incoming(unique_key, true) if not delay then if err == "rejected" then - ngx.header["X-RateLimit-Limit"] = plugin_conf.rate - ngx.header["X-RateLimit-Remaining"] = 0 - pdk.response.exit(503, { err_message = err }) - else - pdk.response.exit(500, { err_message = err }) + pdk.response.set_header("X-RateLimit-Limit", router_plugin.config.count) + pdk.response.set_header("X-RateLimit-Remaining", 0) + pdk.response.exit(503, { err_message = "[Limit-Count] Access denied" }) end + pdk.response.exit(500, { err_message = "[Limit-Count] Failed to limit request, " .. err }) end - ngx.header["X-RateLimit-Limit"] = plugin_conf.rate - ngx.header["X-RateLimit-Remaining"] = err + local remaining = err + pdk.response.set_header("X-RateLimit-Limit", router_plugin.config.count) + pdk.response.set_header("X-RateLimit-Remaining", remaining) end return _M diff --git a/apioak/plugin/limit-req.lua b/apioak/plugin/limit-req.lua index 58dc49a..c5fcd06 100644 --- a/apioak/plugin/limit-req.lua +++ b/apioak/plugin/limit-req.lua @@ -1,6 +1,10 @@ -local limit_req_new = require("resty.limit.req").new -local ngx_var = ngx.var local pdk = require("apioak.pdk") +local sys = require("apioak.sys") +local limit_req = require("resty.limit.req") +local ngx_var = ngx.var +local ngx_sleep = ngx.sleep + +local plugin_name = "limit-req" local _M = { name = "limit-req", @@ -10,13 +14,13 @@ local _M = { rate = { type = "number", minimum = 1, - maximum = 10000, + maximum = 100000, default = 200, description = "the specified request rate (number per second) threshold." }, burst = { type = "number", - minimum = 1, + minimum = 0, maximum = 5000, default = 100, description = "the number of excessive requests per second allowed to be delayed." @@ -24,67 +28,77 @@ local _M = { } } -local schema = { +local config_schema = { type = "object", properties = { rate = { - type = "integer", - minLength = 1 + type = "integer", + minLength = 1, + minimum = 1, + maximum = 100000, }, burst = { - type = "integer", - minLength = 1 - }, - key = { - type = "string", + type = "integer", + minimum = 0, + maximum = 5000, } }, - required = { "rate", "burst", "key" } + required = { "rate", "burst" } } -local function create_limit_obj(conf) - local limit, err = pdk.shared.get(_M.key) - if not err then - return limit, nil - end +local function create_limit_object(router_id, config) + local cache_key = pdk.string.format("%s:ROUTER:%s", plugin_name, router_id) - limit, err = limit_req_new("plugin_limit_req", conf.rate, conf.burst) + local limit = sys.cache.get(cache_key) if not limit then - return nil, err + local err + limit, err = limit_req.new("plugin_limit_req", config.rate, config.burst) + if not limit then + pdk.log.error("[Limit-Req] failed to instantiate a resty.limit.req object: ", err) + else + sys.cache.set(cache_key, limit, 86400) + end end - pdk.shared.set(_M.key, limit) - return limit, nil + + return limit end function _M.http_access(oak_ctx) - if not oak_ctx.plugins or not oak_ctx.plugins[_M.name] then - return false, nil + local router = oak_ctx.router or {} + local plugins = router.plugins + + if not plugins then + return end - local plugin_conf = oak_ctx.plugins[_M.name] - local _, err = pdk.schema.check(schema, plugin_conf) - if err then - return false, nil + local router_plugin = plugins[plugin_name] + if not router_plugin then + return end - local limit, err = create_limit_obj(plugin_conf) + local plugin_config = router_plugin.config or {} + local _, err_message = pdk.schema.check(config_schema, plugin_config) + if err_message then + pdk.log.error("[Limit-Req] Authorization FAIL, backend config error, " .. err_message) + pdk.response.exit(500) + end + + local limit = create_limit_object(router.id, plugin_config) if not limit then - pdk.response.exit(500, - { err_message = "failed to instantiate a resty.limit.req object: " .. err }) + pdk.response.exit(500, { err_message = "[Limit-Req] Failed to instantiate a Limit-Req object" }) end - local key = ngx_var[plugin_conf.key] or "0.0.0.0" - local delay, err = limit:incoming(key, true) + local unique_key = ngx_var.remote_addr + local delay, err = limit:incoming(unique_key, true) if not delay then if err == "rejected" then - pdk.response.exit(503, { err_message = err }) - else - pdk.response.exit(500, { err_message = err }) + pdk.response.exit(503, { err_message = "[Limit-Req] Access denied" }) end + pdk.response.exit(500, { err_message = "[Limit-Req] Failed to limit request, " .. err }) end if delay >= 0.001 then - ngx.sleep(delay) + ngx_sleep(delay) end end -- Gitee From f8b13d4b1288d9fc4f10477c5722cda625162c4f Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:56:14 +0800 Subject: [PATCH 142/165] change: update description validation rules. --- apioak/schema/project.lua | 4 ++-- apioak/schema/router.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apioak/schema/project.lua b/apioak/schema/project.lua index 932cdb9..9adc665 100644 --- a/apioak/schema/project.lua +++ b/apioak/schema/project.lua @@ -66,7 +66,7 @@ _M.updated = { }, description = { type = 'string', - minLength = 5, + minLength = 0, maxLength = 100 }, upstreams = { @@ -170,7 +170,7 @@ _M.created = { }, description = { type = 'string', - minLength = 5, + minLength = 0, maxLength = 100 }, upstreams = { diff --git a/apioak/schema/router.lua b/apioak/schema/router.lua index 7bcef4a..ee8ab74 100644 --- a/apioak/schema/router.lua +++ b/apioak/schema/router.lua @@ -14,7 +14,7 @@ _M.created = { }, description = { type = 'string', - minLength = 5, + minLength = 0, maxLength = 100 }, request_path = { @@ -221,7 +221,7 @@ _M.updated = { }, description = { type = 'string', - minLength = 5, + minLength = 0, maxLength = 100 }, request_path = { -- Gitee From a2f477ef3f902cf9aff50cc6055e071e863af3d0 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:57:13 +0800 Subject: [PATCH 143/165] feature: import lrucache modules. --- apioak/sys.lua | 1 + apioak/sys/cache.lua | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 apioak/sys/cache.lua diff --git a/apioak/sys.lua b/apioak/sys.lua index 185c780..7c2f0da 100644 --- a/apioak/sys.lua +++ b/apioak/sys.lua @@ -1,6 +1,7 @@ return { meta = require("apioak.sys.meta"), admin = require("apioak.sys.admin"), + cache = require("apioak.sys.cache"), router = require("apioak.sys.router"), plugin = require("apioak.sys.plugin"), config = require("apioak.sys.config"), diff --git a/apioak/sys/cache.lua b/apioak/sys/cache.lua new file mode 100644 index 0000000..d9a5050 --- /dev/null +++ b/apioak/sys/cache.lua @@ -0,0 +1,46 @@ +local pdk = require("apioak.pdk") +local ngx_timer_at = ngx.timer.at +local lru_cache = require("resty.lrucache") +local lru_global + +local function created_cache(premature) + if premature then + return + end + + local cache, err = lru_cache.new(1024) -- allow up to 1024 items in the cache + if not cache then + pdk.log.error("failed to create the cache: ", err) + end + + lru_global = cache +end + +local _M = {} + +function _M.init_worker() + ngx_timer_at(0, created_cache) +end + +function _M.get(key) + if not lru_global then + created_cache() + end + return lru_global:get(key) +end + +function _M.set(key, val, ttl) + if not lru_global then + created_cache() + end + return lru_global:set(key, val, ttl) +end + +function _M.del(key) + if not lru_global then + created_cache() + end + return lru_global:delete(key) +end + +return _M -- Gitee From c86fd7760a3dd27c2e1fc70fa8889b0b4dd4c3be Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 13:58:57 +0800 Subject: [PATCH 144/165] change: updated resty.ngxvar to ngx.var module. --- apioak/sys/balancer.lua | 4 ++-- apioak/sys/router.lua | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apioak/sys/balancer.lua b/apioak/sys/balancer.lua index b66835a..cd1845f 100644 --- a/apioak/sys/balancer.lua +++ b/apioak/sys/balancer.lua @@ -3,7 +3,7 @@ local db = require("apioak.db") local ngx_sleep = ngx.sleep local ngx_timer_at = ngx.timer.at local ngx_worker_exiting = ngx.worker.exiting -local ngx_var = require("resty.ngxvar") +local ngx_var = ngx.var local balancer = require("ngx.balancer") local balancer_chash = require('resty.chash') local balancer_round = require('resty.roundrobin') @@ -127,7 +127,7 @@ function _M.gogogo(oak_ctx) local handler = upstream.handler local address if upstream.type == pdk.const.BALANCER_CHASH then - local request_address = ngx_var.fetch("remote_addr") + local request_address = ngx_var.remote_addr address = handler:find(request_address) end diff --git a/apioak/sys/router.lua b/apioak/sys/router.lua index 6bf9ae8..5891b9d 100644 --- a/apioak/sys/router.lua +++ b/apioak/sys/router.lua @@ -2,7 +2,7 @@ local pdk = require("apioak.pdk") local db = require("apioak.db") local pairs = pairs local r3route = require("resty.r3") -local ngx_var = require("resty.ngxvar") +local ngx_var = ngx.var local ngx_sleep = ngx.sleep local ngx_timer_at = ngx.timer.at local ngx_worker_exiting = ngx.worker.exiting @@ -97,18 +97,23 @@ local function automatic_sync_hash_id(premature) pdk.log.error("[sys.router] automatic sync routers last updated timestamp reading failure, ", err) break end - local router_hash_id = res.hash_id + local router_hash_id = res.hash_id or pdk.string.md5("routers") res, err = db.project.query_last_updated_hid() if err then pdk.log.error("[sys.router] automatic sync projects last updated timestamp reading failure, ", err) break end - local project_hash_id = res.hash_id + local project_hash_id = res.hash_id or pdk.string.md5("projects") - if router_hash_id and project_hash_id then - router_latest_hash_id = pdk.string.md5(router_hash_id .. project_hash_id) + res, err = db.plugin.query_project_last_updated_hid() + if err then + pdk.log.error("[sys.router] automatic sync plugins last updated timestamp reading failure, ", err) + break end + local plugin_hash_id = res.hash_id or pdk.string.md5("plugins") + + router_latest_hash_id = pdk.string.md5(project_hash_id .. router_hash_id .. plugin_hash_id) ngx_sleep(10) end @@ -159,8 +164,8 @@ function _M.matched(oak_ctx) end local match_uri = pdk.string.format("/%s%s", oak_ctx.matched.header[pdk.const.REQUEST_API_ENV_KEY], - ngx_var.fetch("uri")) - local match_ok = router_objects:dispatch(match_uri, pdk.request.method(), oak_ctx) + ngx_var.uri) + local match_ok = router_objects:dispatch(match_uri, pdk.request.get_method(), oak_ctx) if not match_ok then return false end -- Gitee From 768068366cf6a98b1bdf413a062a8936fea80726 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 14:00:14 +0800 Subject: [PATCH 145/165] feature: add upstream request rewrite function. --- apioak/apioak.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apioak/apioak.lua b/apioak/apioak.lua index a4a6650..b134364 100644 --- a/apioak/apioak.lua +++ b/apioak/apioak.lua @@ -19,7 +19,7 @@ local function run_plugin(phase, oak_ctx) end local function options_request_handle() - if pdk.request.method() == "OPTIONS" then + if pdk.request.get_method() == "OPTIONS" then pdk.response.exit(200, { err_message = "Welcome to APIOAK" }) @@ -57,6 +57,7 @@ function APIOAK.init_worker() sys.balancer.init_worker() + sys.cache.init_worker() end function APIOAK.http_access() @@ -108,6 +109,8 @@ function APIOAK.http_access() upstream_uri = upstream_uri .. "?" .. pdk.table.concat(query_args, "&") end + pdk.request.set_method(router.backend_method) + ngx.var.upstream_uri = upstream_uri sys.balancer.loading() -- Gitee From 39d82037361e0057d1c6ce9834bcac6652321ba3 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 14:27:47 +0800 Subject: [PATCH 146/165] change: remove dashboard submodule default import. --- .gitmodules | 4 ---- Makefile | 5 ----- dashboard | 1 - 3 files changed, 10 deletions(-) delete mode 100644 .gitmodules delete mode 160000 dashboard diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 27640d8..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "dashboard"] - path = dashboard - url = https://github.com/apioak/apioak-dashboard-built.git - branch = v0.3.0 diff --git a/Makefile b/Makefile index da9560e..befdf43 100644 --- a/Makefile +++ b/Makefile @@ -25,17 +25,12 @@ install: $(INSTALL) -d $(INST_OAK_PRODIR)/bin $(INSTALL) -d $(INST_OAK_PRODIR)/logs $(INSTALL) -d $(INST_OAK_PRODIR)/conf - $(INSTALL) -d $(INST_OAK_PRODIR)/dashboard $(INSTALL) -d $(INST_OAK_PRODIR)/apioak $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/sys $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/pdk $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/admin $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/plugin - git submodule update --init --recursive && \ - $(COPY) dashboard/* $(INST_OAK_PRODIR)/dashboard && \ - $(CHMOD) 775 $(INST_OAK_PRODIR)/dashboard - $(INSTALL) apioak/apioak.lua $(INST_OAK_PRODIR)/apioak/apioak.lua $(INSTALL) apioak/admin.lua $(INST_OAK_PRODIR)/apioak/admin.lua $(INSTALL) apioak/pdk.lua $(INST_OAK_PRODIR)/apioak/pdk.lua diff --git a/dashboard b/dashboard deleted file mode 160000 index ff45249..0000000 --- a/dashboard +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ff452496ecf638c5d527e3bc3e089f857baaf835 -- Gitee From a40f32bd3d4689dd5ccbc75d5c5b206de12dbaf2 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 14:40:41 +0800 Subject: [PATCH 147/165] change: update apioak 0.4.0 version. --- apioak/sys/meta.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioak/sys/meta.lua b/apioak/sys/meta.lua index 3084a20..30c306b 100644 --- a/apioak/sys/meta.lua +++ b/apioak/sys/meta.lua @@ -1,6 +1,6 @@ local version = setmetatable({ major = 0, - minor = 3, + minor = 4, patch = 0, }, { __tostring = function(v) -- Gitee From 24a61afce5ff9451dea073acb5918d52d83eeb7b Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 22 Mar 2020 17:12:07 +0800 Subject: [PATCH 148/165] change: update admin apis response status code. --- apioak/admin/account.lua | 14 +++++------ apioak/admin/controller.lua | 16 ++++++++----- apioak/admin/project.lua | 46 ++++++++++++++++++------------------- apioak/admin/router.lua | 16 ++++++------- apioak/admin/user.lua | 22 +++++++++--------- 5 files changed, 59 insertions(+), 55 deletions(-) diff --git a/apioak/admin/account.lua b/apioak/admin/account.lua index 21a3b37..fb4bb68 100644 --- a/apioak/admin/account.lua +++ b/apioak/admin/account.lua @@ -22,7 +22,7 @@ function account_controller.register() body.is_enable = 1 if body.password ~= body.valid_password then - pdk.response.exit(403, { err_message = "[account.authenticate] inconsistent password entry" }) + pdk.response.exit(501, { err_message = "inconsistent password entry" }) end res, err = db.user.create(body) @@ -31,7 +31,7 @@ function account_controller.register() end if res.insert_id == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -49,16 +49,16 @@ function account_controller.login() end if #res == 0 then - pdk.response.exit(403, { err_message = "[account.authenticate] account not exists" }) + pdk.response.exit(501, { err_message = "account not exists" }) end local user = res[1] if pdk.string.md5(body.password) ~= user.password then - pdk.response.exit(403, { err_message = "[account.authenticate] password validation failure" }) + pdk.response.exit(501, { err_message = "password validation failure" }) end if user.is_enable == 0 then - pdk.response.exit(403, { err_message = "[account.authenticate] account is disabled" }) + pdk.response.exit(501, { err_message = "account is disabled" }) end res, err = db.token.query_by_uid(user.id) @@ -77,7 +77,7 @@ function account_controller.login() end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else user = pdk.table.del(user, "password") user.token = res.token @@ -97,7 +97,7 @@ function account_controller.logout() end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end diff --git a/apioak/admin/controller.lua b/apioak/admin/controller.lua index 5ebf07f..9f5661b 100644 --- a/apioak/admin/controller.lua +++ b/apioak/admin/controller.lua @@ -38,7 +38,7 @@ local controller = function(name) cls.user_authenticate = function() local token = cls.get_header(pdk.const.REQUEST_ADMIN_TOKEN_KEY) if not token then - pdk.response.exit(401, { err_message = "[account.authenticate] property header \"" .. + pdk.response.exit(401, { err_message = "property header \"" .. pdk.const.REQUEST_ADMIN_TOKEN_KEY .. "\" is required" }) end @@ -48,14 +48,14 @@ local controller = function(name) end if #res == 0 then - pdk.response.exit(401, { err_message = "[account.authenticate] token \"" .. + pdk.response.exit(401, { err_message = "property token \"" .. token .. "\" invalid" }) end local exp_at = pdk.time.strtotime(res[1].expired_at) local now_at = pdk.time.time() if exp_at < now_at then - pdk.response.exit(401, { err_message = "[account.authenticate] token \"" .. + pdk.response.exit(401, { err_message = "property token \"" .. token .. "\" expired" }) end @@ -69,11 +69,15 @@ local controller = function(name) end if #res == 0 then - pdk.response.exit(401, { err_message = "[account.authenticate] account not exists" }) + pdk.response.exit(401, { err_message = "account does not exist" }) end local user = res[1] + if user.is_enable == 0 then + pdk.response.exit(401, { err_message = "account is disabled" }) + end + cls.uid = user.id cls.token = token @@ -93,7 +97,7 @@ local controller = function(name) end if #res == 0 then - pdk.response.exit(403, { err_message = "[project.authenticate] no project permissions" }) + pdk.response.exit(501, { err_message = "no project permissions" }) end return res[1] @@ -106,7 +110,7 @@ local controller = function(name) end if #res == 0 then - pdk.response.exit(403, { err_message = "[router.authenticate] no router permissions" }) + pdk.response.exit(501, { err_message = "no router permissions" }) end return cls.project_authenticate(res[1].project_id, user_id) diff --git a/apioak/admin/project.lua b/apioak/admin/project.lua index 6c11db1..890bfff 100644 --- a/apioak/admin/project.lua +++ b/apioak/admin/project.lua @@ -20,7 +20,7 @@ function project_controller.created() local project_id = res.insert_id if project_id == 0 then - pdk.response.exit(200, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) end for i = 1, #body.upstreams do @@ -47,7 +47,7 @@ function project_controller.created() end if res.insert_id == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -65,7 +65,7 @@ function project_controller.updated(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end end @@ -83,7 +83,7 @@ function project_controller.updated(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -105,7 +105,7 @@ function project_controller.selected(params) end if #res == 0 then - pdk.response.exit(403, { err_message = "[project.authenticate] project not exists" }) + pdk.response.exit(501, { err_message = "project not exists" }) end local project = res[1] @@ -127,7 +127,7 @@ function project_controller.deleted(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end end @@ -137,7 +137,7 @@ function project_controller.deleted(params) end if #res > 0 then - pdk.response.exit(403, { err_message = "[project.authenticate] routers in project were not deleted" }) + pdk.response.exit(501, { err_message = "routers in project were not deleted" }) end res, err = db.plugin.delete_by_res(db.plugin.RESOURCES_TYPE_PROJECT, params.project_id) @@ -161,7 +161,7 @@ function project_controller.deleted(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -197,12 +197,12 @@ function project_controller.member_created(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end end if pdk.string.tonumber(body.user_id) == project_controller.uid then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end local res, err = db.role.create(body.project_id, body.user_id, body.is_admin) @@ -211,7 +211,7 @@ function project_controller.member_created(params) end if res.insert_id == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -226,12 +226,12 @@ function project_controller.member_deleted(params) if not project_controller.is_owner then local user_group = project_controller.project_authenticate(params.project_id, project_controller.uid) if user_group.is_admin ~= 1 then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end end if pdk.string.tonumber(params.user_id) == project_controller.uid then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end local res, err = db.role.delete(params.project_id, params.user_id) @@ -240,7 +240,7 @@ function project_controller.member_deleted(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -259,12 +259,12 @@ function project_controller.member_updated(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end end if pdk.string.tonumber(params.user_id) == project_controller.uid then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end local res, err = db.role.update(params.project_id, params.user_id, body.is_admin) @@ -273,7 +273,7 @@ function project_controller.member_updated(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -309,7 +309,7 @@ function project_controller.plugin_created(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end end @@ -319,7 +319,7 @@ function project_controller.plugin_created(params) end if res.insert_id == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -338,7 +338,7 @@ function project_controller.plugin_updated(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end end @@ -348,7 +348,7 @@ function project_controller.plugin_updated(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -363,7 +363,7 @@ function project_controller.plugin_deleted(params) if not project_controller.is_owner then local role = project_controller.project_authenticate(params.project_id, project_controller.uid) if role.is_admin ~= 1 then - pdk.response.exit(403, { err_message = "[project.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end end @@ -373,7 +373,7 @@ function project_controller.plugin_deleted(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end diff --git a/apioak/admin/router.lua b/apioak/admin/router.lua index 29a5de8..20229fe 100644 --- a/apioak/admin/router.lua +++ b/apioak/admin/router.lua @@ -23,7 +23,7 @@ function router_controller.created() end if res.insert_id == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -70,7 +70,7 @@ function router_controller.updated(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -92,7 +92,7 @@ function router_controller.deleted(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -134,7 +134,7 @@ function router_controller.env_push(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -155,7 +155,7 @@ function router_controller.env_pull(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -198,7 +198,7 @@ function router_controller.plugin_created(params) end if res.insert_id == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -224,7 +224,7 @@ function router_controller.plugin_updated(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end @@ -246,7 +246,7 @@ function router_controller.plugin_deleted(params) end if res.affected_rows == 0 then - pdk.response.exit(403, { err_message = "FAIL" }) + pdk.response.exit(500, { err_message = "FAIL" }) else pdk.response.exit(200, { err_message = "OK" }) end diff --git a/apioak/admin/user.lua b/apioak/admin/user.lua index 9887875..a71bc22 100644 --- a/apioak/admin/user.lua +++ b/apioak/admin/user.lua @@ -14,11 +14,11 @@ function user_controller.created() user_controller.check_schema(schema.user.created, body) if body.password ~= body.valid_password then - pdk.response.exit(403, { err_message = "[user.authenticate] inconsistent password entry" }) + pdk.response.exit(501, { err_message = "inconsistent password entry" }) end if not user_controller.is_owner then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end local res, err = db.user.create(body) @@ -37,11 +37,11 @@ function user_controller.deleted(params) user_controller.user_authenticate() if not user_controller.is_owner then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end if pdk.string.tonumber(params.user_id) == user_controller.uid then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end local res, err = db.role.delete_by_uid(params.user_id) @@ -64,11 +64,11 @@ function user_controller.enable(params) user_controller.user_authenticate() if not user_controller.is_owner then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end if pdk.string.tonumber(params.user_id) == user_controller.uid then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end local res, err = db.user.update_status(params.user_id, 1) @@ -87,11 +87,11 @@ function user_controller.disable(params) user_controller.user_authenticate() if not user_controller.is_owner then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end if pdk.string.tonumber(params.user_id) == user_controller.uid then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end local res, err = db.user.update_status(params.user_id, 0) @@ -113,15 +113,15 @@ function user_controller.password(params) user_controller.user_authenticate() if not user_controller.is_owner then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end if pdk.string.tonumber(params.user_id) == user_controller.uid then - pdk.response.exit(403, { err_message = "[user.authenticate] no permissions" }) + pdk.response.exit(501, { err_message = "no permissions" }) end if body.password ~= body.valid_password then - pdk.response.exit(403, { err_message = "[user.authenticate] inconsistent password entry" }) + pdk.response.exit(501, { err_message = "inconsistent password entry" }) end local res, err = db.user.update_password(params.user_id, body.password) -- Gitee From 296ec4a61fc79a5f36494fef01b8b144c9fd8d65 Mon Sep 17 00:00:00 2001 From: Janko Date: Sat, 4 Apr 2020 00:28:32 +0800 Subject: [PATCH 149/165] feature: add admin members api. --- apioak/admin/common.lua | 20 +++++++++++++++++++- apioak/admin/project.lua | 2 +- apioak/db/user.lua | 12 +++++++++--- apioak/sys/admin.lua | 3 +++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/apioak/admin/common.lua b/apioak/admin/common.lua index 07782e9..763184d 100644 --- a/apioak/admin/common.lua +++ b/apioak/admin/common.lua @@ -9,7 +9,12 @@ function common_controller.users() common_controller.user_authenticate() - local res, err = db.user.all() + local res, err + if common_controller.is_owner then + res, err = db.user.all() + else + res, err = db.user.query_by_id(common_controller.uid) + end if err then pdk.response.exit(500, { err_message = err }) end @@ -17,6 +22,19 @@ function common_controller.users() pdk.response.exit(200, { err_message = "OK", users = res }) end +function common_controller.members() + + common_controller.user_authenticate() + + local res, err = db.user.all(true) + + if err then + pdk.response.exit(500, { err_message = err }) + end + + pdk.response.exit(200, { err_message = "OK", members = res }) +end + function common_controller.plugins() common_controller.user_authenticate() diff --git a/apioak/admin/project.lua b/apioak/admin/project.lua index 890bfff..66d6219 100644 --- a/apioak/admin/project.lua +++ b/apioak/admin/project.lua @@ -182,7 +182,7 @@ function project_controller.members(params) pdk.response.exit(500, { err_message = err }) end - pdk.response.exit(200, { err_message = "OK", users = res }) + pdk.response.exit(200, { err_message = "OK", members = res }) end function project_controller.member_created(params) diff --git a/apioak/db/user.lua b/apioak/db/user.lua index 912e284..5d1da1f 100644 --- a/apioak/db/user.lua +++ b/apioak/db/user.lua @@ -7,8 +7,13 @@ local table_name = "oak_users" _M.table_name = table_name -function _M.all() - local sql = pdk.string.format("SELECT id, name, email FROM %s WHERE is_enable = 1", table_name) +function _M.all(is_enable) + local sql + if is_enable then + sql = pdk.string.format("SELECT id, name, email FROM %s WHERE is_enable = 1", table_name) + else + sql = pdk.string.format("SELECT id, name, email, is_enable, is_owner FROM %s", table_name) + end local res, err = pdk.mysql.execute(sql) if err then return nil, err @@ -83,7 +88,8 @@ function _M.query_by_email(email) end function _M.query_by_id(uid) - local sql = pdk.string.format("SELECT * FROM %s WHERE id = %s", table_name, uid) + local sql = pdk.string.format("SELECT id, name, email, is_enable, is_owner FROM %s WHERE id = %s", + table_name, uid) local res, err = pdk.mysql.execute(sql) if err then return nil, err diff --git a/apioak/sys/admin.lua b/apioak/sys/admin.lua index cc03529..368d097 100644 --- a/apioak/sys/admin.lua +++ b/apioak/sys/admin.lua @@ -15,6 +15,9 @@ function _M.init_worker() router:insert_route("/apioak/admin/users", admin.common.users, { method = { "GET" } }) + router:insert_route("/apioak/admin/members", admin.common.members, + { method = { "GET" } }) + router:insert_route("/apioak/admin/projects", admin.common.projects, { method = { "GET" } }) -- Gitee From 3cc67684c7baf318d77d1a5b76ac649c548cd93b Mon Sep 17 00:00:00 2001 From: Janko Date: Sat, 4 Apr 2020 00:36:17 +0800 Subject: [PATCH 150/165] change: admin router api add project info. --- apioak/db/router.lua | 38 ++++++++++++++++++++++++++++---------- apioak/schema/router.lua | 4 ++-- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/apioak/db/router.lua b/apioak/db/router.lua index 74eef23..4a33d6e 100644 --- a/apioak/db/router.lua +++ b/apioak/db/router.lua @@ -176,17 +176,35 @@ end function _M.query(router_id) local sql = pdk.string.format([[ SELECT - id, name, enable_cors, description, constant_params, project_id, - request_path, request_method, request_params, - backend_path, backend_method, backend_params, - response_type, response_success, response_failure, response_codes, response_schema, - !ISNULL(env_prod_config) AS env_prod_publish, - !ISNULL(env_beta_config) AS env_beta_publish, - !ISNULL(env_test_config) AS env_test_publish - FROM %s + routers.id, + routers.name, + routers.enable_cors, + routers.description, + routers.constant_params, + routers.request_path, + routers.request_method, + routers.request_params, + routers.backend_path, + routers.backend_method, + routers.backend_params, + routers.response_type, + routers.response_success, + routers.response_failure, + routers.response_codes, + routers.response_schema, + !ISNULL(routers.env_prod_config) AS env_prod_publish, + !ISNULL(routers.env_beta_config) AS env_beta_publish, + !ISNULL(routers.env_test_config) AS env_test_publish, + projects.id AS project_id, + projects.name AS project_name, + projects.path AS project_path + FROM + %s AS routers + LEFT JOIN + %s AS projects ON routers.project_id = projects.id WHERE - id = %s - ]], table_name, router_id) + routers.id = %s + ]], table_name, project.table_name, router_id) local res, err = pdk.mysql.execute(sql) if err then diff --git a/apioak/schema/router.lua b/apioak/schema/router.lua index ee8ab74..de80491 100644 --- a/apioak/schema/router.lua +++ b/apioak/schema/router.lua @@ -5,7 +5,7 @@ _M.created = { properties = { name = { type = "string", - minLength = 5, + minLength = 3, maxLength = 20, }, enable_cors = { @@ -212,7 +212,7 @@ _M.updated = { properties = { name = { type = "string", - minLength = 5, + minLength = 3, maxLength = 20, }, enable_cors = { -- Gitee From 1a7166d465f380f3828c9950ab7a5e95798a4836 Mon Sep 17 00:00:00 2001 From: Janko Date: Sat, 4 Apr 2020 00:40:41 +0800 Subject: [PATCH 151/165] bugfix: upstream host redirect error and add welcome info. --- apioak/apioak.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apioak/apioak.lua b/apioak/apioak.lua index b134364..57e1505 100644 --- a/apioak/apioak.lua +++ b/apioak/apioak.lua @@ -19,7 +19,7 @@ local function run_plugin(phase, oak_ctx) end local function options_request_handle() - if pdk.request.get_method() == "OPTIONS" then + if pdk.request.get_method() == "OPTIONS" or ngx.var.uri == "/" then pdk.response.exit(200, { err_message = "Welcome to APIOAK" }) @@ -89,8 +89,9 @@ function APIOAK.http_access() sys.router.mapping(oak_ctx) - local router = oak_ctx.router - local matched = oak_ctx.matched + local router = oak_ctx.router + local matched = oak_ctx.matched + local upstream = router.upstream local upstream_uri = router.backend_path for path_key, path_val in pairs(matched.path) do @@ -113,6 +114,8 @@ function APIOAK.http_access() ngx.var.upstream_uri = upstream_uri + ngx.var.upstream_host = upstream.host + sys.balancer.loading() run_plugin("http_access", oak_ctx) -- Gitee From 0c59dbe16f0d8797cef4a7d94ee87aa02c8675ab Mon Sep 17 00:00:00 2001 From: Janko Date: Sat, 4 Apr 2020 18:21:16 +0800 Subject: [PATCH 152/165] change: update pdk.mysql to pdk.database filename. --- apioak/db/plugin.lua | 10 +++++----- apioak/db/project.lua | 16 ++++++++-------- apioak/db/role.lua | 16 ++++++++-------- apioak/db/router.lua | 24 ++++++++++++------------ apioak/db/token.lua | 14 +++++++------- apioak/db/upstream.lua | 14 +++++++------- apioak/db/user.lua | 18 +++++++++--------- apioak/pdk.lua | 2 +- apioak/pdk/{mysql.lua => database.lua} | 4 ++-- conf/apioak.yaml | 4 ++-- 10 files changed, 61 insertions(+), 61 deletions(-) rename apioak/pdk/{mysql.lua => database.lua} (92%) diff --git a/apioak/db/plugin.lua b/apioak/db/plugin.lua index 53e864f..6f7d81e 100644 --- a/apioak/db/plugin.lua +++ b/apioak/db/plugin.lua @@ -13,7 +13,7 @@ _M.RESOURCES_TYPE_PROJECT = "PROJECT" function _M.query_by_res(res_type, res_id) local sql = pdk.string.format("SELECT id, name, type, description, config FROM %s WHERE res_type = '%s' AND res_id = %s", table_name, res_type, res_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -29,7 +29,7 @@ end function _M.create_by_res(res_type, res_id, params) local sql = pdk.string.format("INSERT INTO %s (name, type, description, config, res_type, res_id) VALUES ('%s', '%s', '%s', '%s', '%s', '%s')", table_name, params.name, params.type, params.description, pdk.json.encode(params.config), res_type, res_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -57,7 +57,7 @@ function _M.delete_by_res(res_type, res_id, plugin_id) ]], table_name, res_id, res_type) end - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -84,7 +84,7 @@ function _M.update_by_res(res_type, res_id, plugin_id, params) plugin_id, res_id, res_type) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -105,7 +105,7 @@ function _M.query_project_last_updated_hid() DESC LIMIT 1 ]], table_name, _M.RESOURCES_TYPE_PROJECT) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end diff --git a/apioak/db/project.lua b/apioak/db/project.lua index c7e3a8e..d84bec0 100644 --- a/apioak/db/project.lua +++ b/apioak/db/project.lua @@ -41,7 +41,7 @@ function _M.all(project_name) ]], table_name) end - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -93,7 +93,7 @@ function _M.query_by_uid(user_id, project_name) ]], table_name, role.table_name, user_id) end - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -104,7 +104,7 @@ end function _M.created(params) local sql = pdk.string.format("INSERT INTO %s (name, description, path) VALUES ('%s', '%s', '%s')", table_name, params.name, params.description, params.path) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -115,7 +115,7 @@ end function _M.updated(project_id, params) local sql = pdk.string.format("UPDATE %s SET name = '%s', description = '%s', path = '%s' WHERE id = %s", table_name, params.name, params.description, params.path, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -127,7 +127,7 @@ end function _M.query(project_id) local sql = pdk.string.format("SELECT * FROM %s WHERE id = %s", table_name, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -139,7 +139,7 @@ end function _M.delete(project_id) local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", table_name, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -149,7 +149,7 @@ end function _M.query_env_all() local sql = pdk.string.format("SELECT id, path FROM %s", table_name) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -187,7 +187,7 @@ end function _M.query_last_updated_hid() local sql = pdk.string.format( "SELECT MD5(updated_at) AS hash_id FROM %s ORDER BY updated_at DESC LIMIT 1", table_name) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end diff --git a/apioak/db/role.lua b/apioak/db/role.lua index e106a3f..23e161c 100644 --- a/apioak/db/role.lua +++ b/apioak/db/role.lua @@ -9,7 +9,7 @@ _M.table_name = table_name function _M.create(project_id, user_id, is_admin) local sql = pdk.string.format("INSERT INTO %s (project_id, user_id, is_admin) VALUES ('%s', '%s', '%s')", table_name, project_id, user_id, is_admin) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -21,7 +21,7 @@ end function _M.query(project_id, user_id) local sql = pdk.string.format("SELECT * FROM %s WHERE project_id = %s AND user_id = %s", table_name, project_id, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -33,7 +33,7 @@ end function _M.delete(project_id, user_id) local sql = pdk.string.format("DELETE FROM %s WHERE project_id = %s AND user_id = %s", table_name, project_id, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -45,7 +45,7 @@ end function _M.update(project_id, user_id, is_admin) local sql = pdk.string.format("UPDATE %s SET is_admin = %s WHERE project_id = %s AND user_id = %s", table_name, is_admin, project_id, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -56,7 +56,7 @@ end function _M.query_by_uid(user_id) local sql = pdk.string.format("SELECT * FROM %s WHERE user_id = %s", table_name, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -67,7 +67,7 @@ end function _M.query_by_pid(project_id) local sql = pdk.string.format("SELECT * FROM %s WHERE project_id = %s", table_name, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -78,7 +78,7 @@ end function _M.delete_by_pid(project_id) local sql = pdk.string.format("DELETE FROM %s WHERE project_id = %s", table_name, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -89,7 +89,7 @@ end function _M.delete_by_uid(user_id) local sql = pdk.string.format("DELETE FROM %s WHERE user_id = %s", table_name, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end diff --git a/apioak/db/router.lua b/apioak/db/router.lua index 4a33d6e..a74d479 100644 --- a/apioak/db/router.lua +++ b/apioak/db/router.lua @@ -55,7 +55,7 @@ function _M.all(router_name) ]], table_name, project.table_name) end - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -133,7 +133,7 @@ function _M.query_by_uid(user_id, router_name) ]], table_name, project.table_name, pdk.table.concat(project_ids, ",")) end - res, err = pdk.mysql.execute(sql) + res, err = pdk.database.execute(sql) if err then return nil, err @@ -164,7 +164,7 @@ function _M.query_by_pid(project_id) routers.id DESC ]], table_name, project.table_name, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -203,9 +203,9 @@ function _M.query(router_id) LEFT JOIN %s AS projects ON routers.project_id = projects.id WHERE - routers.id = %s + id = %s ]], table_name, project.table_name, router_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -251,7 +251,7 @@ function _M.created(params) pdk.json.encode(params.response_codes), pdk.json.encode(params.response_schema), params.project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -288,7 +288,7 @@ function _M.updated(router_id, params) pdk.json.encode(params.response_codes), pdk.json.encode(params.response_schema), router_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -299,7 +299,7 @@ end function _M.deleted(router_id) local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", table_name, router_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -311,7 +311,7 @@ function _M.env_push(router_id, env, router_info) router_info = pdk.json.encode(router_info) local sql = pdk.string.format("UPDATE %s SET env_%s_config = '%s' WHERE id = %s", table_name, pdk.string.lower(env), router_info, router_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -322,7 +322,7 @@ end function _M.env_pull(router_id, env) local sql = pdk.string.format("UPDATE %s SET env_%s_config = NULL WHERE id = %s", table_name, pdk.string.lower(env), router_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -334,7 +334,7 @@ function _M.query_env_by_pid(project_id) local sql = "SELECT id, request_method, request_path, response_type, response_success, env_prod_config, " .. "env_beta_config, env_test_config FROM %s WHERE project_id = %s" sql = pdk.string.format(sql, table_name, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -351,7 +351,7 @@ end function _M.query_last_updated_hid() local sql = pdk.string.format( "SELECT MD5(updated_at) AS hash_id FROM %s ORDER BY updated_at DESC LIMIT 1", table_name) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end diff --git a/apioak/db/token.lua b/apioak/db/token.lua index cb254c6..dd91dfb 100644 --- a/apioak/db/token.lua +++ b/apioak/db/token.lua @@ -6,7 +6,7 @@ local table_name = "oak_tokens" function _M.query_by_uid(user_id) local sql = pdk.string.format("SELECT * FROM %s WHERE user_id = %s", table_name, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -16,7 +16,7 @@ end function _M.query_by_token(token) local sql = pdk.string.format("SELECT * FROM %s WHERE token = '%s'", table_name, token) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -30,7 +30,7 @@ function _M.create_by_uid(user_id) local sql = pdk.string.format("INSERT INTO %s (token, user_id, expired_at) VALUES ('%s', '%s', '%s')", table_name, token, user_id, expired) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -46,7 +46,7 @@ function _M.update_by_uid(user_id) local sql = pdk.string.format("UPDATE %s SET token = '%s', expired_at = '%s' WHERE user_id = %s", table_name, token, expired, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -61,7 +61,7 @@ function _M.continue_by_uid(user_id) local sql = pdk.string.format("UPDATE %s SET expired_at = '%s' WHERE user_id = %s", table_name, expired, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -75,7 +75,7 @@ function _M.continue_by_token(token) local sql = pdk.string.format("UPDATE %s SET expired_at = '%s' WHERE token = '%s'", table_name, expired, token) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -86,7 +86,7 @@ end function _M.expire_by_token(token) local sql = pdk.string.format("UPDATE %s SET expired_at = NULL WHERE token = '%s'", table_name, token) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err diff --git a/apioak/db/upstream.lua b/apioak/db/upstream.lua index 2cfc78e..0b59dce 100644 --- a/apioak/db/upstream.lua +++ b/apioak/db/upstream.lua @@ -8,7 +8,7 @@ _M.table_name = table_name function _M.all() local sql = pdk.string.format("SELECT * FROM %s", table_name) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -40,7 +40,7 @@ function _M.create(params) params.project_id, pdk.json.encode(params.timeouts), pdk.json.encode(params.nodes)) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -59,7 +59,7 @@ function _M.update_by_pid(pid, upstream) ]], table_name, upstream.host, upstream.type, pdk.json.encode(upstream.timeouts), pdk.json.encode(upstream.nodes), upstream.id, pid) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -69,7 +69,7 @@ end function _M.query(upstream_id) local sql = pdk.string.format("SELECT * FROM %s WHERE id = %s", table_name, upstream_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -79,7 +79,7 @@ end function _M.query_by_pid(project_id) local sql = pdk.string.format("SELECT * FROM %s WHERE project_id = %s", table_name, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -95,7 +95,7 @@ end function _M.delete_by_pid(project_id) local sql = pdk.string.format("DELETE FROM %s WHERE project_id = %s", table_name, project_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -107,7 +107,7 @@ end function _M.query_last_updated_hid() local sql = pdk.string.format( "SELECT MD5(updated_at) AS hash_id FROM %s ORDER BY updated_at DESC LIMIT 1", table_name) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end diff --git a/apioak/db/user.lua b/apioak/db/user.lua index 5d1da1f..fb2e9b3 100644 --- a/apioak/db/user.lua +++ b/apioak/db/user.lua @@ -14,7 +14,7 @@ function _M.all(is_enable) else sql = pdk.string.format("SELECT id, name, email, is_enable, is_owner FROM %s", table_name) end - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -26,7 +26,7 @@ function _M.create(params) "INSERT INTO %s (name, password, email, is_owner, is_enable) VALUES ('%s', '%s', '%s', '%s', '%s')", table_name, params.name, pdk.string.md5(params.password), params.email, params.is_owner or 0, params.is_enable or 0) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -37,7 +37,7 @@ end function _M.update(user_id, params) local sql = pdk.string.format("UPDATE %s SET name = '%s', password = '%s', email = '%s', is_enable = %s WHERE id = %s", table_name, params.name, pdk.string.md5(params.password), params.email, params.is_enable, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -48,7 +48,7 @@ end function _M.delete(user_id) local sql = pdk.string.format("DELETE FROM %s WHERE id = %s", table_name, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -59,7 +59,7 @@ end function _M.update_password(user_id, password) local sql = pdk.string.format("UPDATE %s SET password = '%s' WHERE id = %s", table_name, pdk.string.md5(password), user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -70,7 +70,7 @@ end function _M.update_status(user_id, status) local sql = pdk.string.format("UPDATE %s SET is_enable = %s WHERE id = %s", table_name, status, user_id) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err @@ -80,7 +80,7 @@ end function _M.query_by_email(email) local sql = pdk.string.format("SELECT * FROM %s WHERE email = '%s'", table_name, email) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -90,7 +90,7 @@ end function _M.query_by_id(uid) local sql = pdk.string.format("SELECT id, name, email, is_enable, is_owner FROM %s WHERE id = %s", table_name, uid) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err end @@ -101,7 +101,7 @@ function _M.query_by_pid(gid) local sql = pdk.string.format( "SELECT users.id, users.name, users.email, roles.is_admin FROM %s AS roles LEFT JOIN %s AS users ON roles.user_id = users.id WHERE roles.project_id = %s", role.table_name, table_name, gid) - local res, err = pdk.mysql.execute(sql) + local res, err = pdk.database.execute(sql) if err then return nil, err diff --git a/apioak/pdk.lua b/apioak/pdk.lua index fc51929..264202a 100644 --- a/apioak/pdk.lua +++ b/apioak/pdk.lua @@ -13,5 +13,5 @@ return { admin = require("apioak.pdk.admin"), pool = require("apioak.pdk.tablepool"), const = require("apioak.pdk.const"), - mysql = require("apioak.pdk.mysql"), + database = require("apioak.pdk.database"), } diff --git a/apioak/pdk/mysql.lua b/apioak/pdk/database.lua similarity index 92% rename from apioak/pdk/mysql.lua rename to apioak/pdk/database.lua index 4ca755e..90cb8d5 100644 --- a/apioak/pdk/mysql.lua +++ b/apioak/pdk/database.lua @@ -10,7 +10,7 @@ function _M.new() end local db = res - res, err = config.query("mysql") + res, err = config.query("database") if err then return nil, err end @@ -21,7 +21,7 @@ function _M.new() res, err = db:connect({ host = conf.host or "127.0.0.1", port = conf.port or 3306, - database = conf.database or "apioak", + database = conf.db_name or "apioak", user = conf.user or "apioak", password = conf.password or "" }) diff --git a/conf/apioak.yaml b/conf/apioak.yaml index cd1a904..efc34ac 100644 --- a/conf/apioak.yaml +++ b/conf/apioak.yaml @@ -1,7 +1,7 @@ -mysql: +database: host: 127.0.0.1 port: 3306 - database: apioak + db_name: apioak user: root password: 123000 timeout: 1000 # millisecond -- Gitee From 26004bbec47ac2ece73620601ed5697c1b91f7b4 Mon Sep 17 00:00:00 2001 From: Janko Date: Sat, 4 Apr 2020 18:25:49 +0800 Subject: [PATCH 153/165] bugfix: fix db.router query error. --- apioak/db/router.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apioak/db/router.lua b/apioak/db/router.lua index a74d479..a188bbb 100644 --- a/apioak/db/router.lua +++ b/apioak/db/router.lua @@ -203,7 +203,7 @@ function _M.query(router_id) LEFT JOIN %s AS projects ON routers.project_id = projects.id WHERE - id = %s + routers.id = %s ]], table_name, project.table_name, router_id) local res, err = pdk.database.execute(sql) -- Gitee From 0cc9aec133a07561fe4477a25c7d34ec90c68b72 Mon Sep 17 00:00:00 2001 From: Janko Date: Sat, 4 Apr 2020 18:35:38 +0800 Subject: [PATCH 154/165] change: update connection test server port. --- conf/nginx.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/nginx.conf b/conf/nginx.conf index 3ff2adf..45afd46 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -59,7 +59,7 @@ http { } server { - listen 10111; + listen 10666; location / { content_by_lua_block { @@ -80,7 +80,7 @@ http { } server { - listen 10222; + listen 10888; location / { content_by_lua_block { -- Gitee From d3fcf05aab213d121b7660ece2a715e06ffc074e Mon Sep 17 00:00:00 2001 From: Janko Date: Sat, 4 Apr 2020 21:41:57 +0800 Subject: [PATCH 155/165] change: update package path and dashboard power. --- conf/nginx.conf | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/conf/nginx.conf b/conf/nginx.conf index 45afd46..824963b 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -15,7 +15,7 @@ worker_shutdown_timeout 3s; http { include mime.types; - lua_package_path "$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/apioak/?.lua;$prefix/?.lua;/usr/share/lua/5.1/?.lua;/usr/local/lor/?.lua;;"; + lua_package_path "$prefix/deps/share/lua/5.1/?.lua;$prefix/?.lua;/usr/share/lua/5.1/?.lua;;"; lua_package_cpath "$prefix/deps/lib64/lua/5.1/?.so;$prefix/deps/lib/lua/5.1/?.so;/usr/lib64/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;;"; log_format main '$remote_addr\t$http_x_forwarded_for\t$time_iso8601\t$scheme://$http_host\t$request\t$request_length\t' @@ -102,9 +102,6 @@ http { } location /apioak/dashboard { - allow 127.0.0.0/24; - deny all; - index index.html; alias dashboard/; -- Gitee From 176588c61f4d9c9c2786a1c274ddcca81c55df60 Mon Sep 17 00:00:00 2001 From: Janko Date: Sat, 4 Apr 2020 21:51:40 +0800 Subject: [PATCH 156/165] feature: add logs placeholder folder. --- .gitignore | 4 +--- logs/apioak.txt | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 logs/apioak.txt diff --git a/.gitignore b/.gitignore index 14b2dfc..357ca3c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,4 @@ luac.out .idea -deps/ -logs/ -*.DS_Store \ No newline at end of file +*.DS_Store diff --git a/logs/apioak.txt b/logs/apioak.txt new file mode 100644 index 0000000..7efa16b --- /dev/null +++ b/logs/apioak.txt @@ -0,0 +1 @@ +Welcome to APIOAK -- Gitee From 21bdf550a4d93ba9360a9c3df90ca6719fecb6cb Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 5 Apr 2020 00:29:38 +0800 Subject: [PATCH 157/165] change: update install dependencies document. --- doc/install-dependencies.md | 109 ++++++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 16 deletions(-) diff --git a/doc/install-dependencies.md b/doc/install-dependencies.md index b64a09e..e456092 100644 --- a/doc/install-dependencies.md +++ b/doc/install-dependencies.md @@ -6,42 +6,119 @@ CentOS 7 ======== +> Install OpenResty and other required dependencies. + ```shell -# install epel, `luarocks` need it. +# Install epel, `LuaRocks` need it. + wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm sudo rpm -ivh epel-release-latest-7.noarch.rpm -# add openresty source + +# Addition `OpenResty` Repo. + sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo -# install openresty and some compilation tools -sudo yum install -y openresty openresty-resty curl git automake autoconf \ - gcc pcre-devel openssl-devel libtool gcc-c++ luarocks cmake3 lua-devel etcd -sudo ln -s /usr/bin/cmake3 /usr/bin/cmake +# Install OpenResty and Dependencies. + +sudo yum install -y gcc \ + gcc-c++ \ + git \ + curl \ + wget \ + openresty \ + openresty-resty \ + automake \ + autoconf \ + luarocks \ + lua-devel \ + libtool \ + pcre-devel +``` + + +> Install MariaDB + +```shell +# Addition `MariaDB` Repo. + +sudo cat > /etc/yum.repos.d/MariaDB.repo < Install OpenResty and other required dependencies. + ```shell -# add openresty source +# Addition `OpenResty` Repo. + wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add - sudo apt-get update sudo apt-get -y install software-properties-common sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" sudo apt-get update -# install openresty and some compilation tools -sudo apt-get install -y openresty openresty-resty curl git luarocks\ - check libpcre3 libpcre3-dev libjemalloc-dev \ - libjemalloc1 build-essential libtool libssl1.0-dev automake autoconf pkg-config \ - cmake etcd -# start etcd server -sudo service etcd start +# Install OpenResty and Dependencies. + +sudo apt-get install -y build-essential \ + gcc \ + g++ \ + git \ + curl \ + wget \ + openresty \ + openresty-resty \ + automake \ + autoconf \ + luarocks \ + libtool \ + libpcre3-dev + + +# After successful installation of OpenResty, it will start by default, first shut him down. + +sudo openresty -s stop +``` + + +> Install MariaDB + +```shell +# key is imported and the repository added. + +sudo apt-get -y install software-properties-common +sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc' +sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://mirror.hosting90.cz/mariadb/repo/10.2/ubuntu bionic main' +sudo apt update + + +# Install `MariaDB` and set root password (After installation, set the root password according to the system prompt). + +apt -y install mariadb-server ``` -- Gitee From 94d93d6dafb716c8a3334b4deea9534231a35417 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 5 Apr 2020 00:40:07 +0800 Subject: [PATCH 158/165] change: update default file permissions. --- apioak/admin.lua | 0 apioak/admin/account.lua | 0 apioak/admin/common.lua | 0 apioak/admin/controller.lua | 0 apioak/admin/project.lua | 0 apioak/admin/router.lua | 0 apioak/admin/user.lua | 0 apioak/apioak.lua | 0 apioak/db.lua | 0 apioak/db/plugin.lua | 0 apioak/db/project.lua | 0 apioak/db/role.lua | 0 apioak/db/router.lua | 0 apioak/db/token.lua | 0 apioak/db/upstream.lua | 0 apioak/db/user.lua | 0 apioak/pdk.lua | 0 apioak/pdk/admin.lua | 0 apioak/pdk/const.lua | 0 apioak/pdk/ctx.lua | 0 apioak/pdk/database.lua | 0 apioak/pdk/json.lua | 0 apioak/pdk/log.lua | 0 apioak/pdk/plugin.lua | 0 apioak/pdk/request.lua | 0 apioak/pdk/response.lua | 0 apioak/pdk/schema.lua | 0 apioak/pdk/shared.lua | 0 apioak/pdk/string.lua | 0 apioak/pdk/table.lua | 0 apioak/pdk/tablepool.lua | 0 apioak/pdk/time.lua | 0 apioak/plugin/jwt-auth.lua | 0 apioak/plugin/key-auth.lua | 0 apioak/plugin/limit-conn.lua | 0 apioak/plugin/limit-count.lua | 0 apioak/plugin/limit-req.lua | 0 apioak/schema.lua | 0 apioak/schema/account.lua | 0 apioak/schema/common.lua | 0 apioak/schema/project.lua | 0 apioak/schema/router.lua | 0 apioak/schema/user.lua | 0 apioak/sys.lua | 0 apioak/sys/admin.lua | 0 apioak/sys/balancer.lua | 0 apioak/sys/cache.lua | 0 apioak/sys/config.lua | 0 apioak/sys/meta.lua | 0 apioak/sys/plugin.lua | 0 apioak/sys/router.lua | 0 conf/apioak.sql | 0 conf/apioak.yaml | 0 conf/mime.types | 0 conf/nginx.conf | 0 55 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 apioak/admin.lua mode change 100644 => 100755 apioak/admin/account.lua mode change 100644 => 100755 apioak/admin/common.lua mode change 100644 => 100755 apioak/admin/controller.lua mode change 100644 => 100755 apioak/admin/project.lua mode change 100644 => 100755 apioak/admin/router.lua mode change 100644 => 100755 apioak/admin/user.lua mode change 100644 => 100755 apioak/apioak.lua mode change 100644 => 100755 apioak/db.lua mode change 100644 => 100755 apioak/db/plugin.lua mode change 100644 => 100755 apioak/db/project.lua mode change 100644 => 100755 apioak/db/role.lua mode change 100644 => 100755 apioak/db/router.lua mode change 100644 => 100755 apioak/db/token.lua mode change 100644 => 100755 apioak/db/upstream.lua mode change 100644 => 100755 apioak/db/user.lua mode change 100644 => 100755 apioak/pdk.lua mode change 100644 => 100755 apioak/pdk/admin.lua mode change 100644 => 100755 apioak/pdk/const.lua mode change 100644 => 100755 apioak/pdk/ctx.lua mode change 100644 => 100755 apioak/pdk/database.lua mode change 100644 => 100755 apioak/pdk/json.lua mode change 100644 => 100755 apioak/pdk/log.lua mode change 100644 => 100755 apioak/pdk/plugin.lua mode change 100644 => 100755 apioak/pdk/request.lua mode change 100644 => 100755 apioak/pdk/response.lua mode change 100644 => 100755 apioak/pdk/schema.lua mode change 100644 => 100755 apioak/pdk/shared.lua mode change 100644 => 100755 apioak/pdk/string.lua mode change 100644 => 100755 apioak/pdk/table.lua mode change 100644 => 100755 apioak/pdk/tablepool.lua mode change 100644 => 100755 apioak/pdk/time.lua mode change 100644 => 100755 apioak/plugin/jwt-auth.lua mode change 100644 => 100755 apioak/plugin/key-auth.lua mode change 100644 => 100755 apioak/plugin/limit-conn.lua mode change 100644 => 100755 apioak/plugin/limit-count.lua mode change 100644 => 100755 apioak/plugin/limit-req.lua mode change 100644 => 100755 apioak/schema.lua mode change 100644 => 100755 apioak/schema/account.lua mode change 100644 => 100755 apioak/schema/common.lua mode change 100644 => 100755 apioak/schema/project.lua mode change 100644 => 100755 apioak/schema/router.lua mode change 100644 => 100755 apioak/schema/user.lua mode change 100644 => 100755 apioak/sys.lua mode change 100644 => 100755 apioak/sys/admin.lua mode change 100644 => 100755 apioak/sys/balancer.lua mode change 100644 => 100755 apioak/sys/cache.lua mode change 100644 => 100755 apioak/sys/config.lua mode change 100644 => 100755 apioak/sys/meta.lua mode change 100644 => 100755 apioak/sys/plugin.lua mode change 100644 => 100755 apioak/sys/router.lua mode change 100644 => 100755 conf/apioak.sql mode change 100644 => 100755 conf/apioak.yaml mode change 100644 => 100755 conf/mime.types mode change 100644 => 100755 conf/nginx.conf diff --git a/apioak/admin.lua b/apioak/admin.lua old mode 100644 new mode 100755 diff --git a/apioak/admin/account.lua b/apioak/admin/account.lua old mode 100644 new mode 100755 diff --git a/apioak/admin/common.lua b/apioak/admin/common.lua old mode 100644 new mode 100755 diff --git a/apioak/admin/controller.lua b/apioak/admin/controller.lua old mode 100644 new mode 100755 diff --git a/apioak/admin/project.lua b/apioak/admin/project.lua old mode 100644 new mode 100755 diff --git a/apioak/admin/router.lua b/apioak/admin/router.lua old mode 100644 new mode 100755 diff --git a/apioak/admin/user.lua b/apioak/admin/user.lua old mode 100644 new mode 100755 diff --git a/apioak/apioak.lua b/apioak/apioak.lua old mode 100644 new mode 100755 diff --git a/apioak/db.lua b/apioak/db.lua old mode 100644 new mode 100755 diff --git a/apioak/db/plugin.lua b/apioak/db/plugin.lua old mode 100644 new mode 100755 diff --git a/apioak/db/project.lua b/apioak/db/project.lua old mode 100644 new mode 100755 diff --git a/apioak/db/role.lua b/apioak/db/role.lua old mode 100644 new mode 100755 diff --git a/apioak/db/router.lua b/apioak/db/router.lua old mode 100644 new mode 100755 diff --git a/apioak/db/token.lua b/apioak/db/token.lua old mode 100644 new mode 100755 diff --git a/apioak/db/upstream.lua b/apioak/db/upstream.lua old mode 100644 new mode 100755 diff --git a/apioak/db/user.lua b/apioak/db/user.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk.lua b/apioak/pdk.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/admin.lua b/apioak/pdk/admin.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/const.lua b/apioak/pdk/const.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/ctx.lua b/apioak/pdk/ctx.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/database.lua b/apioak/pdk/database.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/json.lua b/apioak/pdk/json.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/log.lua b/apioak/pdk/log.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/plugin.lua b/apioak/pdk/plugin.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/request.lua b/apioak/pdk/request.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/response.lua b/apioak/pdk/response.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/schema.lua b/apioak/pdk/schema.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/shared.lua b/apioak/pdk/shared.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/string.lua b/apioak/pdk/string.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/table.lua b/apioak/pdk/table.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/tablepool.lua b/apioak/pdk/tablepool.lua old mode 100644 new mode 100755 diff --git a/apioak/pdk/time.lua b/apioak/pdk/time.lua old mode 100644 new mode 100755 diff --git a/apioak/plugin/jwt-auth.lua b/apioak/plugin/jwt-auth.lua old mode 100644 new mode 100755 diff --git a/apioak/plugin/key-auth.lua b/apioak/plugin/key-auth.lua old mode 100644 new mode 100755 diff --git a/apioak/plugin/limit-conn.lua b/apioak/plugin/limit-conn.lua old mode 100644 new mode 100755 diff --git a/apioak/plugin/limit-count.lua b/apioak/plugin/limit-count.lua old mode 100644 new mode 100755 diff --git a/apioak/plugin/limit-req.lua b/apioak/plugin/limit-req.lua old mode 100644 new mode 100755 diff --git a/apioak/schema.lua b/apioak/schema.lua old mode 100644 new mode 100755 diff --git a/apioak/schema/account.lua b/apioak/schema/account.lua old mode 100644 new mode 100755 diff --git a/apioak/schema/common.lua b/apioak/schema/common.lua old mode 100644 new mode 100755 diff --git a/apioak/schema/project.lua b/apioak/schema/project.lua old mode 100644 new mode 100755 diff --git a/apioak/schema/router.lua b/apioak/schema/router.lua old mode 100644 new mode 100755 diff --git a/apioak/schema/user.lua b/apioak/schema/user.lua old mode 100644 new mode 100755 diff --git a/apioak/sys.lua b/apioak/sys.lua old mode 100644 new mode 100755 diff --git a/apioak/sys/admin.lua b/apioak/sys/admin.lua old mode 100644 new mode 100755 diff --git a/apioak/sys/balancer.lua b/apioak/sys/balancer.lua old mode 100644 new mode 100755 diff --git a/apioak/sys/cache.lua b/apioak/sys/cache.lua old mode 100644 new mode 100755 diff --git a/apioak/sys/config.lua b/apioak/sys/config.lua old mode 100644 new mode 100755 diff --git a/apioak/sys/meta.lua b/apioak/sys/meta.lua old mode 100644 new mode 100755 diff --git a/apioak/sys/plugin.lua b/apioak/sys/plugin.lua old mode 100644 new mode 100755 diff --git a/apioak/sys/router.lua b/apioak/sys/router.lua old mode 100644 new mode 100755 diff --git a/conf/apioak.sql b/conf/apioak.sql old mode 100644 new mode 100755 diff --git a/conf/apioak.yaml b/conf/apioak.yaml old mode 100644 new mode 100755 diff --git a/conf/mime.types b/conf/mime.types old mode 100644 new mode 100755 diff --git a/conf/nginx.conf b/conf/nginx.conf old mode 100644 new mode 100755 -- Gitee From 3943faeae172f992d72f0aec005a0591c33bb97b Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 5 Apr 2020 01:48:48 +0800 Subject: [PATCH 159/165] change: update version 0.4.0 README. --- README.md | 75 ++++++++++++++++++++++++++----- README_CN.md | 78 +++++++++++++++++++++++++++------ doc/images/APIOAK-process.jpeg | Bin 312169 -> 0 bytes doc/images/APIOAK-process.png | Bin 0 -> 76689 bytes 4 files changed, 128 insertions(+), 25 deletions(-) delete mode 100644 doc/images/APIOAK-process.jpeg create mode 100644 doc/images/APIOAK-process.png diff --git a/README.md b/README.md index 903e94b..d426260 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[简体中文](README_CN.md) | [English](README.md) + # APIOAK [![Build Status](https://travis-ci.org/apioak/apioak.svg?branch=master)](https://travis-ci.org/apioak/apioak) @@ -8,25 +10,67 @@ APIOAK provides full life cycle management of API release, management, and opera ## Why APIOAK -APIOAK can help you isolate internal and external traffic, provide dynamic load balancing, authentication, rate limiting, etc. through plugin mechanisms, and support your own custom plugins. +APIOAK performance is almost comparable to native `Nginx`, and provides dynamic authentication, flow control and other functions through the plug-in mechanism, and supports custom plug-ins according to specific business scenarios. It also provides a multiple of dynamic load balancing strategies and a powerful and easy-to-use console management panel. -![APIOAK](doc/images/APIOAK-process.jpeg) +![APIOAK](doc/images/APIOAK-process.png) ## Features -- **Dynamic Load Balancing:** Round-robin load balancing with weight. -- **Hash-based Load Balancing:** Load balance with consistent hashing sessions. -- **Multi environment deployment Publishing:** Support the release and deployment of `prod`,` beta`, and `dev` environments. -- **Plugins hot update and hot plug:** All plugins support hot update and dynamic plugin. -- **High scalability:** Custom plugins can mount any Openresty execution phase for different demand scenarios. -- **Mock request:** Supports responding to the client with preset data, speeding up the front-end and back-end separation development process. -- **Distributed deployment:** Data storage, service discovery, configuration sharing via `etcd`. +- **Projects** + + - Support project prefix for multi-tenant isolation. + + - Support multi-environment configuration, `Production Environment`,` Pre-launch Environment`, `Test Environment` completely isolated to meet the full life cycle management of `CI` and `CD`. + + - Support dynamic weighted `Round-Robin` load balancing. + + - Support dynamic consistency `Hash` load balancing. + + - Support dynamic node configuration, dynamic `Host` configuration. + + - Support upstream service `Connection`,` Send`, `Read` timeout setting. + + - Support plug-in hot plug, project plug-in can be inherited by all routes(APIs) under the project. + + - Support automatic generation of project documents. + + - Support project member management. + +- **Routers** + + - Support front-end and back-end request routing mapping. + + - Support front-end and back-end request method mapping. + + - Support cross mapping of front and back request parameters. + + - Support request constant parameter definition. + + - Support custom response data and response data type. + + - Support plug-in hot swap. + + - Support Mock request, accelerate the development process of front and back end separation. + + - Supports automatic generation of routing (APIs) documents. + + - Support multi-environment routing (APIs) online and offline. + + - Support multi-environment routing (APIs) one-click replication. + +- **Users** + + - Support user login and registration. + + - Support user creation and editing. + + - Support users to disable globally. ## Installation -System dependencies (`openresty`, `resty-cli`, `luarocks`, etc.) necessary to install `APIOAK` on different operating systems, See: [Install Dependencies](doc/install-dependencies.md) Document. +System dependencies (`OpenResty >= 1.15.8.2`、`luarocks >= 2.3`、`MySQL >= 5.7 or MariaDB >= 10.2`, etc.) necessary to install `APIOAK` on different operating systems, See: [Install Dependencies](doc/install-dependencies.md) Document. > Installation via LuaRocks @@ -50,16 +94,23 @@ sudo dpkg -i apioak-{VERSION}-1_amd64.deb ## Quickstart +> Configure APIOAK + +- Import the database configuration file into `MySQL` or` MariaDB`, the configuration file path `/path/conf/apioak.sql`. + +- Edit database connection information of the `database` option in the` APIOAK` configuration file, the configuration file path `/path/conf/apioak.yaml`. + > Launch APIOAK ```bash sudo apioak start ``` +> Access APIOAK -## Contributing +- Enter `http://127.0.0.1:10080/apioak/dashboard` in the browser to access dashboard management panel. -See the [CONTRIBUTING](CONTRIBUTING.md) document. +At this point, `APIOAK` has all been installed and configured, please enjoy it. ## Thanks diff --git a/README_CN.md b/README_CN.md index d77ec7d..f46c91c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,3 +1,5 @@ +[简体中文](README_CN.md) | [English](README.md) + # APIOAK [![Build Status](https://travis-ci.org/apioak/apioak.svg?branch=master)](https://travis-ci.org/apioak/apioak) @@ -8,25 +10,67 @@ APIOAK 提供API发布、管理、运维的全生命周期管理。辅助用户 ## 为什么选择APIOAK -APIOAK 可以帮你隔离内外部流量,通过插件机制提供动态负载平衡,身份验证,速率限制等,并支持您自己的自定义插件。 +APIOAK 提供了几乎可以媲美原生 `Nginx` 的强劲性能,通过插件机制提供动态身份认证、流量控制等功能,并支持根据特定业务场景的自定义插件。同时还提供了多种动态负载均衡策略和功能强大易用的控制台管理面板。 -![APIOAK](doc/images/APIOAK-process.jpeg) +![APIOAK](doc/images/APIOAK-process.png) ## 功能 -- **动态轮询 `round` 负载均衡:** 动态支持有权重的 `round-robin` 负载平衡。 -- **动态一致性 `hash` 负载均衡:** 动态支持一致性 `hash` 的负载均衡。 -- **多环境部署发布:** 提供多种发布环境`prod`,`beta`和`dev`,满足不同场景使用需求。 -- **插件热更新和热插拔:** 所有插件均支持热更新和动态插拔。 -- **高扩展性:** 自定义插件可以挂载任意 `Openresty` 执行阶段,用于不同需求场景。 -- **Mock请求:** 支持通过预设数据响应客户端,加速前后端分离开发过程。 -- **分布式部署:** 通过 `etcd` 进行数据存储、服务发现、配置共享。 +- **项目** + + - 支持项目前缀,用于多租户隔离。 + + - 支持多环境环境配置,`生产环境`、`预发环境`、`测试环境` 不同环境完全隔离,满足`持续集成`、`持续交付`的全生命周期管理。 + + - 支持动态加权的 `round-robin` 负载均衡。 + + - 支持动态一致性 `hash` 负载均衡。 + + - 支持动态节点配置,动态 `Host` 配置。 + + - 支持上游服务 `连接`、`发送`、`读取` 超时设置。 + + - 支持插件热插拔,项目插件可被项目下所有路由继承。 + + - 支持自动生成项目文档。 + + - 支持项目成员管理。 + +- **路由** + + - 支持前后端请求路由映射。 + + - 支持前后端请求方式映射。 + + - 支持前后端请求参数交叉映射。 + + - 支持常量参数定义。 + + - 支持自定义响应数据及响应数据类型。 + + - 支持插件热插拔。 + + - 支持Mock请求,用户加速前后端分离开发过程。 + + - 支持自动生成路由(APIs)文档。 + + - 支持多环境路由(APIs)上下线。 + + - 支持多环境路由(APIs)一键复制。 + +- **用户** + + - 支持用户登录、注册。 + + - 支持用户创建、编辑。 + + - 支持用户全局禁用。 ## 安装 -在不同的操作系统上安装 `APIOAK` 所必需的系统依赖(`openresty`、`resty-cli`、`luarocks`等),请参见:[依赖安装文档](doc/install-dependencies.md)。 +在不同的操作系统上安装 `APIOAK` 所必需的系统依赖(`OpenResty >= 1.15.8.2`、`luarocks >= 2.3`、`MySQL >= 5.7 或 MariaDB >= 10.2`等),请参见:[依赖安装文档](doc/install-dependencies.md)。 > 通过 LuaRocks 安装 @@ -34,7 +78,7 @@ APIOAK 可以帮你隔离内外部流量,通过插件机制提供动态负载 sudo luarocks install apioak ``` -请在 [发行列表](https://github.com/apioak/apioak/releases) 中获得相应版本的 `RPM` 或 `DEB` 安装包。 +请在 [发行列表](https://gitee.com/apioak/apioak/releases) 中获得相应版本的 `RPM` 或 `DEB` 安装包。 > 通过 PRM 安装 (CentOS 7) @@ -51,16 +95,24 @@ sudo dpkg -i apioak-{VERSION}-1_amd64.deb ## 快速开始 +> 配置 APIOAK + + - 导入数据库配置文件到 `MySQL` 或 `MariaDB` 中,配置文件路径 `/path/conf/apioak.sql`。 + + - 编辑 `APIOAK` 配置文件中 `database` 项的数据库连接信息,配置文件路径 `/path/conf/apioak.yaml`。 + + > 启动 APIOAK ```bash sudo apioak start ``` +> 访问 APIOAK -## 如何贡献 +- 浏览器输入 `http://127.0.0.1:10080/apioak/dashboard` 即可访问控制台管理面板。 -请参阅 [贡献](CONTRIBUTING_CN.md) 文档。 +至此,`APIOAK` 已全部安装并配置完毕,请尽情享受。 ## 致谢 diff --git a/doc/images/APIOAK-process.jpeg b/doc/images/APIOAK-process.jpeg deleted file mode 100644 index 47e99a87695498a4b60473515b3ead148d7af7dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 312169 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�OwSqdcF{{L$n9nRA1s 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 E3&v_|C;$Ke diff --git a/doc/images/APIOAK-process.png b/doc/images/APIOAK-process.png new file mode 100644 index 0000000000000000000000000000000000000000..0bfee43d8caf5deab4fc45c0a178982bfa1869c7 GIT binary patch literal 76689 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
xmTgpEllDGSQ)}iwaCb>rBtdBBvQPCe z1YiE%Li{WGs@x%Gwx#2K@`#MdssSh9Wa$!mvt{BxXz4aQM zmY_gNgkZGPt|t!;1P}P0KZ8y4k{W8KRQWcanp`fAgY-EpyG$tc)|vN7GEUj>j2%xU z#d&&<@oKXYI+?t>`zlzeg=@mc^*hdB{#5Q~dCUzLCYlcJGougA0d9@t9<7OfiBjkU zrF6$*vCaIPoAAr~vKRg<6K+tX-A|c6gS5EHT1GJby>#g-|K;r16LEwh_T7xvx7}E- z71{_ax552hy2hQBi_?D!t2x|Hu>hHniG-)XtW2keo3CDURXA_Ka{~MuK>}Ow`!Qyq zg@;Q1f+IM?x&d?WSytI%{ZH*S?QiEX48Pnh%|7(T9R`jvRoPCK>tr7q9;$GkV~BsP z0~dN2+Epsb=G96rve3lUBlcXtuR}JM4;Ir2WC|*w9|s^n=nAG((ajond*~_9z}_os zU1~UH=8S3f=Y|B<+<1Wxztr#a5GIrO=J(Z_c+y^>P>NeJaVEnyKai&ib!j;6PWkmI zcrzdGuMSNrA3X+MHV@hs8y)B zXW2a(9fgtb9)>OXx%B+F1=)c_w@?szCG?Vq($&WNw4IM)6>| zsyhOD=QicA13Us>BJvIkUQ7t$F{Tmm`@=9R;}r1q;@@~?PU>1F zmE_^>S7|}?{TY4}&fB17egw5>z_jv$ghs+E$@!J^ozC?4>xS4~$g$$sRmGn=Pj8J8#X+>gyxm=vn z(SO2fi&?XgII!c;OKbzaChVDVu%>bYRDiFgIeJAro@T zwF&0|m^SGwTn7{b*4tFu4_gw|trQ_KSJO4`Ob-hB_{I3e^+!>%M$7?>EOxrWQg(3l zYrhGZ-)+6Gd$)Wke&|%xEqb-7D)tt@ zICLDUk79)-GMOAOrr&6}^v(w7FZ@UC z!~Sh{*;y$&s)Y`m;-{SG9eo@NAeSe9JC9muq!&}K zSWqxKpZ@Om5v0E}?=k(xA>!n_pB4bZdk564*u?d-p8=WDnhYS^4Wi2Ka7>6G16`h& z(+Cg}uZ=acV$%^j<#Lrx9aK@&0$$5*toymZE9ijcwjS>YwBx`V0mGYds@tQxDnJHg zwH!5c5XcmAQeAe*&KiPeYH8fQ_1R9Yn#vq~ z3u|1Ta63Eu*#}f8=h0O?xL@3eT_g#1QDhCPxiorzfA?y@WIm>uU-yyg8AO%ch3f?p zdif8I%pBv%gOHeXljqCLbw(&wj58Sb+V6+`QvD{{045`BTW-rhAH*Q`i9K!ukmf6R z>~q5r65PUSHn+GJ&WrK3(}J`PRibmi{$&V(U&4krgcFnRc_>C3uWNh zpEo#ch)Y>|aKFKRKrm)ee3@@ToYkjk%EjkchQ)q^XO8s{ukCt0GAct3xw4juDTNkN zmQLXY4n)4lEA-=l{por7(l5R67I> zz?F;nSx*(unqORl`~{s`-gviy7-~muPq(neFr_LH?x_dz*L%BgjRJ_0Kt1JIJ{wAu zfWq2`%t(`Xv4@Y?mOX^7H?yAIMPu4H5az7=Cc4PSNn->cSG{A*2<-`KeK9iXn+F2i zCU1US7??C70MTM)=6mn%4%UTyT?H${d_$&cpp4WZ7R;qVEPp-c*7o@`gC_RuBw+~0 zd)-E_hY53Ab%r7+gn$;oVT{0Zw{BK##ftljV{A)joIv#1x9y0}RAas>sxZSso~2Yi zX~g|TTm~Szppk5=@|O#6dL)9gkMd_Yu-3z{=37LB|Mw@l^OP=_)=!GHHTV3Cf{dzk Jg`{cd{{SFw(9-|_ -- Gitee From 3de8220aeb5c78da8e9760e606a5a14348039978 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 6 Apr 2020 22:16:55 +0800 Subject: [PATCH 165/165] change: update version 0.4.0 changelog. --- CHANGELOG.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ CHANGELOG_CN.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd6362f..cce51d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,52 @@ +## 0.4.0 +> Released on 2020.04.06 + +#### Feature + +- Refactored gateway core modules. +- Refactored dashboard management panel (this version is powerful and easy to use, highly recommended). +- Refactored `Project`,` Routing` management `APIs`. +- Added `Account`,` User`, `Public Service` Management` APIs`. +- Configuration center was changed from `ETCD` to` MariaDB` database. +- New Added `Project` management. + - Support project prefix for multi-tenant isolation. + - Support multi-environment configuration, `Production Environment`,` Pre-launch Environment`, `Test Environment` completely isolated to meet the full life cycle management of `CI` and `CD`. + - Support dynamic weighted `Round-Robin` load balancing. + - Support dynamic consistency `Hash` load balancing. + - Support dynamic node configuration, dynamic `Host` configuration. + - Support upstream service `Connection`,` Send`, `Read` timeout setting. + - Support plug-in hot plug, project plug-in can be inherited by all routes(APIs) under the project. + - Support automatic generation of project documents. + - Support project member management. +- New Added `Route` management. + - Support front-end and back-end request routing mapping. + - Support front-end and back-end request method mapping. + - Support cross mapping of front and back request parameters. + - Support request constant parameter definition. + - Support custom response data and response data type. + - Support plug-in hot swap. + - Support `Mock` request, accelerate the development process of front and back end separation. + - Supports automatic generation of routing (APIs) documents. + - Support multi-environment routing (APIs) online and offline. + - Support multi-environment routing (APIs) one-click replication. +- New Added `User` management. + - Support users login and registration. + - Support users to create, edit and delete. + - Support users to disable globally. + + +#### Change + +- Removed dependent library `lua-resty-template`. +- Remove dependent library `lua-resty-etcd`. +- Remove dependent library `lua-resty-ngxvar`. +- Remove dependent library `lua-resty-jit-uuid`. +- Removed `Service` module and related management APIs and documents in` 0.3.0` version. +- Removed `Plugin` module and related management APIs and documents in` 0.3.0` version. +- Removed the `Router` module and related management APIs and documentation in` 0.3.0` version. + + + ## 0.3.0 > Released on 2020.01.29 diff --git a/CHANGELOG_CN.md b/CHANGELOG_CN.md index f771f64..66dd183 100644 --- a/CHANGELOG_CN.md +++ b/CHANGELOG_CN.md @@ -1,3 +1,51 @@ +## 0.4.0 +> 发布于 2020.04.06 + +#### 功能 + +- 重构网关内核模块。 +- 重构控制台管理面板(此版本强大易用,强烈推荐)。 +- 重构 `项目`、`路由` 管理后台 `APIs`。 +- 新增 `账号`、`用户`、`公共服务` 管理后台 `APIs`。 +- 配置中心由 `ETCD` 更换为 `MariaDB` 数据库。 +- 新增 `项目` 管理。 + - 支持项目前缀,用于多租户隔离。 + - 支持多环境环境配置,`生产环境`、`预发环境`、`测试环境` 不同环境完全隔离,满足`持续集成`、`持续交付`的全生命周期管理。 + - 支持动态加权的 `round-robin` 负载均衡。 + - 支持动态一致性 `hash` 负载均衡。 + - 支持动态节点配置,动态 `Host` 配置。 + - 支持上游服务 `连接`、`发送`、`读取` 超时设置。 + - 支持插件热插拔,项目插件可被项目下所有路由继承。 + - 支持自动生成项目文档。 + - 支持项目成员管理。 +- 新增 `路由` 管理。 + - 支持前后端请求路由映射。 + - 支持前后端请求方式映射。 + - 支持前后端请求参数交叉映射。 + - 支持常量参数定义。 + - 支持自定义响应数据及响应数据类型。 + - 支持插件热插拔。 + - 支持 `Mock` 请求,加速前后端分离开发过程。 + - 支持自动生成路由(APIs)文档。 + - 支持多环境路由(APIs)上下线。 + - 支持多环境路由(APIs)一键复制。 +- 新增 `用户` 管理。 + - 支持用户登录、注册。 + - 支持用户创建、编辑、删除。 + - 支持用户全局禁用。 + + +#### 变更 + +- 移除依赖库 `lua-resty-template`。 +- 移除依赖库 `lua-resty-etcd`。 +- 移除依赖库 `lua-resty-ngxvar`。 +- 移除依赖库 `lua-resty-jit-uuid`。 +- 移除 `0.3.0` 版本中 `Service` 模块和相关管理API、文档。 +- 移除 `0.3.0` 版本中 `Plugin` 模块和相关管理API、文档。 +- 移除 `0.3.0` 版本中 `Router` 模块和相关管理API、文档。 + + ## 0.3.0 > 发布于 2020.01.29 -- Gitee
xmTgpEllDGSQ)}iwaCb>rBtdBBvQPCe z1YiE%Li{WGs@x%Gwx#2K@`#MdssSh9Wa$!mvt{BxXz4aQM zmY_gNgkZGPt|t!;1P}P0KZ8y4k{W8KRQWcanp`fAgY-EpyG$tc)|vN7GEUj>j2%xU z#d&&<@oKXYI+?t>`zlzeg=@mc^*hdB{#5Q~dCUzLCYlcJGougA0d9@t9<7OfiBjkU zrF6$*vCaIPoAAr~vKRg<6K+tX-A|c6gS5EHT1GJby>#g-|K;r16LEwh_T7xvx7}E- z71{_ax552hy2hQBi_?D!t2x|Hu>hHniG-)XtW2keo3CDURXA_Ka{~MuK>}Ow`!Qyq zg@;Q1f+IM?x&d?WSytI%{ZH*S?QiEX48Pnh%|7(T9R`jvRoPCK>tr7q9;$GkV~BsP z0~dN2+Epsb=G96rve3lUBlcXtuR}JM4;Ir2WC|*w9|s^n=nAG((ajond*~_9z}_os zU1~UH=8S3f=Y|B<+<1Wxztr#a5GIrO=J(Z_c+y^>P>NeJaVEnyKai&ib!j;6PWkmI zcrzdGuMSNrA3X+MHV@hs8y)B zXW2a(9fgtb9)>OXx%B+F1=)c_w@?szCG?Vq($&WNw4IM)6>| zsyhOD=QicA13Us>BJvIkUQ7t$F{Tmm`@=9R;}r1q;@@~?PU>1F zmE_^>S7|}?{TY4}&fB17egw5>z_jv$ghs+E$@!J^ozC?4>xS4~$g$$sRmGn=Pj8J8#X+>gyxm=vn z(SO2fi&?XgII!c;OKbzaChVDVu%>bYRDiFgIeJAro@T zwF&0|m^SGwTn7{b*4tFu4_gw|trQ_KSJO4`Ob-hB_{I3e^+!>%M$7?>EOxrWQg(3l zYrhGZ-)+6Gd$)Wke&|%xEqb-7D)tt@ zICLDUk79)-GMOAOrr&6}^v(w7FZ@UC z!~Sh{*;y$&s)Y`m;-{SG9eo@NAeSe9JC9muq!&}K zSWqxKpZ@Om5v0E}?=k(xA>!n_pB4bZdk564*u?d-p8=WDnhYS^4Wi2Ka7>6G16`h& z(+Cg}uZ=acV$%^j<#Lrx9aK@&0$$5*toymZE9ijcwjS>YwBx`V0mGYds@tQxDnJHg zwH!5c5XcmAQeAe*&KiPeYH8fQ_1R9Yn#vq~ z3u|1Ta63Eu*#}f8=h0O?xL@3eT_g#1QDhCPxiorzfA?y@WIm>uU-yyg8AO%ch3f?p zdif8I%pBv%gOHeXljqCLbw(&wj58Sb+V6+`QvD{{045`BTW-rhAH*Q`i9K!ukmf6R z>~q5r65PUSHn+GJ&WrK3(}J`PRibmi{$&V(U&4krgcFnRc_>C3uWNh zpEo#ch)Y>|aKFKRKrm)ee3@@ToYkjk%EjkchQ)q^XO8s{ukCt0GAct3xw4juDTNkN zmQLXY4n)4lEA-=l{por7(l5R67I> zz?F;nSx*(unqORl`~{s`-gviy7-~muPq(neFr_LH?x_dz*L%BgjRJ_0Kt1JIJ{wAu zfWq2`%t(`Xv4@Y?mOX^7H?yAIMPu4H5az7=Cc4PSNn->cSG{A*2<-`KeK9iXn+F2i zCU1US7??C70MTM)=6mn%4%UTyT?H${d_$&cpp4WZ7R;qVEPp-c*7o@`gC_RuBw+~0 zd)-E_hY53Ab%r7+gn$;oVT{0Zw{BK##ftljV{A)joIv#1x9y0}RAas>sxZSso~2Yi zX~g|TTm~Szppk5=@|O#6dL)9gkMd_Yu-3z{=37LB|Mw@l^OP=_)=!GHHTV3Cf{dzk Jg`{cd{{SFw(9-|_ literal 0 HcmV?d00001 -- Gitee From c8a9712717cdc004561a1c2e9c57bbcfb11b572e Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 5 Apr 2020 22:54:11 +0800 Subject: [PATCH 160/165] change: update version 0.4.0 Makefile. --- Makefile | 75 ++++++++++++++++--------------------- doc/install-dependencies.md | 2 +- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index befdf43..8bed2e7 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ INSTALL ?= install REMOVE ?= rm -rf COPY ?= cp -rf CHMOD ?= chmod -R +DOWNLOAD ?= wget +UNTAG ?= tar -zxvf INST_OAK_PRODIR ?= /usr/local/apioak INST_OAK_BINDIR ?= /usr/bin LUTJIT_DIR ?= $(shell ${OR_EXEC} -V 2>&1 | grep prefix | grep -Eo 'prefix=(.*?)/nginx' | grep -Eo '/.*/')luajit @@ -22,53 +24,40 @@ endif .PHONY: install install: - $(INSTALL) -d $(INST_OAK_PRODIR)/bin - $(INSTALL) -d $(INST_OAK_PRODIR)/logs - $(INSTALL) -d $(INST_OAK_PRODIR)/conf $(INSTALL) -d $(INST_OAK_PRODIR)/apioak - $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/sys - $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/pdk $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/admin + $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/db + $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/pdk $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/plugin + $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/schema + $(INSTALL) -d $(INST_OAK_PRODIR)/apioak/sys + $(INSTALL) -d $(INST_OAK_PRODIR)/conf + $(INSTALL) -d $(INST_OAK_PRODIR)/bin + $(INSTALL) -d $(INST_OAK_PRODIR)/logs + + $(INSTALL) apioak/*.lua $(INST_OAK_PRODIR)/apioak/ + $(INSTALL) apioak/admin/*.lua $(INST_OAK_PRODIR)/apioak/admin/ + $(INSTALL) apioak/db/*.lua $(INST_OAK_PRODIR)/apioak/db/ + $(INSTALL) apioak/pdk/*.lua $(INST_OAK_PRODIR)/apioak/pdk/ + $(INSTALL) apioak/plugin/*.lua $(INST_OAK_PRODIR)/apioak/plugin/ + $(INSTALL) apioak/schema/*.lua $(INST_OAK_PRODIR)/apioak/schema/ + $(INSTALL) apioak/sys/*.lua $(INST_OAK_PRODIR)/apioak/sys/ + + $(INSTALL) conf/mime.types $(INST_OAK_PRODIR)/conf/mime.types + $(INSTALL) conf/apioak.yaml $(INST_OAK_PRODIR)/conf/apioak.yaml + $(INSTALL) conf/apioak.sql $(INST_OAK_PRODIR)/conf/apioak.sql + $(INSTALL) conf/nginx.conf $(INST_OAK_PRODIR)/conf/nginx.conf + + $(INSTALL) bin/apioak $(INST_OAK_PRODIR)/bin/apioak + $(INSTALL) bin/apioak $(INST_OAK_BINDIR)/apioak + + $(INSTALL) README.md $(INST_OAK_PRODIR)/README.md + $(INSTALL) README_CN.md $(INST_OAK_PRODIR)/README_CN.md + $(INSTALL) COPYRIGHT $(INST_OAK_PRODIR)/COPYRIGHT - $(INSTALL) apioak/apioak.lua $(INST_OAK_PRODIR)/apioak/apioak.lua - $(INSTALL) apioak/admin.lua $(INST_OAK_PRODIR)/apioak/admin.lua - $(INSTALL) apioak/pdk.lua $(INST_OAK_PRODIR)/apioak/pdk.lua - $(INSTALL) apioak/sys.lua $(INST_OAK_PRODIR)/apioak/sys.lua - $(INSTALL) apioak/sys/admin.lua $(INST_OAK_PRODIR)/apioak/sys/admin.lua - $(INSTALL) apioak/sys/balancer.lua $(INST_OAK_PRODIR)/apioak/sys/balancer.lua - $(INSTALL) apioak/sys/meta.lua $(INST_OAK_PRODIR)/apioak/sys/meta.lua - $(INSTALL) apioak/sys/plugin.lua $(INST_OAK_PRODIR)/apioak/sys/plugin.lua - $(INSTALL) apioak/sys/router.lua $(INST_OAK_PRODIR)/apioak/sys/router.lua - $(INSTALL) apioak/pdk/admin.lua $(INST_OAK_PRODIR)/apioak/pdk/admin.lua - $(INSTALL) apioak/pdk/config.lua $(INST_OAK_PRODIR)/apioak/pdk/config.lua - $(INSTALL) apioak/pdk/const.lua $(INST_OAK_PRODIR)/apioak/pdk/const.lua - $(INSTALL) apioak/pdk/ctx.lua $(INST_OAK_PRODIR)/apioak/pdk/ctx.lua - $(INSTALL) apioak/pdk/etcd.lua $(INST_OAK_PRODIR)/apioak/pdk/etcd.lua - $(INSTALL) apioak/pdk/json.lua $(INST_OAK_PRODIR)/apioak/pdk/json.lua - $(INSTALL) apioak/pdk/log.lua $(INST_OAK_PRODIR)/apioak/pdk/log.lua - $(INSTALL) apioak/pdk/plugin.lua $(INST_OAK_PRODIR)/apioak/pdk/plugin.lua - $(INSTALL) apioak/pdk/request.lua $(INST_OAK_PRODIR)/apioak/pdk/request.lua - $(INSTALL) apioak/pdk/response.lua $(INST_OAK_PRODIR)/apioak/pdk/response.lua - $(INSTALL) apioak/pdk/schema.lua $(INST_OAK_PRODIR)/apioak/pdk/schema.lua - $(INSTALL) apioak/pdk/shared.lua $(INST_OAK_PRODIR)/apioak/pdk/shared.lua - $(INSTALL) apioak/pdk/string.lua $(INST_OAK_PRODIR)/apioak/pdk/string.lua - $(INSTALL) apioak/pdk/table.lua $(INST_OAK_PRODIR)/apioak/pdk/table.lua - $(INSTALL) apioak/pdk/tablepool.lua $(INST_OAK_PRODIR)/apioak/pdk/tablepool.lua - $(INSTALL) apioak/admin/plugin.lua $(INST_OAK_PRODIR)/apioak/admin/plugin.lua - $(INSTALL) apioak/admin/router.lua $(INST_OAK_PRODIR)/apioak/admin/router.lua - $(INSTALL) apioak/admin/service.lua $(INST_OAK_PRODIR)/apioak/admin/service.lua - $(INSTALL) apioak/plugin/limit-conn.lua $(INST_OAK_PRODIR)/apioak/plugin/limit-conn.lua - $(INSTALL) apioak/plugin/limit-count.lua $(INST_OAK_PRODIR)/apioak/plugin/limit-count.lua - $(INSTALL) apioak/plugin/limit-req.lua $(INST_OAK_PRODIR)/apioak/plugin/limit-req.lua - $(INSTALL) apioak/plugin/key-auth.lua $(INST_OAK_PRODIR)/apioak/plugin/key-auth.lua - $(INSTALL) conf/mime.types $(INST_OAK_PRODIR)/conf/mime.types - $(INSTALL) conf/apioak.yaml $(INST_OAK_PRODIR)/conf/apioak.yaml - $(INSTALL) conf/nginx.conf $(INST_OAK_PRODIR)/conf/nginx.conf - $(INSTALL) bin/apioak $(INST_OAK_PRODIR)/bin/apioak - $(INSTALL) bin/apioak $(INST_OAK_BINDIR)/apioak - $(INSTALL) README.md $(INST_OAK_PRODIR)/README.md - $(INSTALL) COPYRIGHT $(INST_OAK_PRODIR)/COPYRIGHT + $(DOWNLOAD) https://github.com/apioak/dashboard/releases/download/v0.4.0/dashboard-0.4.0.tar.gz + $(UNTAG) dashboard-0.4.0.tar.gz -C $(INST_OAK_PRODIR) + $(REMOVE) dashboard-0.4.0.tar.gz .PHONY: uninstall diff --git a/doc/install-dependencies.md b/doc/install-dependencies.md index e456092..cdacd27 100644 --- a/doc/install-dependencies.md +++ b/doc/install-dependencies.md @@ -110,7 +110,7 @@ sudo openresty -s stop > Install MariaDB ```shell -# key is imported and the repository added. +# Key is imported and the repository added. sudo apt-get -y install software-properties-common sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc' -- Gitee From a2d5fda316e7c85c5f8fc45fb8e3123dc2d8b801 Mon Sep 17 00:00:00 2001 From: Janko Date: Sun, 5 Apr 2020 23:05:22 +0800 Subject: [PATCH 161/165] change: update version 0.4.0 COPYRIGHT. --- COPYRIGHT | 151 +++++++++++++++--------------------------------------- 1 file changed, 42 insertions(+), 109 deletions(-) diff --git a/COPYRIGHT b/COPYRIGHT index 08bc38a..700bcb7 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,62 +1,3 @@ -lua-resty-template - Templating Engine (HTML) for Lua and OpenResty. - -https://github.com/bungle/lua-resty-template/blob/master/LICENSE - -The BSD License (BSD 3-Clause License) https://opensource.org/licenses/BSD-3-Clause - -Copyright (c) 2014 - 2017 Aapo Talvensaari - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - -* Neither the name of the {organization} nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ------------------------------------------------------------------------------ - -lua-resty-etcd - Nonblocking Lua etcd driver library for OpenResty. - -https://github.com/iresty/lua-resty-etcd/blob/master/LICENSE - -The Apache License (Apache License 2.0) https://opensource.org/licenses/Apache-2.0 - -Copyright (c) 2019 Yuansheng Wang membphis@gmail.com - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - ------------------------------------------------------------------------------ - lua-resty-balancer - A generic consistent hash implementation for OpenResty/Lua. https://github.com/openresty/lua-resty-balancer#copyright-and-license @@ -77,56 +18,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ----------------------------------------------------------------------------- -lua-resty-ngxvar - Fetch nginx variable by FFI way for OpenResty which is faster. - -https://github.com/iresty/lua-var-nginx-module/blob/master/LICENSE - -The Apache License (Apache License 2.0) https://opensource.org/licenses/Apache-2.0 - -Copyright (c) 2019 Yuansheng Wang membphis@gmail.com - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - ------------------------------------------------------------------------------ - -lua-resty-jit-uuid - Fast and dependency-free UUID library for LuaJIT/ngx_lua. - -https://github.com/thibaultcha/lua-resty-jit-uuid/blob/master/LICENSE - -The MIT License (MIT License) http://opensource.org/licenses/MIT - -Copyright (c) 2016-2019 Thibault Charbonnier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ------------------------------------------------------------------------------ - lua-resty-jwt - JWT For The Great Openresty. https://github.com/cdbattags/lua-resty-jwt/blob/master/LICENSE @@ -363,3 +254,45 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +----------------------------------------------------------------------------- + +lua-resty-mysql - This module is licensed under the BSD license. + +https://github.com/openresty/lua-resty-mysql#copyright-and-license + +The BSD License (BSD 2-Clause License) https://opensource.org/licenses/BSD-2-Clause + +Copyright (C) 2012-2018, by Yichun "agentzh" Zhang (章亦春) agentzh@gmail.com, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- + +lua-resty-lrucache - This module is licensed under the BSD license. + +https://github.com/openresty/lua-resty-lrucache#copyright-and-license + +The BSD License (BSD 2-Clause License) https://opensource.org/licenses/BSD-2-Clause + +Copyright (C) 2014-2019, by Yichun "agentzh" Zhang, OpenResty Inc. + +Copyright (C) 2014-2017, by Shuxin Yang. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- Gitee From 643b48831dd9a33d91962e7356a037f9ec3b0ef8 Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 6 Apr 2020 19:13:30 +0800 Subject: [PATCH 162/165] change: README add BenchmarkF lameGraph and Update Thanks info. --- README.md | 41 +- README_CN.md | 41 +- doc/images/APIOAK-flamegraph.svg | 1072 ++++++++++++++++++++++++++++++ doc/images/APIOAK-thanks.jpg | Bin 0 -> 136220 bytes doc/images/APISIX-logo.jpg | Bin 17438 -> 0 bytes doc/images/KONG-logo.jpg | Bin 17286 -> 0 bytes doc/images/ORANGE-logo.jpg | Bin 19107 -> 0 bytes 7 files changed, 1146 insertions(+), 8 deletions(-) create mode 100644 doc/images/APIOAK-flamegraph.svg create mode 100644 doc/images/APIOAK-thanks.jpg delete mode 100644 doc/images/APISIX-logo.jpg delete mode 100644 doc/images/KONG-logo.jpg delete mode 100644 doc/images/ORANGE-logo.jpg diff --git a/README.md b/README.md index d426260..925ada2 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ APIOAK performance is almost comparable to native `Nginx`, and provides dynamic ## Installation -System dependencies (`OpenResty >= 1.15.8.2`、`luarocks >= 2.3`、`MySQL >= 5.7 or MariaDB >= 10.2`, etc.) necessary to install `APIOAK` on different operating systems, See: [Install Dependencies](doc/install-dependencies.md) Document. +System dependencies (`OpenResty >= 1.15.8.2`、`luarocks >= 2.3`、`MySQL >= 5.7 or MariaDB >= 10.2`, etc.) necessary to install `APIOAK` on different operating systems, See: [Install Dependencies](doc/en_US/install-dependencies.md) Document. > Installation via LuaRocks @@ -113,7 +113,40 @@ sudo apioak start At this point, `APIOAK` has all been installed and configured, please enjoy it. +## Benchmark + +> Test environment & parameters + + - Use Google Cloud N1 series basic version (1 vCPU + 3.75 GB RAM) server for testing. + + - Runs benchmark for 20 seconds, using 2 threads, keeping 200 HTTP connections open. + +> RTT & QPS + +```bash +Thread Stats Avg Stdev Max +/- Stdev +Latency 2.65s 584.41ms 3.66s 57.25% +Requests/sec: 24012.38 +``` + +> Latency Distribution + +```bash + 50.000% 2.63s + 75.000% 3.18s + 90.000% 3.44s + 99.000% 3.60s + 99.900% 3.64s + 99.990% 3.65s + 99.999% 3.66s +100.000% 3.66s +``` + +## FlameGraph + +![FlameGraph](doc/images/APIOAK-flamegraph.svg) + + ## Thanks -![Kong](doc/images/KONG-logo.jpg) -![APISIX](doc/images/APISIX-logo.jpg) -![Orange](doc/images/ORANGE-logo.jpg) + +![Thanks](doc/images/APIOAK-thanks.jpg) diff --git a/README_CN.md b/README_CN.md index f46c91c..c52a01e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -70,7 +70,7 @@ APIOAK 提供了几乎可以媲美原生 `Nginx` 的强劲性能,通过插件 ## 安装 -在不同的操作系统上安装 `APIOAK` 所必需的系统依赖(`OpenResty >= 1.15.8.2`、`luarocks >= 2.3`、`MySQL >= 5.7 或 MariaDB >= 10.2`等),请参见:[依赖安装文档](doc/install-dependencies.md)。 +在不同的操作系统上安装 `APIOAK` 所必需的系统依赖(`OpenResty >= 1.15.8.2`、`luarocks >= 2.3`、`MySQL >= 5.7 或 MariaDB >= 10.2`等),请参见:[依赖安装文档](doc/zh_CN/install-dependencies.md)。 > 通过 LuaRocks 安装 @@ -115,7 +115,40 @@ sudo apioak start 至此,`APIOAK` 已全部安装并配置完毕,请尽情享受。 +## 性能 + +> 测试环境和参数 + +- 使用Google Cloud N1系列基本版本(1 vCPU + 3.75 GB RAM)服务器进行测试。 + +- 使用2个线程运行基准测试20秒,保持200个HTTP连接打开。 + +> 平均响应时间(RTT)和每秒响应次数(QPS) + +```bash +Thread Stats Avg Stdev Max +/- Stdev +Latency 2.65s 584.41ms 3.66s 57.25% +Requests/sec: 24012.38 +``` + +> 请求响应时间分布 + +```bash + 50.000% 2.63s + 75.000% 3.18s + 90.000% 3.44s + 99.000% 3.60s + 99.900% 3.64s + 99.990% 3.65s + 99.999% 3.66s +100.000% 3.66s +``` + +## 火焰图 + +![FlameGraph](doc/images/APIOAK-flamegraph.svg) + + ## 致谢 -![Kong](doc/images/KONG-logo.jpg) -![APISIX](doc/images/APISIX-logo.jpg) -![Orange](doc/images/ORANGE-logo.jpg) + +![Thanks](doc/images/APIOAK-thanks.jpg) diff --git a/doc/images/APIOAK-flamegraph.svg b/doc/images/APIOAK-flamegraph.svg new file mode 100644 index 0000000..2198613 --- /dev/null +++ b/doc/images/APIOAK-flamegraph.svg @@ -0,0 +1,1072 @@ + + + + + + + + + + + + + + +Lua-land on-CPU flamegraph + +Reset Zoom +Search +ic + + + +_int_malloc (11 samples, 1.58%) + + + +lj_str_new (10 samples, 1.43%) + + + +gc_sweep (15 samples, 2.15%) +g.. + + +lj_lib_checkint (3 samples, 0.43%) + + + +0x7f452cf23baf (3 samples, 0.43%) + + + +@/usr/local/apioak/apioak/apioak.lua:6 (71 samples, 10.17%) +@/usr/local/ap.. + + +ngx_pnalloc (3 samples, 0.43%) + + + +lj_BC_CAT (5 samples, 0.72%) + + + +r3_vector__expand (5 samples, 0.72%) + + + +ngx_http_set_header_helper (9 samples, 1.29%) + + + +ngx_http_lua_set_output_header (4 samples, 0.57%) + + + +@/usr/local/apioak/apioak/apioak.lua:131 (71 samples, 10.17%) +@/usr/local/a.. + + +rehashtab (3 samples, 0.43%) + + + +lj_BC_TGETS (3 samples, 0.43%) + + + +C:ngx_http_lua_ngx_say (48 samples, 6.88%) +C:ngx_htt.. + + +@/usr/local/apioak/apioak/sys/router.lua:155 (113 samples, 16.19%) +@/usr/local/apioak/apioak/.. + + +T:@/usr/local/apioak/deps/share/lua/5.1/resty/chash.lua:260 (28 samples, 4.01%) +T:@/.. + + +@/usr/local/apioak/apioak/pdk/plugin.lua:8 (71 samples, 10.17%) +@/usr/local/ap.. + + +__GI_memset (4 samples, 0.57%) + + + +rehashtab (9 samples, 1.29%) + + + +lj_BC_CALL (4 samples, 0.57%) + + + +T:@/usr/local/apioak/apioak/sys/router.lua:140 (37 samples, 5.30%) +T:@/ho.. + + +ngx_http_lua_set_input_header (5 samples, 0.72%) + + + +lj_alloc_malloc (3 samples, 0.43%) + + + +@/usr/local/openresty/lualib/resty/core/var.lua:78 (24 samples, 3.44%) +@/h.. + + +lj_BC_JFUNCF (3 samples, 0.43%) + + + +@/usr/local/apioak/apioak/apioak.lua:6 (29 samples, 4.15%) +@/ho.. + + +ngx_strlow (7 samples, 1.00%) + + + +C:ngx_http_lua_ngx_crc32_short (7 samples, 1.00%) + + + +lj_tab_set (3 samples, 0.43%) + + + +rehashtab (3 samples, 0.43%) + + + +lj_tab_new (4 samples, 0.57%) + + + +lj_tab_newkey (7 samples, 1.00%) + + + +ngx_http_header_filter (12 samples, 1.72%) + + + +propagatemark (3 samples, 0.43%) + + + +lj_BC_TGETS (11 samples, 1.58%) + + + +@/usr/local/apioak/apioak/pdk/plugin.lua:8 (16 samples, 2.29%) +@.. + + +__memcpy_ssse3_back (4 samples, 0.57%) + + + +@/usr/local/apioak/apioak/sys/router.lua:169 (13 samples, 1.86%) +@.. + + +lj_BC_TGETS (5 samples, 0.72%) + + + +propagatemark (10 samples, 1.43%) + + + +@/usr/local/apioak/apioak/apioak.lua:6 (41 samples, 5.87%) +@/usr/l.. + + +T:@/usr/local/apioak/apioak/pdk/plugin.lua:8 (16 samples, 2.29%) +T.. + + +_IO_str_init_static_internal (3 samples, 0.43%) + + + +=log_by_lua(nginx.conf:131):0 (16 samples, 2.29%) +=.. + + +gc_sweep (7 samples, 1.00%) + + + +lj_tab_len (4 samples, 0.57%) + + + +@/usr/local/openresty/lualib/resty/core/misc.lua:56 (15 samples, 2.15%) +@.. + + +=header_filter_by_lua:0 (110 samples, 15.76%) +=header_filter_by_lua:0 + + +@/usr/local/openresty/lualib/resty/core/misc.lua:66 (3 samples, 0.43%) + + + +lj_str_new (7 samples, 1.00%) + + + +builtin#5 (3 samples, 0.43%) + + + +lj_alloc_free (3 samples, 0.43%) + + + +=body_filter_by_lua:0 (71 samples, 10.17%) +=body_filter_b.. + + +propagatemark (5 samples, 0.72%) + + + +lj_str_new (9 samples, 1.29%) + + + +lj_BC_TGETS (9 samples, 1.29%) + + + +lj_str_new (18 samples, 2.58%) +lj.. + + +ngx_hash_key_lc (11 samples, 1.58%) + + + +ngx_http_set_header_helper (4 samples, 0.57%) + + + +rehashtab (5 samples, 0.72%) + + + +lj_alloc_free (4 samples, 0.57%) + + + +@/usr/local/apioak/deps/share/lua/5.1/resty/chash.lua:292 (7 samples, 1.00%) + + + +@/usr/local/apioak/apioak/apioak.lua:137 (16 samples, 2.29%) +@.. + + +lj_cf_table_insert (3 samples, 0.43%) + + + +mmcall (4 samples, 0.57%) + + + +ngx_http_lua_ffi_set_resp_header (6 samples, 0.86%) + + + +T:@/usr/local/openresty/lualib/resty/core/misc.lua:66 (3 samples, 0.43%) + + + +=content_by_lua(nginx.conf:92):0 (66 samples, 9.46%) +=content_by_l.. + + +r3_tree_matchl_base (12 samples, 1.72%) + + + +ngx_palloc (6 samples, 0.86%) + + + +lj_alloc_free (5 samples, 0.72%) + + + +ngx_strncasecmp (3 samples, 0.43%) + + + +lj_alloc_malloc (3 samples, 0.43%) + + + +T:@/usr/local/apioak/apioak/pdk/plugin.lua:8 (67 samples, 9.60%) +T:@/usr/local.. + + +lj_BC_RET (7 samples, 1.00%) + + + +_IO_vfprintf (11 samples, 1.58%) + + + +lj_alloc_malloc (3 samples, 0.43%) + + + +lj_tab_free (3 samples, 0.43%) + + + +ngx_output_chain (3 samples, 0.43%) + + + +gc_sweep (4 samples, 0.57%) + + + +@/usr/local/apioak/apioak/sys/balancer.lua:92 (35 samples, 5.01%) +@/usr/.. + + +propagatemark (4 samples, 0.57%) + + + +ngx_http_lua_ngx_crc32_short (7 samples, 1.00%) + + + +T:@/usr/local/apioak/apioak/pdk/plugin.lua:8 (31 samples, 4.44%) +T:@/h.. + + +@/usr/local/apioak/apioak/apioak.lua:113 (51 samples, 7.31%) +@/usr/loca.. + + +T:@/usr/local/openresty/lualib/resty/core/request.lua:299 (14 samples, 2.01%) +T.. + + +lj_buf_putstr (3 samples, 0.43%) + + + +lua_tolstring (3 samples, 0.43%) + + + +0x7f452cf25216 (3 samples, 0.43%) + + + +ngx_http_lua_set_output_header (12 samples, 1.72%) + + + +rehashtab (3 samples, 0.43%) + + + +lj_meta_tset (3 samples, 0.43%) + + + +T:@/usr/local/openresty/lualib/resty/core/ctx.lua:33 (3 samples, 0.43%) + + + +T:@/usr/local/openresty/lualib/resty/core/response.lua:48 (7 samples, 1.00%) + + + +lj_tab_newkey (3 samples, 0.43%) + + + +lj_str_new (16 samples, 2.29%) +l.. + + +T:@/usr/local/apioak/apioak/apioak.lua:6 (3 samples, 0.43%) + + + +@/usr/local/apioak/apioak/apioak.lua:62 (381 samples, 54.58%) +@/usr/local/apioak/apioak/apioak.lua:62 + + +@/usr/local/openresty/lualib/resty/core/response.lua:48 (10 samples, 1.43%) + + + +T:@/usr/local/apioak/apioak/sys/router.lua:169 (13 samples, 1.86%) +T.. + + +@/usr/local/apioak/apioak/apioak.lua:6 (16 samples, 2.29%) +@.. + + +lj_tab_new (11 samples, 1.58%) + + + +ngx_vslprintf (3 samples, 0.43%) + + + +@/usr/local/apioak/apioak/pdk/plugin.lua:8 (29 samples, 4.15%) +@/ho.. + + +gc_sweep (3 samples, 0.43%) + + + +lj_mem_realloc (3 samples, 0.43%) + + + +ngx_http_set_host_header (5 samples, 0.72%) + + + +lj_strscan_scan (5 samples, 0.72%) + + + +lj_alloc_malloc (7 samples, 1.00%) + + + +lj_alloc_free (4 samples, 0.57%) + + + +__memcpy_ssse3_back (8 samples, 1.15%) + + + +_IO_default_xsputn (4 samples, 0.57%) + + + +propagatemark (13 samples, 1.86%) +p.. + + +__memcpy_ssse3_back (9 samples, 1.29%) + + + +strchrnul (3 samples, 0.43%) + + + +0x7f452cf23bde (3 samples, 0.43%) + + + +lj_str_new (3 samples, 0.43%) + + + +T:@/usr/local/openresty/lualib/resty/core/var.lua:78 (3 samples, 0.43%) + + + +lj_alloc_free (11 samples, 1.58%) + + + +lj_str_new (4 samples, 0.57%) + + + +lj_alloc_malloc (4 samples, 0.57%) + + + +lj_str_new (27 samples, 3.87%) +lj_s.. + + +all (698 samples, 100%) + + + +ngx_http_chunked_header_filter (3 samples, 0.43%) + + + +lj_tab_resize (4 samples, 0.57%) + + + +builtin#101 (38 samples, 5.44%) +builtin.. + + +=balancer_by_lua:0 (51 samples, 7.31%) +=balancer_.. + + +gc_sweep (6 samples, 0.86%) + + + +lj_ffh_pairs (3 samples, 0.43%) + + + +lj_alloc_free (5 samples, 0.72%) + + + +lj_BC_CALLT (3 samples, 0.43%) + + + +lj_buf_putstr (4 samples, 0.57%) + + + +ngx_http_lua_ffi_req_get_headers (3 samples, 0.43%) + + + +0x7f452cf23b92 (6 samples, 0.86%) + + + +lj_BC_UGET (3 samples, 0.43%) + + + +lj_buf_putstr (4 samples, 0.57%) + + + +rehashtab (9 samples, 1.29%) + + + +ngx_http_lua_ngx_echo (3 samples, 0.43%) + + + +__memcpy_ssse3_back (3 samples, 0.43%) + + + +lj_meta_cat (3 samples, 0.43%) + + + +ngx_http_write_filter (6 samples, 0.86%) + + + +lj_buf_putstr (4 samples, 0.57%) + + + +T:@/usr/local/openresty/lualib/resty/core/ctx.lua:33 (69 samples, 9.89%) +T:@/usr/local/.. + + +T:@/usr/local/apioak/apioak/pdk/plugin.lua:8 (29 samples, 4.15%) +T:@/.. + + +rehashtab (3 samples, 0.43%) + + + +lj_buf_putstr_lower (3 samples, 0.43%) + + + +@/usr/local/openresty/lualib/resty/core/request.lua:299 (14 samples, 2.01%) +@.. + + +builtin#93 (9 samples, 1.29%) + + + +__memcpy_ssse3_back (12 samples, 1.72%) + + + +@/usr/local/apioak/apioak/sys/router.lua:140 (75 samples, 10.74%) +@/usr/local/ap.. + + +ngx_parse_url (6 samples, 0.86%) + + + +lj_BC_TGETS (3 samples, 0.43%) + + + +@/usr/local/apioak/apioak/apioak.lua:119 (110 samples, 15.76%) +@/usr/local/apioak/apioak.. + + +__memcpy_ssse3_back (8 samples, 1.15%) + + + +ngx_http_lua_ffi_var_set (3 samples, 0.43%) + + + +lj_BC_TGETS (3 samples, 0.43%) + + + +T:@/usr/local/apioak/apioak/sys/router.lua:155 (83 samples, 11.89%) +T:@/usr/local/ap.. + + +_int_free (4 samples, 0.57%) + + + +lj_alloc_free (7 samples, 1.00%) + + + +@/usr/local/openresty/lualib/resty/core/misc.lua:108 (5 samples, 0.72%) + + + +lj_alloc_malloc (3 samples, 0.43%) + + + +lj_tab_setstr (3 samples, 0.43%) + + + +ngx_sprintf_num (3 samples, 0.43%) + + + +lj_tab_set (3 samples, 0.43%) + + + +lj_str_new (14 samples, 2.01%) +l.. + + +lj_vm_exit_interp (3 samples, 0.43%) + + + +_IO_vsprintf (4 samples, 0.57%) + + + +r3_tree_match_route (4 samples, 0.57%) + + + +=access_by_lua(nginx.conf:119):0 (384 samples, 55.01%) +=access_by_lua(nginx.conf:119):0 + + +lj_alloc_free (8 samples, 1.15%) + + + +@/usr/local/apioak/apioak/pdk/plugin.lua:8 (41 samples, 5.87%) +@/usr/l.. + + +@/usr/local/openresty/lualib/resty/core/ctx.lua:33 (19 samples, 2.72%) +@/.. + + +gc_sweep (9 samples, 1.29%) + + + + diff --git a/doc/images/APIOAK-thanks.jpg b/doc/images/APIOAK-thanks.jpg new file mode 100644 index 0000000000000000000000000000000000000000..263d4163619a6cb3fd7e9b9f8ac0eafbad78579e GIT binary patch literal 136220 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

c(shp(N88_54%k$bxzC4dq;1d(J;yWR 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 literal 0 HcmV?d00001 diff --git a/doc/images/APISIX-logo.jpg b/doc/images/APISIX-logo.jpg deleted file mode 100644 index e769e46fd4248ea3827edfaa6fe235e76a10d95b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17438 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 diff --git a/doc/images/ORANGE-logo.jpg b/doc/images/ORANGE-logo.jpg deleted file mode 100644 index 3c7c16efdd2439d8fd1e7d2192eb1f9895fd37fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19107 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 -- Gitee From 426501a2610137b6a1515d2457e17c278594bf7a Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 6 Apr 2020 19:22:09 +0800 Subject: [PATCH 163/165] change: add Chinese documents. --- doc/dev-manual-cn.md | 53 ------ doc/dev-manual.md | 0 doc/{ => en_US}/install-dependencies.md | 16 +- doc/plugin-cn.md | 57 ------- doc/plugin.md | 57 ------- doc/router-cn.md | 212 ------------------------ doc/router.md | 208 ----------------------- doc/service-cn.md | 184 -------------------- doc/service.md | 162 ------------------ doc/zh_CN/install-dependencies.md | 118 +++++++++++++ 10 files changed, 123 insertions(+), 944 deletions(-) delete mode 100644 doc/dev-manual-cn.md delete mode 100644 doc/dev-manual.md rename doc/{ => en_US}/install-dependencies.md (88%) delete mode 100644 doc/plugin-cn.md delete mode 100644 doc/plugin.md delete mode 100644 doc/router-cn.md delete mode 100644 doc/router.md delete mode 100644 doc/service-cn.md delete mode 100644 doc/service.md create mode 100644 doc/zh_CN/install-dependencies.md diff --git a/doc/dev-manual-cn.md b/doc/dev-manual-cn.md deleted file mode 100644 index 55687bf..0000000 --- a/doc/dev-manual-cn.md +++ /dev/null @@ -1,53 +0,0 @@ -### OpenResty及系统依赖 (Centos 7) -```shell -# install epel, `luarocks` need it. -wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -sudo rpm -ivh epel-release-latest-7.noarch.rpm - -# add openresty source -sudo yum install -y yum-utils -sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo - -# install openresty and some compilation tools -sudo yum install -y openresty openresty-resty curl git automake autoconf \ - gcc pcre-devel openssl-devel libtool gcc-c++ luarocks cmake3 lua-devel etcd - -sudo ln -s /usr/bin/cmake3 /usr/bin/cmake - -sodu systemctl start etcd -``` - -### 框架依赖 -```shell -make dev -``` - -### 安装ETCD -```shell -yum -y install etcd -``` - -### 启动ETCD -```shell -systemctl start etcd -``` - -### 安装项目依赖 -```shell -make dev -``` - -### 初始化项目 -```shell -./bin/apioak init -``` - -### 初始化ETCD -```shell -./bin/apioak init_etcd -``` - -### 启动项目 -```shell -./bin/apioak start -``` diff --git a/doc/dev-manual.md b/doc/dev-manual.md deleted file mode 100644 index e69de29..0000000 diff --git a/doc/install-dependencies.md b/doc/en_US/install-dependencies.md similarity index 88% rename from doc/install-dependencies.md rename to doc/en_US/install-dependencies.md index cdacd27..0975a48 100644 --- a/doc/install-dependencies.md +++ b/doc/en_US/install-dependencies.md @@ -9,21 +9,15 @@ CentOS 7 > Install OpenResty and other required dependencies. ```shell -# Install epel, `LuaRocks` need it. +# Addition OpenResty Repo. -wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -sudo rpm -ivh epel-release-latest-7.noarch.rpm - - -# Addition `OpenResty` Repo. - -sudo yum install -y yum-utils +sudo yum -y install yum-utils sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo # Install OpenResty and Dependencies. -sudo yum install -y gcc \ +sudo yum -y install gcc \ gcc-c++ \ git \ curl \ @@ -60,7 +54,7 @@ sudo yum -y install MariaDB-server MariaDB-client # Start `MariaDB` Server. -sudo systemctl status mariadb +sudo systemctl start mariadb # Initialize `MariaDB` and set root password. @@ -86,7 +80,7 @@ sudo apt-get update # Install OpenResty and Dependencies. -sudo apt-get install -y build-essential \ +sudo apt-get -y install build-essential \ gcc \ g++ \ git \ diff --git a/doc/plugin-cn.md b/doc/plugin-cn.md deleted file mode 100644 index a96cce1..0000000 --- a/doc/plugin-cn.md +++ /dev/null @@ -1,57 +0,0 @@ -## 插件 - -> 插件是为自定义网关功能保留的接口,可以通过插件针对应用程序场景进行扩展。 - -- [全局插件列表](#全局插件列表) -- [服务插件保存](#服务插件保存) -- [服务插件删除](#服务插件删除) -- [路由插件保存](#路由插件保存) -- [路由插件删除](#路由插件删除) - - -### 全局插件列表 -```shell -curl X GET http://127.0.0.1:10080/apioak/admin/plugins -``` - - -### 服务插件保存 -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/service/{service_id}/plugin -d ' -{ - "key": "limit-conn", - "config": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } -}' -``` - - -### 服务插件删除 -```shell -curl -X DELETE http://127.0.0.1:10080/apioak/admin/service/{service_id}/plugin/{plugin_key} -``` - - -### 路由插件保存 -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/router/{router_id}/plugin -H "APIOAK-SERVICE-ID: {service_id}" -d ' -{ - "key": "limit-conn", - "config": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } -}' -``` - - -### 路由插件删除 -```shell -curl -X DELETE http://127.0.0.1:10080/apioak/admin/router/{router_id}/plugin/{plugin_key} -H "APIOAK-SERVICE-ID: {service_id}" -``` diff --git a/doc/plugin.md b/doc/plugin.md deleted file mode 100644 index 12a870f..0000000 --- a/doc/plugin.md +++ /dev/null @@ -1,57 +0,0 @@ -## Plugin - -> A plugin is an interface reserved for a custom gateway function, and can be extended for application scenarios through plugins. - -- [Global Plugin Lists](#Global-Plugin-Lists) -- [Service Plugin Save](#Service-Plugin-Save) -- [Service Plugin Remove](#Service-Plugin-Remove) -- [Router Plugin Save](#Router-Plugin-Save) -- [Router Plugin Remove](#Router-Plugin-Remove) - - -### Global Plugin Lists -```shell -curl X GET http://127.0.0.1:10080/apioak/admin/plugins -``` - - -### Service Plugin Save -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/service/{service_id}/plugin -d ' -{ - "key": "limit-conn", - "config": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } -}' -``` - - -### Service Plugin Remove -```shell -curl -X DELETE http://127.0.0.1:10080/apioak/admin/service/{service_id}/plugin/{plugin_key} -``` - - -### Router Plugin Save -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/router/{router_id}/plugin -H "APIOAK-SERVICE-ID: {service_id}" -d ' -{ - "key": "limit-conn", - "config": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } -}' -``` - - -### Router Plugin Remove -```shell -curl -X DELETE http://127.0.0.1:10080/apioak/admin/router/{router_id}/plugin/{plugin_key} -H "APIOAK-SERVICE-ID: {service_id}" -``` diff --git a/doc/router-cn.md b/doc/router-cn.md deleted file mode 100644 index 8ac7048..0000000 --- a/doc/router-cn.md +++ /dev/null @@ -1,212 +0,0 @@ -## 路由 - -> 路由是API网关的核心,负责所有请求的转发、认证、鉴权等。 - - -- [结构解析](#结构解析) -- [创建接口](#创建路由) -- [更新接口](#更新路由) -- [查询接口](#查询路由) -- [删除接口](#删除接口) -- [接口列表](#路由列表) -- [接口插件保存](#路由插件保存) -- [接口插件删除](#路由插件删除) -- [接口环境发布](#路由环境发布) -- [接口环境删除](#路由环境删除) - - -### 结构解析 -|名称|类型|必选|说明| -|---|---|---|---| -|name |string |是| 接口名称,字符长度 `1-60`。| -|path |string |是| 前端路径地址。| -|method |string |是| 前端请求方式:`GET`、`POST`、`PUT`、`DELETE`、`HEAD`。| -|enable_cors |boolean|是| 是否支持跨域。| -|desc |string |否| 前端接口描述。| -|request_params |array |否| 前端参数列表。| -|request_params[].name |string |否| 前端参数名称。| -|request_params[].position |string |否| 前端参数位置:`Header`、`Path`、`Query`。| -|request_params[].type |string |否| 前端参数数据类型:`string`、`int`、`long`、`float`、`double`、`boolean`。| -|request_params[].default_val |string |否| 前端参数默认值。| -|request_params[].require |boolean|否| 前端参数是否必传。| -|request_params[].desc |string |否| 前端参数描述。| -|service_path |string |否| 后端参数路径。| -|service_method |string |是| 后端请求方式:`GET`、`POST`、`PUT`、`DELETE`、`HEAD`。| -|timeout |integer|否| 后端超时(秒)。| -|service_params |array |否| 后端参数列表。| -|service_params[].service_name |string |否| 后端参数名。| -|service_params[].service_position |string |否| 后端参数位置:`Header`、`Path`、`Query`。| -|service_params[].name |string |否| 对应前端参数名。| -|service_params[].position |string |否| 对应前端参数位置。| -|service_params[].type |string |否| 对应参数数据类型:`string`、`int`、`long`、`float`、`double`、`boolean`。| -|service_params[].desc |string |否| 对应前端参数描述。| -|constant_params |array |否| 常量参数。| -|constant_params[].name |string |否| 常量参数名。| -|constant_params[].position |string |否| 常量参数位置:`Header`、`Path`、`Query`。| -|constant_params[].value |string |否| 常量参数值。| -|constant_params[].desc |string |否| 常量参数描述。| -|response_type |string |是| 返回类型:`JSON`、`HTML`、`TEXT`、`XML`、`BINARY`。| -|response_success |string |是| 成功返回类型。| -|response_fail |string |是| 错误返回类型。| -|response_error_codes |array |否| 错误码配置。| -|response_error_codes[].code |integer|否| 错误码。| -|response_error_codes[].msg |string |否| 错误信息。| -|response_error_codes[].desc |string |否| 错误备注。| -|plugins |object |否| 服务下插件对象集合。| -|plugins[plugin_key?] |object |否| 所属插件下存储相关参数配置。| -|push_dev |object |否| 发布环境,包括 `prod`、 `beta`、 `dev`,值为boolean。| - - -### 创建路由 -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/router -H "APIOAK-SERVICE-ID: {service_id}" -d ' -{ - "name": "news list interface", - "path": "/news", - "method": "GET", - "enable_cors": true, - "desc":"Query the news list interface by time and column", - "request_params": [ - { - "name": "time", - "position": "Query", - "type": "string", - "default_val": "2019-12-12", - "require": false, - "desc": "" - } - ], - "service_path": "/api/v1/news", - "service_method": "GET", - "timeout": 5, - "service_params": [ - { - "service_name": "time", - "service_position": "Query", - "name": "time", - "position": "Query", - "type": "string", - "desc": "" - } - ], - "constant_params":[ - { - "name": "gateway", - "position": "Query", - "value": "apioak", - "desc": "" - } - ], - "response_type": "JSON", - "response_success": "{\"code\":200,\"message\":\"OK\"}", - "response_fail": "{\"code\":500,\"message\":\"error\"}", - "response_error_codes":[ - { - "code": 200, - "msg": "OK", - "desc": "" - } - ], - "plugins":{ - "limit-conn": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } - } -}' -``` - - -### 更新路由 -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/router/{id} -H "APIOAK-SERVICE-ID: {service_id}" -d ' -{ - "name": "news list interface", - "path": "/news", - "method": "GET", - "enable_cors": true, - "desc":"Query the news list interface by time and column", - "request_params": [ - { - "name": "time", - "position": "Query", - "type": "string", - "default_val": "2019-12-12", - "require": false, - "desc": "" - } - ], - "service_path": "/api/v1/news", - "service_method": "GET", - "timeout": 5, - "service_params": [ - { - "service_name": "time", - "service_position": "Query", - "name": "time", - "position": "Query", - "type": "string", - "desc": "" - } - ], - "constant_params":[ - { - "name": "gateway", - "position": "Query", - "value": "apioak", - "desc": "" - } - ], - "response_type": "JSON", - "response_success": "{\"code\":200,\"message\":\"OK\"}", - "response_fail": "{\"code\":500,\"message\":\"error\"}", - "response_error_codes":[ - { - "code": 200, - "msg": "OK", - "desc": "" - } - ], - "plugins":{ - "limit-conn": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } - } -}' -``` - - -### 查询路由 -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/router/{router_id} -H "APIOAK-SERVICE-ID: {service_id}" -``` - - -### 删除路由 -```shell -curl -X DELETE http://127.0.0.1:10080/apioak/admin/router/{router_id} -H "APIOAK-SERVICE-ID: {service_id}" -``` - - -### 路由列表 -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/routers -H "APIOAK-SERVICE-ID: {service_id}" -``` - - -### 路由环境发布 -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/router/{router_id}/env/{env} -H "APIOAK-SERVICE-ID: {service_id}" -d ' -``` -> `env` 变量值为 `prod`、`beta`、`dev`。 - - -### 路由环境删除 -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/router/{router_id}/env/{env} -H "APIOAK-SERVICE-ID: {service_id}" -d ' -``` diff --git a/doc/router.md b/doc/router.md deleted file mode 100644 index f72cf16..0000000 --- a/doc/router.md +++ /dev/null @@ -1,208 +0,0 @@ -## Router - -> Routing is the core of the API gateway and is responsible for forwarding, authentication, and authentication of all requests. - -- [Data Structure](#Data-Structure) -- [Router Create](#Router-Create) -- [Router Update](#Router-Update) -- [Router Query](#Router-Query) -- [Router Remove](#Router-Remove) -- [Router Lists](#Router-Lists) -- [Router Env Push](#Router-Env-Push) -- [Router Env Remove](#Router-Env-Remove) - - -### Data Structure -|Name|type|Required|Description| -|---|---|---|---| -|name |string |Y| Router name, character length `1-60`.| -|path |string |Y| Front-end path address.| -|method |string |Y| Front-end request mode:`GET`、`POST`、`PUT`、`DELETE`、`HEAD`.| -|enable_cors |boolean|Y| Cross-domain support.| -|desc |string |N| Front-end router description.| -|request_params |array |N| Front-end parameter list.| -|request_params[].name |string |N| Front-end parameter name.| -|request_params[].position |string |N| Front parameter position:`Header`、`Path`、`Query`.| -|request_params[].type |string |N| Front-end parameter data type:`string`、`int`、`long`、`float`、`double`、`boolean`.| -|request_params[].default_val |string |N| The default values of the front-end parameters.| -|request_params[].require |boolean|N| Whether the front end parameters must be passed.| -|request_params[].desc |string |N| Front-end parameter description.| -|service_path |string |N| Back end parameter path.| -|service_method |string |Y| Backend request mode:`GET`、`POST`、`PUT`、`DELETE`、`HEAD`.| -|timeout |integer|N| Backend timeout (seconds).| -|service_params |array |N| Backend parameter list.| -|service_params[].service_name |string |N| Backend parameter names.| -|service_params[].service_position |string |N| Backend parameter position:`Header`、`Path`、`Query`.| -|service_params[].name |string |N| Corresponds to the front parameter name.| -|service_params[].position |string |N| Corresponding to the parameter position of the front end.| -|service_params[].type |string |N| Corresponding parameter data type:`string`、`int`、`long`、`float`、`double`、`boolean`.| -|service_params[].desc |string |N| Corresponding front end parameter description.| -|constant_params |array |N| Constant parameters.| -|constant_params[].name |string |N| Constant parameter name.| -|constant_params[].position |string |N| Constant parameter position:`Header`、`Path`、`Query`.| -|constant_params[].value |string |N| Constant parameter value.| -|constant_params[].desc |string |N| Constant parameter description.| -|response_type |string |Y| The return type:`JSON`、`HTML`、`TEXT`、`XML`、`BINARY`.| -|response_success |string |N| Successful return.| -|response_fail |string |N| Error return.| -|response_error_codes |array |N| Error code configuration.| -|response_error_codes[].code |integer|N| Error code.| -|response_error_codes[].msg |string |N| Error message.| -|response_error_codes[].desc |string |N| Error note.| -|plugins |object |N| A collection of plug-in objects under the service.| -|plugins[plugin_key?] |object |N| The associated parameter configuration is stored under the owning plug-in.| -|push_dev |object |N| Push environment,key is `prod`、 `beta`、 `dev`,value is `boolean`.| - - -### Router Create -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/router -H "APIOAK-SERVICE-ID: {service_id}" -d ' -{ - "name": "news list interface", - "path": "/news", - "method": "GET", - "enable_cors": true, - "desc":"Query the news list interface by time and column", - "request_params": [ - { - "name": "time", - "position": "Query", - "type": "string", - "default_val": "2019-12-12", - "require": false, - "desc": "" - } - ], - "service_path": "/api/v1/news", - "service_method": "GET", - "timeout": 5, - "service_params": [ - { - "service_name": "time", - "service_position": "Query", - "name": "time", - "position": "Query", - "type": "string", - "desc": "" - } - ], - "constant_params":[ - { - "name": "gateway", - "position": "Query", - "value": "apioak", - "desc": "" - } - ], - "response_type": "JSON", - "response_success": "{\"code\":200,\"message\":\"OK\"}", - "response_fail": "{\"code\":500,\"message\":\"error\"}", - "response_error_codes":[ - { - "code": 200, - "msg": "OK", - "desc": "" - } - ], - "plugins":{ - "limit-conn": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } - } -}' -``` - -### Router Update -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/router/{id} -H "APIOAK-SERVICE-ID: {service_id}" -d ' -{ - "name": "news list interface", - "path": "/news", - "method": "GET", - "enable_cors": true, - "desc":"Query the news list interface by time and column", - "request_params": [ - { - "name": "time", - "position": "Query", - "type": "string", - "default_val": "2019-12-12", - "require": false, - "desc": "" - } - ], - "service_path": "/api/v1/news", - "service_method": "GET", - "timeout": 5, - "service_params": [ - { - "service_name": "time", - "service_position": "Query", - "name": "time", - "position": "Query", - "type": "string", - "desc": "" - } - ], - "constant_params":[ - { - "name": "gateway", - "position": "Query", - "value": "apioak", - "desc": "" - } - ], - "response_type": "JSON", - "response_success": "{\"code\":200,\"message\":\"OK\"}", - "response_fail": "{\"code\":500,\"message\":\"error\"}", - "response_error_codes":[ - { - "code": 200, - "msg": "OK", - "desc": "" - } - ], - "plugins":{ - "limit-conn": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } - } -}' -``` - - -### Router Query -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/router/{router_id} -H "APIOAK-SERVICE-ID: {service_id}" -``` - - -### Router Remove -```shell -curl -X DELETE http://127.0.0.1:10080/apioak/admin/router/{router_id} -H "APIOAK-SERVICE-ID: {service_id}" -``` - - -### Router Lists -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/routers -H "APIOAK-SERVICE-ID: {service_id}" -``` - - -### Router Env Push -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/router/{router_id}/env/{env} -H "APIOAK-SERVICE-ID: {service_id}" -d ' -``` -> `env` variable is `prod`、`beta`、`dev`. - - -### Router Env Remove -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/router/{router_id}/env/{env} -H "APIOAK-SERVICE-ID: {service_id}" -d ' -``` diff --git a/doc/service-cn.md b/doc/service-cn.md deleted file mode 100644 index 4f0a168..0000000 --- a/doc/service-cn.md +++ /dev/null @@ -1,184 +0,0 @@ -## 服务 - -> 服务的可以理解为一组API的抽象,在APIOAK中服务是最顶层维度,所有的插件、上游节点、接口全部是在服务之下的,在实际的业务场景中可以把服务理解为项目。 - -- [结构解析](#结构解析) -- [创建服务](#创建服务) -- [更新服务](#更新服务) -- [查询服务](#查询服务) -- [删除服务](#删除服务) -- [服务列表](#服务列表) -- [添加/重新配置服务下插件](#添加/重新配置服务下插件) -- [删除服务下插件](#删除服务下插件) - -### 结构解析 -|名称|必选|说明| -|---|---|---| -|name |是| 服务名称,字符长度 `1-20`。| -|prefix |是| 服务请求前缀,字符长度 `1-20`。| -|desc |否| 服务说明,字符长度 `1-50`。| -|upstreams |是| 上游服务节点集合。| -|upstreams.prod |否| 生产环境节点,`prod`、`beta`、`beta` 至少存在一个。| -|upstreams.prod.host |是| 上游主机地址。| -|upstreams.prod.type |是| 负载均衡算法 `chash` 或 `roundrobin`。| -|upstreams.prod.nodes |是| 上游节点信息,可以为多组。| -|upstreams.prod.nodes[].port |是| 节点端口,取值范围 `0-65535`。| -|upstreams.prod.nodes[].ip |是| 节点IP地址。| -|upstreams.prod.nodes[].weight |是| 节点权重,取值范围 `0-100`。| -|upstreams.beta |是| 同 `upstreams.prod`。| -|upstreams.dev |是| 同 `upstreams.prod`。| -|plugins |是| 服务下插件对象集合。| -|plugins[plugin_name?] |是| 所属插件下存储相关参数配置。| - -### 创建服务 -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/service -d ' -{ - "name":"First APIOAK Project", - "prefix":"/one", - "desc":"this is a first apioak project", - "upstreams":{ - "prod":{ - "host":"prod.apioak.com", - "type":"chash", - "nodes":[ - { - "port":10111, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10222, - "ip":"127.0.0.1", - "weight":50 - } - ] - }, - "dev":{ - "host":"dev.apioak.com", - "type":"roundrobin", - "nodes":[ - { - "port":10333, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10444, - "ip":"127.0.0.1", - "weight":50 - } - ] - }, - "beta":{ - "host":"beta.apioak.com", - "type":"chash", - "nodes":[ - { - "port":10555, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10666, - "ip":"127.0.0.1", - "weight":50 - } - ] - } - }, - "plugins":{ - "limit-conn": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } - } -}' -``` - -### 更新服务 -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/service/{service_id} -d ' -{ - "name":"First APIOAK Project", - "prefix":"/one", - "desc":"this is a first apioak project", - "upstreams":{ - "prod":{ - "host":"prod.apioak.com", - "type":"chash", - "nodes":[ - { - "port":10111, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10222, - "ip":"127.0.0.1", - "weight":50 - } - ] - }, - "dev":{ - "host":"dev.apioak.com", - "type":"roundrobin", - "nodes":[ - { - "port":10333, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10444, - "ip":"127.0.0.1", - "weight":50 - } - ] - }, - "beta":{ - "host":"beta.apioak.com", - "type":"chash", - "nodes":[ - { - "port":10555, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10666, - "ip":"127.0.0.1", - "weight":50 - } - ] - } - }, - "plugins":{ - "limit-conn": { - "conn": 200, - "burst": 100, - "key": "http_x_real_ip", - "default_conn_delay":1 - } - } -}' -``` - -### 查询服务 -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/service/{service_id} -``` - - -### 删除服务 -```shell -curl -X DELETE http://127.0.0.1:10080/apioak/admin/service/{service_id} -``` - - -### 服务列表 -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/services -``` diff --git a/doc/service.md b/doc/service.md deleted file mode 100644 index 7fcfcfd..0000000 --- a/doc/service.md +++ /dev/null @@ -1,162 +0,0 @@ -## Service - -> The service can be understood as an abstraction of a set of APIs. In APIOAK, the service is the topmost dimension. All plug-ins, upstream nodes, and interfaces are all under the service. In actual business scenarios, services can be understood as projects. - -- [Data Structure](#Data-Structure) -- [Create Service](#Create-Service) -- [Update Service](#Update-Service) -- [Query Service](#Query-Service) -- [Delete Service](#Delete-Service) -- [Service List](#Service-List) - -### Data Structure -| Name | Required | Description | -|---|---|---| -|name |Y| Service name, character length `1-20`.| -|prefix |Y| Service request prefix, character length `1-20`.| -|desc |N| Service description, character length `1-50`.| -|upstreams |Y| A collection of upstream service nodes.| -|upstreams.prod |N| Production environment node, at least one of `prod`,` beta`, and `beta` exists.| -|upstreams.prod.host |Y| Upstream host address.| -|upstreams.prod.type |Y| Load balancing algorithm `chash` or` roundrobin`.| -|upstreams.prod.nodes |Y| The upstream node information can be multiple groups.| -|upstreams.prod.nodes[].port |Y| Node port. The value ranges from 0 to 65535.| -|upstreams.prod.nodes[].ip |Y| Node IP address.| -|upstreams.prod.nodes[].weight |Y| Node weight. The value ranges from 0 to 100.| -|upstreams.beta |Y| Same as `upstreams.prod`.| -|upstreams.dev |Y| Same as `upstreams.prod`.| - -### Create Service -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/service -d ' -{ - "name":"First APIOAK Project", - "prefix":"/one", - "desc":"this is a first apioak project", - "upstreams":{ - "prod":{ - "host":"prod.apioak.com", - "type":"chash", - "nodes":[ - { - "port":10111, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10222, - "ip":"127.0.0.1", - "weight":50 - } - ] - }, - "dev":{ - "host":"dev.apioak.com", - "type":"roundrobin", - "nodes":[ - { - "port":10333, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10444, - "ip":"127.0.0.1", - "weight":50 - } - ] - }, - "beta":{ - "host":"beta.apioak.com", - "type":"chash", - "nodes":[ - { - "port":10555, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10666, - "ip":"127.0.0.1", - "weight":50 - } - ] - } - } -}' -``` - -### Update Service -```shell -curl -X POST http://127.0.0.1:10080/apioak/admin/service/00000000000000010080 -d ' -{ - "name":"First APIOAK Project", - "prefix":"/one", - "desc":"this is a first apioak project", - "upstreams":{ - "prod":{ - "host":"prod.apioak.com", - "type":"chash", - "nodes":[ - { - "port":10111, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10222, - "ip":"127.0.0.1", - "weight":50 - } - ] - }, - "dev":{ - "host":"dev.apioak.com", - "type":"roundrobin", - "nodes":[ - { - "port":10333, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10444, - "ip":"127.0.0.1", - "weight":50 - } - ] - }, - "beta":{ - "host":"beta.apioak.com", - "type":"chash", - "nodes":[ - { - "port":10555, - "ip":"127.0.0.1", - "weight":50 - }, - { - "port":10666, - "ip":"127.0.0.1", - "weight":50 - } - ] - } - } -}' -``` - -### Query Service -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/service/00000000000000010080 -``` - -### Delete Service -```shell -curl -X DELETE http://127.0.0.1:10080/apioak/admin/service/00000000000000010080 -``` - -### Service List -```shell -curl -X GET http://127.0.0.1:10080/apioak/admin/services -``` diff --git a/doc/zh_CN/install-dependencies.md b/doc/zh_CN/install-dependencies.md new file mode 100644 index 0000000..1f621c4 --- /dev/null +++ b/doc/zh_CN/install-dependencies.md @@ -0,0 +1,118 @@ +# 安装依赖 + +* [CentOS 7](#centos-7) +* [Ubuntu 18](#ubuntu-18) + +CentOS 7 +======== + +> 安装 OpenResty 和其他必需的依赖项。 + +```shell +# 添加 OpenResty 镜像源。 + +sudo yum -y install yum-utils +sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo + + +# 安装 OpenResty 和依赖项。 + +sudo yum -y install gcc \ + gcc-c++ \ + git \ + curl \ + wget \ + openresty \ + openresty-resty \ + automake \ + autoconf \ + luarocks \ + lua-devel \ + libtool \ + pcre-devel +``` + + +> 安装 MariaDB + +```shell +# 添加 MariaDB 镜像源。 + +sudo cat > /etc/yum.repos.d/MariaDB.repo < 安装 OpenResty 和其他必需的依赖项。 + +```shell +# 添加 OpenResty 镜像源。 + +wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add - +sudo apt-get update +sudo apt-get -y install software-properties-common +sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" +sudo apt-get update + + +# 安装 OpenResty 和依赖项。 + +sudo apt-get install -y build-essential \ + gcc \ + g++ \ + git \ + curl \ + wget \ + openresty \ + openresty-resty \ + automake \ + autoconf \ + luarocks \ + libtool \ + libpcre3-dev + + +# 安装 OpenResty 成功后,会默认启动,此时先将其停止。 + +sudo openresty -s stop +``` + + +> 安装 MariaDB + +```shell +# 导入密钥并添加存储库。 + +sudo apt-get -y install software-properties-common +sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc' +sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://mirror.hosting90.cz/mariadb/repo/10.2/ubuntu bionic main' +sudo apt update + + +# 初始化 MariaDB 并设置 root 密码(安装过程中会提示设置 root 密码)。 + +apt -y install mariadb-server +``` -- Gitee From ff7e2713a427e1b56df93038bdc9744e0815223f Mon Sep 17 00:00:00 2001 From: Janko Date: Mon, 6 Apr 2020 22:10:03 +0800 Subject: [PATCH 164/165] change: update process image. --- README.md | 6 +++--- README_CN.md | 4 ++-- doc/images/APIOAK-process.png | Bin 76689 -> 71145 bytes 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 925ada2..bd95029 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ APIOAK performance is almost comparable to native `Nginx`, and provides dynamic - Support plug-in hot swap. - - Support Mock request, accelerate the development process of front and back end separation. + - Support `Mock` request, accelerate the development process of front and back end separation. - Supports automatic generation of routing (APIs) documents. @@ -61,9 +61,9 @@ APIOAK performance is almost comparable to native `Nginx`, and provides dynamic - **Users** - - Support user login and registration. + - Support users login and registration. - - Support user creation and editing. + - Support users to create, edit and delete. - Support users to disable globally. diff --git a/README_CN.md b/README_CN.md index c52a01e..ca18744 100644 --- a/README_CN.md +++ b/README_CN.md @@ -51,7 +51,7 @@ APIOAK 提供了几乎可以媲美原生 `Nginx` 的强劲性能,通过插件 - 支持插件热插拔。 - - 支持Mock请求,用户加速前后端分离开发过程。 + - 支持 `Mock` 请求,加速前后端分离开发过程。 - 支持自动生成路由(APIs)文档。 @@ -63,7 +63,7 @@ APIOAK 提供了几乎可以媲美原生 `Nginx` 的强劲性能,通过插件 - 支持用户登录、注册。 - - 支持用户创建、编辑。 + - 支持用户创建、编辑、删除。 - 支持用户全局禁用。 diff --git a/doc/images/APIOAK-process.png b/doc/images/APIOAK-process.png index 0bfee43d8caf5deab4fc45c0a178982bfa1869c7..95a2fbd26f2345aa0ed8502bcff4a597eb2e6fd4 100644 GIT binary patch literal 71145 zcma%iWmp?+6K)9Z6sJI;v=l4$Qrto*#l5&Y6n7_3q_`A!cc-`o5AGC)0L3L(a5?ll z*ZF_)XE)dGj@&c%%siVXVM+>;xLB{T00024wA2?B008yn6gY{2{BpR-XEl8}pje2> ziva+&G1w1A|E?lAt4MwZRE<;ZzkKmllKUp{{QTV9+_t&3CH`|@dS(vMM0RTWq<5PbYm#gZUegp;;l~reC=f}k-l9H0TxOp%$GnuAO zh=>Ro8X5I192NF&hSV-9DJmd(r4SR2SIfyymn#o@O@9hl_t*Q&L%IB{1hyez4gdfx zK>CZ=H;<*GRb16A9-=Ns2W~>I-+y`KmfMQgpLIl%-iDd5gne!nq6RX z{yqX6c$tkzV?+ybi7{XbdpR14;6ls#cs4PdU|$BcL;reE4%ZM!N=}lf*3IUN=&$-2l*u zyH$p66|UgFZ}v|hrP!dJ4GRX?`HdVCL6>`tN0lQJknM9pM7uMf)90%E;?;@Gjkgpl zryLaNX1KAEok1LSMU*8(-jy8X=SH<`-0Su(vVb0~YySPx*%VoX_;RWoaLk3upIs8y z1gys;YpK&V=1IcJ3Fsu9iRzuR;JsbIOO4`)Q=#>*7GG-g)j%k?+d`<3u+;y8~Ll<{^iM;f<7t(Dk~3U* z@$M~1h6TW~a|%mtDKziXnP>0gvm;li(P0;HF}B{bePqYBg`jwCi@(N*25R^m?$d*a-9!c2UQGxB0l&BfKwl?N9FWNHge*t2GZmi?> zp_rL0S~fuSMivV%WE%Z&#zl9IMU^H|+5OEFg@nz9SI^^?g)5Re3A~UD<_qwU8UR&+>04kAANsJMS!b z5U+iVi?a4o??0`ywq00LOG=zg4W^|4()c{XtcEA?ViI9 z5LQF`fu_!y4Hz3uoFxuNn5O0s zdGsGn1Wdyx9Mn!u_)1Y;A^?#}^Y(T>v)9A2v1#z;SBgid}VF+nAHKy*H>hit zq&XUr>-KRNc*?oc!kZt&%2b{BQ*>&s`(DXijD&r7*vGk1_`7UEE#9!DOaMUSv!wyq zf0Wxc^rDRSYPDAS*}gZEpU!4obd?7>_OKEXT`$g*5B`}MRc5sElWgmFQRpW8Xx{DT zs^-*c3c5yH=Iy}ZBZ-^}=L;oB%kS5UoYkmDk2fY<5l({N>22V4LxD6s96{(z-)kQT z#WiUC-Q#1@TX;)1oouZrHt(1HkE)9K9)*b+Nwj z20_YBoUE07_g;g=-&7px=10%=TtuKau)2QVPyE#Bw)41H9i_&@@pT!}Mr`?+NPbiS z%H+0#+^oR-l)3U-D6{-v`@Ho0l$ZJpK_BJ1AwpsP-rMy}!`eExh4bDv7$ByVf(-^Hlq>R5m&kHrGE7{fd(j$U} z1s|?2Cr%$$NF}?5@yK+H3l0@x?u#U}&KV!Z*6Z?#F=)YghtS-5CAYnYavS@J4^4SA z+1cKeGJa<_r=b6ms|nr!KLJ4 z<%vEQ0+(%1hxyz1w+_R8><#Ee`^^Uq56iy0oU!Q0d`a2fZB$Qdc%FJfiH}8_*=wFY zEqTNxD-L<|Uze)>KJjLfnhhaT3441i0kT#n$E};|jYIvrbPEtg_82cK55G6VI_QVG zvZDzu$srt~^{F*gtn(JYh0L&4M@Br%yV>YKzg0q_Hpg%`ul^WCc6m>xnnMkSfy;%$ zR%2y#JAsq#8A4=3k*P(_8GhZg$1EE}`N6k`@|~RG9k5#uSn)ad{A{E1Y%UMB(805& z`Q<`^%ak$38a}SkC1$*zb>{S}{F%Kc+H7k_5wRxs<3oz4FrzLN(B#D)F|DF4e?jx< z6&=TlkJjty=Z5Q4B+p5hr%(7I5`!#uv4_S75n=GvRQQt7eW1+C&G)XWD>|NIRlc5g z^A^#*(3OOD5u4&UlyxJ(5qR{n0<_Ty8X&(>o?kwU4h`?tIM z+xfq6niiQHyX>s{UnK$t|#EzP(AGs>^V<-*>usZPwrW_+m$0}JyPgCPfF>}`5-1CHihm)E=(ol-QItPy=QOAeG-Y!u zI%b)_cr75RX=w`1BScxx>jqH2H}ilwKY^3Wyo$Zn{6A#b5VhYz_*K&`8Q*e6mLmIm zEMaUZb~YM1XOT5@>0k9#AIhP{^_SmYEnIED3DB<#yWSO(nQhxi6EfH(wHXUk*&f{$ z@o5|T!xfi!=7}mYqm~IYGam20JuHuP9EXF!@EG57=mx=!C?pMQQ66eWYIdvS{ubO+ z;{j$;O5^)WN|V<0qEv_IcwzgQUE`11j_-!OjOzXKZ+Grd+j7uk5ss!mismnBhQ^EM z=k1wX{C7d`O^d6gcih(GRz8inJd}+cudPv=-hCa5%6d9kL=3Qj)hj`QwLr|o?a?dH z^n=1EmRxJDLc)8l8AtLkF&9kD#l4?+YlctK;FGAB8~BGb zjXcF3-2ru20YT6{-(l%`Kj*8^zP^es3F^^0T|8p4%xI!-`6QvB2a%F3Plz@h^ERWB zO%CLamE$?&bbVEMeU8FE`96{BL@PjuhY@|oaj8~ExeT%29HmZ6)N+1J)8(=fuisKP<6e`N4y|m?1Ot$v}LHA6sKN9^=cfu zT&@n!{L2U|l1dIL4@t59pBoq)*GYSDZ0*55qMax?-_ee!U%q2dh3hr<_O}r1&@~QX zzUV#&uyPW2DQXETVI^pwG`vLTwN3&(qjmsBpMCd$x4jaWUdd2ZQOnh1-g!j-ro+~w zxeh*H2QF&`xf9I^cm8WFx zdNeH6fPciYX@q3zZjCY;da zskZpY0}L|SFrLCP{ZigPfabXpZBhZrTYF%d?Z)Vf`Kn%2?em8I`JA3QBk;}@vPeK% zc&{z*uIH+!KXGHN2b#cXU->*|bLa$Fm+{XTu&4m#{Acl0shnD6eVho^vkLv^8#?mN zT9*E;jp-}B_USY*^(r5*ptV9y4q3FKtLo6PVsvh61|5o40#8CUx*xI5r9&tCp2Uxm_zYFEuG~NZ!9( zXCxSQe;)-%-d!sx_9(^Su2u9mMBQsvq3y~?mxw)fe(|Rk^WJ7zZRY~hWJc%n z|4XhIq+&xK`d75FDc6=(>?LTvz*%b4;o)-+(HkSwXuH5uk-Kwl6pXXJr0TbWTk(jb zWycu_tw@*)74S@)w!|^U0f_X0wZq9Z5pxb+4=l)+8vR6pp$;>pueX2dQ1H*h=i>rOycCwMIT0R5_zD_`h=s%rJDf1Dgfejb~ z%-+tBnMr`kj0CF6pA&X~+OilZo9Sf+Tc7RiuGBsf!9t?0eaSiCJK$oJl3u#llk)u} za*J@A&C5chUya@+K_mOM03Bz{d7vom?@Kmq9Qm#M&@_-vZ%fG4M=|M(` zT?~haTZ_jFYk58HlVCX^(6LrP(nPC&QQ6gf^@lD8+J%yyPIJ^GsLIiQtdjPu#m$V^ zFOTo6H~VeOW(`FgxTifhyOL-f0Ph7fLmCWt$yLTn*EJ+J&QFbpA_P)qp(+Qb`i!~% z>UC%4Et!u>cEkdTI5X0?jizHcz=Uvt1$dv_7||CQ(e>DV=bHtPzZAisAaxfm4x}hG zUap_Q`a;`@9~mYxd8-^qa#Mi9)5uhkM~NiNQp3wcT zh51sYGH)$Bd<5_+K`5JTDd01wASzFD(@WwI&6v)}Q3&WYzB!>5;oXDe@e}VKY#du>uBRgc9(Irt0}TYr z+0QyfXauOWde-Z5n*^vUXauZGH)T=Mg+Fel$zASbhbcgF|M5qMqqXLwDykdfzgD!Y z&jCwp1}IjQppx=UlANqB#-rMo+n&JD2&dy8M4zO7xtunv>Uy+vFFh@fdt1-d^2ECn z{PQF`4eT;BeBk6SXEW11z*e$8wV9|A!bwieAch;4(!Vzn3TVpcw|OFDvWSdoWQj{T z`oju`a2L4!j2Oso;^Co2w|e)l6o5^if^Q@Fkb0bD%<9!&acOP#_3qX4Wsu5)j^&~B zDPX*-i_5K!=|c3Klg}xdwCMRw>)p;pswn)C!>QJcro*5cusy}5a{`{?M7I&UJczos}P=tNjz8u zGJBYQ?QeI=dXp=5DEgy^xG-nM6XvEDw762qdV}R z{6h|m0*?n*_@d2Ya&%(_eRNc4{HiosTe_H(&k>DBgi&L&V6*O=$hu#5S1E#5EHF|w zv!Tjx(a@;uDcsKfBfMO-JM(=fn&_t>cfk`!ap8QlXL&k;XQH6&2Wv)-Fn|sV$HAh7 z?`6MDA;e7pFU7JSS*`@Y>O=XwoWx@gY89yN=8Zmd>%g}=8@$BO@uyu=nOwOvSv-=OhdSJ*b&-4M8wav6R0SX{K^W z>@8wzE5)5@fJSd!1fWaZzM`h3z>lK%_B{Gki_<~bzzaAz+d`RQmG7_CzYRF=I0+a> zspt4F;M0S!oz+f3VWG;}tr^G9TX`Q*Hrdm_X%nOr-CPIcakFu|F3C)SUP>1y;e!#x zB_eqCbE+h?1WWC`nv0H?+Yd^Rr`9>22oem7tgp7_dfFTyt{p!3FFir@NrX7vqhU&P zeB8I3&;O<_*_~{CD#6o;u8jtfuP&$2WT@8$zq_$~f|0^}Mcd&PI~zMJpqh*G{u(1; z2NI@e`=9P4qO}F0_{GetSoQdxw!>>k1en%J=C)}o38SoZ8eDiSDe+dv znC=l4@(RAH(dD8$Y7F{^hYF%xg+U=WrBPgtI@Ng9EV!XRfx=*>JEPY{mt+w~RWhHw zG~P!3Od}(A;uWER2UytPKjL?eJ!O%uLEh@=SAjZTsjx(~p{!^b(c-k7INuV7P{^bD zO14~T?{SbiWdd3cinlJq(J{Oh-#2MS$GV(F2s?FrRjeAh4hc}T?xFzN6mJzh8oC|c z(Z%qxq(oa|PRjhNRMkjdY+6`VvPx1=P9Dn~UF0ogDe~1B?82nYh+*+UDbX{vST$bj zu=%OGnXwMf%iSc6)!Swa8w2&?dVBc{l&r7DOS&!vG)LrTXmGnfd$2VJMA6ogqLeJT zD)I9+9Ra&|{ScP<@Z|@oNTBDoaIv#|tfc3SF3}ftTfqRg8Aq=wU{Fib^FmuAkEAWV zr8PntO&S9ydY*#z`Pl$XTGz!Yz-Gtl>!ZVei>~IoTNNXEb9!^7wyz1RZi-TkN&cD##t=XQe${h1ZUIXU-xZIU|4g*gQ@CHg3=?nJ-CJLTpwW9^M3s}w6I1K59Q{foiu6=IwUMcJMzmnxXYCyb`n_T56 z2jhWlw3p1B-;KNw>jV51pPn2K=xxy2KLsAy_vzI&VH}I)kR%j}*M*EszrtIa;XdI* z45>74+Ukc5B_8%N`xw&Yg=wB(ni8NX)pdw^Y=dJ|)Dc=t1hBXyK?QB<91^cYSk2*+ zj<5ZN8Y1!0j*riOhG08-D^8z#@^Mxsashkz_2~J?K;Yx^?^Dl0$B6MSqBn?)?Qj zcwKe_Re>qE82m;t8>5D;;&&_w3tc~0AUc#a9w;OHXsboDh@~DqLvaG@@*Fh<2q?o* zkUdTxoZCZ=#Y$7CtLQ^O&_Nc?7vUO$`%XdXjVz|@L`%FFTUhZWf(jBnvG^V}GB#lr zR$)+st>{DJT#dF(1G1`4%liE4`TlsIB$jJH6j=gEeyg>NTnBnU2zt7RgObJH9c2!t z(N7)iKTi0fe-o#$Xtdry_x#+O&tQ5tT!5CNa@*eMJk(Xkne-c5CITzb#S^@2N2sg7 zC9~c`y1s8)n_;7iPvGYqr?lyH{H8+*Tc#8O!hQc5%;Q zgpUVzd(gU+#SNF z&~7cP3=QLleoK+ZvzR@t(XLVSY&1R3ngakU?+V9N)-+bjmXnNJl`f`)svxRLuL<^E zA!^m<5gpIqwc=GLzZoEIjKgji9DWDu-;S-|xNQY1z^+yD@f9)a3qaoQ z^HiGJXOkZj$d#~kMSO_S-%PIPLo$p)JvDI|Ico%-AIB4|lb7;{)vedOz8A&C#udVi zrp4Ve=uztgj9%x#VmchZXf~=1j~NA(pLpGW_M6SN272P&(I8Vi1#$aJYZ)}{M189+ z+d$yqpGY=fOFtHlvmZBd{*N<7Qj+ReKKirO&`-)PlPqwiJ#>2*HCfa0dylER=XQ`<{fY$y2aXeI z;huq?#@(Ak+#Cj0yLh7D&%G=2J*!s64n7}nmnzkBi>{2z1owi#6Sg&07rr$hhLE0t)Bf=g@ zPr=Q8$a`)XJKf4mN>M~>Mi6y_258{?+e#ql<8p}hf@HVv9;S!f_ryxnEB$-P^s1Il zTz|(mx+#jJb4UuS*;&#Muix)G%q7x)F04fz4tSn?YTI(!J`d<`E0M}0Ab(J>vEl4M zLcOj^2w3N-YaQU1s>Ti~UfX+jGb7}YmxMw3{Kp4~>rZ|WEw(>#2j=a(Ke(S)veSm` zxkt(MuA>8*-=Y56kCz}67#HmajXJqF2q}~RvS7LIvi`YLClCzHm#yT16`>3;|9Fr8 z`w3*22#b>JrZ*&DJ1pzDy!dRBmjtrsd%FeX<^mXx3Q!3?iB(3!>rywrz77Zf#=J!a z6iBHnjWGW`AIf_&C5g&G@iR{=t=3%ck@%8RRw9O{soH1gbJH||gX zb24(Ys92+EuE?btSp-C{xP)yByu^4}6LpQka>rE+t8~jGJoMLlMiOfKzf5@~SllMa zJ$sK*Lv6h(mDJEJGJ`;i-`q~aW2?k_fuMJ&UTJIRh;~BcJJ4lBTIypK0K}c*M(dDw z@D*u6L=P)7Q&cpMgS7!bAL1h}tWkySCDi7=sDx>(#>7ye-HF!+;X|o1U~$*I$)}Cb zS1f7eNo25xo`qDZALI_8rh!u5CWhI%b)bbcTk@SZwU|KZ z8*aJpmotDv3!~G^&xr@J0r{sjwQd`59Dqt6OHm;x_mdcLMW3a$l+_bvQK3E`Bd1}; zO9UVq4<-b#0>a(X@xsk+!bIYWpg%pI{N7N0$PuiHrJ5leJDR`S8s`r1*z9hAsS*7p zYNpPK1HS4C;ANT=#`&yw1{VU13u%5l;&p*e z4|g}enb@e`^#7_OjLKAlG+T$pn}USNu@jx{H8R%U8u{(A>G?gEsPTQJ+{I4@3}VUG zFx_Kj)VrN##JnYT&H9Wu!O$p`2hetbwq1CEo>i?{>L-4A!+SJTur_kRe-|)aNYMe* zDY`7RsatX)P5L_p)N`x-Gjg)7MZvq#;zBT;ji_=!k<#3+x3 zWS=mF%KC&w>3E-#1%oP99Z}svt3ULvcLmnU`k*?mx=!IK$p-wlfK`CaIgoSXP6!C5 z>l}212B^RYuk+N6M?>6GqH1MLHrB0}D>2;3WS}p94WrVR2SHSSz?#)nCsD#&%#QZZ zG&W7OBO zyVPm>{}DA%_I3@VUpDK2f?+Kkl}q^i1)1+eI#kWRz=kQD=L|O4|C&3ExO6i+6tGoC z*x~9=XooDXlq4Ro_~!nR50B-)3#AZPI5o=Gcvtq;Yp1%REJ!rUDeU75=B1nKS#aAP zW(#l&%E?qu7q_b3q+@kqms8B+06|SglRn6OWmhF<$t!GNa~_-f>Jw-`->8`+6n_Jb z3?PoHOtxh=x2HE>jo>lB8)t~{s;?2Os(Kl_4jID1lAXNXeKK@{<^Tt~R~QK7zuiuW zNzuQqnsO>ihjdW-LIv6CNHpJ6cOIec%g$)virW5Lmaf9kEtojwV_1A3yprXf%-``X3*2aH`bM*SyM+5QaKs1C zNH%V{XSEa~o3}X7x6nrLdvY>^+d|j@jAAgI2zPUqSbh9HNDQDxJz_!<_}2cauq%QB zS%;9eOxBwHt8E>!t#=a8`LQA|#=`yMNo=X0A#v<9T|sEc5iZ)VT zHtYA+jz``^X><3{L+)>EUpdG<>G{ zem5JZ%d1Pd>!StvsGDS`*bl_A3IVkzfgN{cY|q4QP$U*k0q}?gJ=*Q7vbc|p(&sLt zw6BhE`PKDL8M36E=8n0~wSAe=EzZJ=@qb*seJk*W!dZP%L;%5avpu@IrDEL0x6@5a zbpTdMg4@zOVZ`D?>eJtha*le~=Jsz8XVx^O#);;ZNp~5vQ8T>lV2*Js>@q$i|Ip6- zD3|7z{(u1NNdT5Vw3yzdc(dibj9DV|6CbR6pZm%+&dnzze1ojc;42K~V?4;KK05KL zuL`=93_7Le)YsMrW`A`z&f6sO#_l|;rK{o?77cB?8JMktda$(0T0f3xH>CJXm#gG- zuuUH)k}IA~1ibZ>N%Ja1YD@z90&48O$=cS0S4Xmb6k*67+LuyF|CGq9f2j0~>z<(> zR}f<_z#{7`iZyj5ZA>oe_k)=HAi^C2ZH&|phCIm(pcQMjEqDX?`afO)NGOr|R!QU@ zmj~)PK85Lx6(*h;A>I%Lw$QJ&{@9-iO$u)evn;+ss%d>M&ktp;uf5Cjuja}9G4c18 z*A}9PI5c05hV6u{KQsBQ){ahLGP(It`YGdOcW&o*tFwvg`*{i~O^u#avT#!VYg)9s zH^+I&b2G7tRxT@JB73#M^bVOz?~YG6k$B&Db~X~m4{7v?qDNIpJB^wDt#%N8>kVMM zgm-7w1~dNYhZIl~HT5yI+!;6RB=H9(VkaGDN_XHn$J}RH(sCKhxE)lLne=FR#C`Vd zH+E&|5F`gik(r_3F}@Rxaey)<@y>c;O>zG!%rKn0w$-3?AHtSpiv7ahr)GSXce5)L zm~U>ME&#&=d_QAHt-<<5u30s>`MU=9gR4m#R1ZUr{>yTH`|hD57;+(}@!y4(UXc+C z$LzECL%dR~zvQ*GATDVJkc95qJ)-A?uPyEu%7^M9>1ODBb)e54Ju0(Z7%3E9ROsXc7CPZ|7Tou1 zW9PcyQZfcfXog`Tw)-HP!Z(M!n<~4u2N;P>Y<*(Tj)AgN!xVGQ0kchw?Iqz4e;q~! z9p-rP85~(r-@Fv*1g~H10xe^^gyQeD`>*_CE06Nf2iLl9@tQ_WhvRC(#S&*x7^%OpGj&-tyrMAuh*I4 zPkm2!$XZ6;aQSu~>zN-7NFjhkz0lm@Rdr=&0*bLe7SfQ>9Cfjmf$~T1%j-MCz9n-9 z=rP;pk>V_k`&7Ol;sbZKZtUJ##MU?b(8o46ruPJi+(Ki^rf4T8K;_2q{!pcI0ae645+y4^3C)@eJtJMvOxTOzb1#q-XX6dl4I?9<7`k=wZ z47IH_FRTKpqeRtf6&|*@THPbWNHsfP(Gj!HH9jNMk|!USYt|#ROm_}{`^bg`Xh;z2 z*C}PIKa?*qf3P~C|Krgc@O=tpKH~?RB8a!)>%3!3Ls+ugp*Qfv?;Ztx>b-_Bpr#ZL z+NmWHz2YQdUSt(r;)nKpfQv|xeZ}eMxZ+6g!o&&QYb>)lF8)HEN0SfGwO7b(G~R7n znrvJH&X^%I+BD?mG&>=#rU#E_4k9w)OL62C-?vhWKjQ3d z7zBa}FdVLNzK>9&D+KIzHVJ2as0xJJ0$T4ugDK8_t0-{qmaRKqOJeCE6Ue9!HBE;m z7S*a_(SM6$$s8m9>h48i!<)$b@80%@!6&@cUw4^_)UHTTNKHzz5B#Q5{0zjm40^dj z8z>)-C^!8KVq-K#TDD^4Bnz`gG?4Q(HrOkF`g8PYBP?X+$qmW8wwH^XIB(ay653*@ zOWi7?3>rC3n-8N|m^nki;f7T$TV9itTG`$_RzCHCeL1b`8#-8IS{|#PeXERJt?lin zzU6_Do-QeaY=-=PsvgM%gs4t_UeI!u1+-uCX({$@sIC)e^d|p})H#6o%;#xM&=MJ* z_msa^dW-dY{FlW=&PPgs{9a;pmA1hzi%*mY?bCTN*yhb$r(iU8+nEGoGR67LXdd#D zQESd|Yi6jKj71Dj^VVnIJUTU?e9?43nR~4qW=oO zUK@30DV)xKdrU6Uk~-W|EB&l?AQULX0Ke*%`?OLBiW zC+>$FqrnhCJl2&iuTiRFKXW6ZSK=r5EF$2ej|X%l(FNgK9Qb$$Et&hZoAZ(OVV?0K z<%ctGN=!|C3kSsqb()NZO6N=~5!i8JXDWBWF~kK`k8YJK*6@_9+>mK99T+$P&^l>G z0&KHv(j?oOgn`pmRvFgj&8`rdgGII}VJOUmSpSX5IjW^}pMYTE2AG~1@}M7rpL^AG zfF=zk820)l6G{?`Rqxju+1Wcf!?-J0A(XsSk%EYjqj?}O``6x1Y96eF) zd#V&GXeW_9sT%iv*I~M8ociJTp?OL|fwW9zlG?pXVlPnq5%F0k?D`WKN-PB9>2b?j zgQlEew!l=!oAW4!mcThzhXG2>5bs)O--ydqzJ;TPkV18V?t?L_P>-*>4@2|Bwtr2dbCfW?|dFntPKM@SSQ7N72jHQg+;KJ-1* zJMGY%{!=_NNz{e@Bz3Vx?~N;y1DwK=>6GphK)sl3`mp7yV`xz~Fv>>p-79KdqnC*} zf9`R|#+%E_*+*g!5Ok4IQTh|0C}NU?%EU3dPV}r2Gg)5p-@bSJO>x^gTpQ2_ApyQ= z->@BLDg$<)1=Ma@{h=AzBre>xSN(F*vJzsItMu{vpXmbni>Y^iqh|!E*kAh8T(TZp zvDbNV1<&W|q@o@ltd6vt3b>!(UYjd0XVZi;nt_QUL=(0bEzY8I5DiL!+tvy4YaL8! z=i@Cp|E|t6qSm*3r2&V~C~SU6qA@^n0fk=NwSvN~%KA6%Bo6RV){$I^&(bK7WR6{7=|SoVib*t=7|bucSZT$@BaG zTix7qM$d(G)XJTJS+w`)W_U7#&ilgVer3%Ip`;Dm@sB^)Jv#vrwy?{7e~DniG>7KVj= zCp(=9((>w`pEo5%efKQasn;Q_2cxuvn!3Np6#Pil#^A&;LAPsLy`v~gnLYH4|0n3prjh)0L+Z|_1E9# zN~g(@2{1xx%AQjeAi3k}^WerTq-C;aQ_}h3GGqUi{QGV`tfwpTXi{I*<5SU>^^Wrv zBSAeQsTOUJscd6>6p`rpGk6Qh>n-{F_ugTpX-4f% zyFtU{cq$>E^l}@&);aP9C6Rb#F6r?t(JOao|IK<{hPCe*J)iLN&=OtE%O3GY`d6PX zspOm6%);i!wuyI+EGDk!6Iovq0J?>x%QP!~GP(%PBB6Yn$?*TfEzE3=2b{7w99e0m zsxKfN%(rDr;a0;M_RJ@`(aE1g$POR`rhqM(yqiS|c!vO`^lkI$t4kTC?8va(&U#;` zn_Bu01JA@&aNDFug&RfLKa9R2rX@UL{NYy7W=0 z{N9zNGi%}ce^)UMI>JJA8x~uV_q`(Xdr``O<33&ym!l6Cj>H1I9bfB z_28$S}2kBhE_-N!@Gu{AILq) zjKekd>ij3$8gJOKSvW<@-Hk@eBwm@X;;L$ypO*XQ=QUUUzi!X;rWc3_YFj;aVewqU)nwN5i=ha=iMJ>V^F_9bo1}zr29iad3@ z+2PT@$!Z;yQ{)pZQ9$&l#)^b|^?l@CIS5o}(QC<5dt^|@r-h&{H;K%}p{L4w03`Eh z!t3`DFGFKr-wneijUZLu;c2ms@u`9|SbXRDz03UTFMip}oH-u{GJr<6G}h>56eoNM zpLQYH$umzbw+|2AQUWW*8DmuiDW@x85*_jAO+5>3Y`qME#ncO zCbN*eHHq93neaI!J!1H^GZw-e@J)r5vY^Y!#+rtxY1p z^EcHbYP`dA!etFDdr*zRbTgodZU*sIM{UnFDbIyoXo5)gw_|#Z-^$OHf35k?;BFX7 zteo`88tdmf=-@-*k2V^)z7E{J{?2miew-bDJ#e1AT9=2n+`{PpHMK*=3d-6Yq`cL@)og_8U)|FQ=0cTHs0 zu)#7_W!CC$Plm@K)~s~jzm^5X+Hj#(#E798*<*kX3kq8cj4VJHxUNvM8xP_|Sty1( z>RgWW(h%Tf6Zkk5p9PxwNzV(Onsu#g4lpI`o3S57eS_$Yks7=Mhq*rXj1wcV4E#c_ zOcKf)`qg>E>?>)TcC7^M-YeiH7u4??*t;aitw0TUG}nfdR7uh^N@l?56UHq0aq|}A zgjQNz#*F)CLoizKG6^42*IR5;TxaZVC9^)l=QW_et{)ys6;=t0Lx1}Ak&=dF#FxL_ zocfV#*GV!Mx(|G2EFxExf?}(xpE8LU0~x`C$F*sgNuIgRnB&YLQ`r0xyU~XPto?Ek z;8ZWPq>Y#(-B%8=M^HAWA1E`eKx5#KE?Xh;v-U|WK=OpYFhkEcz#FIHJCdMyBo#qw zB|97l@?*{J82m&cRKw;xJtT7ucj_vu7)4KFp75Um++4iP;bfG}J*W}S#2oAf zHPVJZbSqD=mhV&K)Z%{Cl=7%(X=15KzjKoN<&I4ICIW%cb*N_L%_A44%r46gP@&Q> zHnzdzq_~xHjDA!+$GRH9a^~}jQ6s#UYWt`VBd7ME1s#)Dl6Dj;A0hEJz#|ylT>*Jm zVjOv*t4D|cm?+3LSMojSn`PD(uT(%6h1@x_6f#vo7xP#ZP^!XRc5tGJcTKj2z2^YbI1@WGZ=3ruh4bmQB6;6#eNlGm|!I%hvmjH8xZ_d^bz&m25G5|0E2? zukI_RGID9R+6MZlUB9|rnk3G~tON-??UJeo=Bc362b}6b=N-2oIwDmHo6MVCy{kY$ zOh#+#uK#dtjvd`?WAd?kH1;!N>(sDjH)#-jxC!H?83ANZD+UQAX(Q{HZClfoDzGDp zQzS}@PhDbIaz*d54+W&xaQ#IR(tYs$K(r3n>A6NUb_{^wDu$7hQk0>$-9mg+PqFWw zkl55`NrJ#bmNECJZWgVT?0!05YIEsB28Ak#o(+owHoH?b9xlXaDP)=7xKoXEBT^St z4o4m+TvT8%6bj6mnni~^9b@gMh-lZuc1nQok$W6EJ1ECXD^{!1sRTRvMIXVAyO4Nb zWaV{zTk*!xyrk-}i#&MtMWc08PdK_NU#cO{c1y>FCzV5m^JT#^4iZI%F@7TlPJIzZ zS#|ufX3|+T9JE8oyoKWcLgTXvlTKQIlLGqQcm#@|vrM~XQL>{8F-9Z_$pmmF{h-D} zdY6sxZtE%Ec*o;L{hMK^L%8ZN=E?a`n9it21D31|x#6ATf9sy{@SnslOw1NFEF`Wt zMb8vwu6Gzd-7Nzy+xY0Qe|u5yzkn{Du~rG+{eUSV!NU3f6f)4@6U+W5%8Sj~%E>7-S}j2Hqa(iF{y@iP(wAq#tYZ4%+I>qLmooR{U!U0$LtDH?Im{(!&wghq+R%dcdWBMgKvlD1zuS1 z#WX48(PJVjha>x7{R3)l%F}Rp4)dXx_`TcL04v~w$D8_!1<`%Tek1{ZtV+LF&z{JJ z{M^~o4*7|l7`APs;p;i|xz@;#^s-J9*%SjKU=tvFH9AB1TN{bvqHJsT>9wgUNEq|ZGu3j>FyhfmLnLQw>8q@WH1HZe7w zbPp3~L}5{}MbR#Ho`D8AwS)oq=>|XM-Q5(mooD-rNm#99+_=_iQ2c{Yd9%j#ny1x+_(@~$R0F@aLAa{?ZtS)D^4HnU4oDSMiJ*U|sK zy#ShOWH%FVMfQAR)6v_X(USCwu_buzBmf^b-MOtlV4EHx>;@@}sgvf;7S)I{_~N7v z69@df(}+rF1b{%tGX%y3N#NAhz0s^B#qb}$GY|Ve0Ixt$ztQ$exHp2~mF!`*mBehz zY!3fKwA&0 zhT(T-#~Du2!z%DOogkHz4p0r+Nd$gQO?cgV0Id0UtBkUyN@6sw0jBdBpqvR|!H&NN zY+PLrhkWd_N=}0idLe_a`hW|Q?^c>SB3(Bm+oZrBzHHyy>(OuLK=mdYrm6|iKyr_! zR`6ITi^ysz5DKG7KwU?O9Uu2x!hrp;34HT7j*A8XA3^t=+F{~ko%7rTr|1sOoO1YU zS>X5^j&_`oG$C#Do7AXE-y?yUGQ@m8RSP)H01c@=Ent;sRS+91qjN^mUJ_+FE2h#8 zs@B@Fzfl%e$xb&Z@P~_kasH0@H@YzhmLj5jiXy6z8OIV;}gYt}@@mu*%Jjix$U7QCb&9 zce;ruy5j_Qqtp8%l-``79soM=en-es9bj=@Dq|5%b!e7Ap-dcMX8WQ0?D-lgFmv0h zTmIwyfkjx?%_ar@TQ$FBMZB9$t8MED8m(pRNA!etA_y@vk;AtT+ zK-Uu7ydOrNxM!+%d~e993<4hA1Q9ZJ&W~Q3!_$d_d9KdFEV6X5t^!h<_%=apK?o7q%P(f)qF#3yB~xFAHI+Z3+GjGn1mGCf1rq zQzRa|9#25|WW(9&)QefXGKoqOByYKDb{r-JCPCuaB22NOm;gVgsWbuh2%g`ap}y~R z$F(F7V}MZ0SNQD%6QoLrl}R)`XapV?zAssa*IbRw6#S(?tm}rdO$z*p;-5VE(?9=E zi|eFt*^vBjaN?i149Q>V9=SU?$Pf!YtJrI)T~*+pC)5HqTfeT7MxOO32D`tL2T@f) zoLTSAjvGsX!k~5Pc_v{BD5gvx-Eo=bTD8k{WbU#5T2wGv>Ihji*njuAN_8n0Y3Ap z_TkbcL-M&;D3CI(!t1bdjBdogFXxtkJ}s}X?%Ytb*e(qeP*ekhf~hL*m9yizR|~>a zf|lVOa`aJA)i211z{?L*Zl)%JA-G2CS@)O@`(BX2!${estS+-r40Ter^sxWN|{Lb z;+d?)KMVp%@0?ODAxxuDAcOZlqXGT1*MRV0cie~psx>KFD1GUt{z(8R}#JhfqkQ_h{w?Wy;TMNoCSSvuA!A>OXt-8Xr;O?f(^|6H{j<0up9rD74!iF zmJS*1kuc{v;OH46v5sAh3P^!z2chcD;OQ#px(eo$4np7{ljbzJm*q-ke~D8N$=&lT zR?!9e*IeYi(z?j=LaTj~vPs)>nyECZOJBGYe+dG!q`d~R?vBHIfv+oy_5#(4e~$j$ zy8%F(3~BiwNP&7$Tygt=WQl$&4(?R4vsBhDWsGbM!ard_-&=b!!B=HQ|6ps$1T0AF z+FdP2n}4^v1L-p_Z2qni!p@u)U&mV(w?en!N+J!VToYOGjowqVY+ta7{-u9BUKByC|6sscBB z`0gvsPy0b5|D;~!UujDqNn3phijK?fS^z9Nd;bnqAC&~SYyQE7z?BVWKNb=#s&=r7 z)U6?QhI+gEk@r;`kw%b@y&N=bk-*1!Zh=z-qa-b06`O;v)B@+IF$>WKd5X zWeJ*NSp|WC>>`T220f#!mI3M&jk|>FM=SnWbQ8@6XuiOuK=XU1V3xS(w_9nW67tSH z3k-O|IPRODZJznxeG$y`iirf{6f2@Cu3Iv3IMr)yTSm7^*3QcQ{3)Px+WivrO@+8R}G@53}pR zp?Di>RE$!Uy%h&%<{FGIZQC8!GC;M)x+T>^@h@xwfO|w6HTZJx#K3KFV1C2{?O4ZR z^oa@c=vz`C$I2kKs=)8OpzjS$b}_>#*vsOdIV_#t{DX=x0Ia3I=I@3Uh8+fIR&APY z$V4ItS`{c&(1yX*DQnIlVShc>Jn;OHr>*c6jyT2Zad4ExX@MBZQV^LaLjM>_PDj2C zipN-Y9F1Zbz6^mPp1KBYcbsE@*W-MZ`kh3tp0N2)J(jJ~&nvXubTy|w&3JiNC((a3R z7;g)VtG53Wv?OMk!_hF|1}G!U)Pk~XlLEit3Wnm}XiK!`bWQ1t9^?;(3=p!wuJF0e zew1BR{MKrKX#;>n>RG!_6aTn4n6hy9tQ`_Q>&bC5QaYLIG;rKs3*Mox6GLU^6Nkhn6@@2s0_R&~c22B=q+D1-Q?dT7>0zaW0F`rFH| ziISKQ>5fbE?9dqq6JCgeSu9<83OC@1(oG8dW-DAay`n-^dKFz$`Z5YnMgSau&N=)sNc^todlTSz|Hwvku52ma zom~L@ZsomK6>|}o6vzz%YrDNXd|ye1w=+)B_U1m{?0VFIhl))K{5C7bv?*K)Or*Ct zhv8|%B<;VS&)*gR?Ja#@xa}RnmM?F+u^mDk1sOWm0N_b^4!fxHfQ&&d#HK-he;JNt z{St;WC{@zWMVSCm^yU;Nc+MIYqoHSSJ1BVDyHC-`Y+` zf1@g1oSV{@et}EV1YYh!1AdSKi^^Y!6gVu61_ajHcORHIB=n{2QIdpy*0nX~*}LTc6mlE?Sce3I zde6`2^9fclpTlfOru2#lYUZu`aSEaELz*w+ETP!*66lto<>JM9N3w1%|fUdO#M7*7bS;-82? zc==hqgXb5dMLO9KfwxZ0_gyW=!O3II7}#hIW{VYEVEfq~dg>^M&LyZ`?aKRrQ@ z5zka@Qe|$d_AZ;d+r_Av>FMdNPseKZH(SaBG*Nl!_Qb`$0RZbxtD!Xz#vP$a(;6?P zrYRN!pqvA0cl4cF4gUMF|BwplWyDwLz%VX}5&i_$@xtOfzZ1Bo=+}P9p+15sC8{nV z>KEm1+^hT&Rd8v}W^Bw{Pe@{7f{`h?;KVEnsqZ+S(z+>0#QG1LVTgdg%k9dq+s)K>B&ecA6eZDC2w@EPIykdN`?HpO5*v8;Mhfxq}Fjwd?zd!4j%xCgYPF9^au z1As&8D<9?-MKeJN?W|M^-U=@UK&!qJ72wBLmVc`JOss@vvX@(76%gqG&4IF4JaRSJ z5eJ$;HHB^VQoh8;zwPjAFZxTh`qF7AI~#D!%-VG1owketaOyiYS|_U-kpm!qebtzN zx)ygCSdoA>7bpiI_P>joHc1u(Ab%`J`{0~56OLCyJiTqtlNV1a@RwdycWSZ~@MpX? zV$;{Qj$oexz_x8m59I0y{IRxiBgv9BSqy-Kz9suIzb{@0itV`1&%b%fRIu>!;~$xu z2)8RT8TYigEfVEmeSq^}5cQR78=d4MmPl2Kse0MTZ zJh$V1WB1#eUM=W;$M(k&0go$LXjdw{66c_FuVBlVT`hVOn&(H?7WbW-^EXUIQG8}p{VM22z6 zi+fH%NsOQ*!Dx2aHekMK3`ihUhk++KpUmqk4M;%41zH2J>uy+zd&geISn%#QSqXsA zIX9~6d1Rh6;0gB`pOTngc+nGE4}Y$Bwn-CYL9{K<0HZEQ`Gz+Lj>%Q zf13bMy`O9yy|E9PJ1Gld&%wb;(%3eKGSC{hTI)B zjJ+e5XP!Powb#JLNhy6!cG54#BEGV12-rJlLA!zy0PXi!H6@9$$Ui&`#GDk8^#Ev% zuiRR7NzMyo!1I*E{E7=HhH4G~W^;Q$X+Oyy`;VCbDBz6wEim@w4xJT6TbTA|0|4qi z7$Js4T$~!$SW9uy2=!@6OwAAq9RqU<|86_&@+Aqti#rivLrr0N;6(kSMg9S581pNc z4p4Fjlo@Q4#p;xQH})L^zoQREV1SBK0V+i&ay#JRqgPa*w*!oY8s1Bx+X8^P59YP1 zCy!nf`3!8wJtZ+ezxyrpk(dU+xmdu@Jyl2lb3YpZc_AREKn%6O`b0OYQT?f14n?yuw8e(CU8TX!grjH271Jp2% zNrH&b3^LS(DiGR^JFwzVE@C_8y#^@gn|<~|HN`9|iArM1+s(H?z)hILz6k)Oih&-Q zVz#M8aDnmkPf5%Vzp~!wY#IP(R|F+%JK0*I{Sp8aR3Hk6AQ-3s6RZ8%2!Q;p-$}|| z%!4ho8+0*`H3JdEnhsRNnaIsdrr*5}qThE}4_-o}EH)5+n&}fw?@5z?kW<20PnUrV zbwSyOTdYD%Qs(Plc?<7>20-Q#8Vt@v58i`PC>o@&Zv{YH(XNApTzFB=OV~3V4o@oZ z$6jTD*+IwrO#t9*5RiGiOr{e(mI0uo0^JG_LmVJ=*q?0xD01VN&U~{H)Q1T5Pi-E! z?C6UuCMIIJN4Dc`%JW(%qc0b|>Gf_O7Gvs{`YrBIUmU>@r`(W#P=Ii#3KUsPxU%!k zzM~ID75_BZp$5$K?G|`I!CF&+7+^o_EA_~~ZvjA&o4_L0*Yl}BKb}6zE6R`9Qxfwt zFJ)dKvl+7q0GwS4$j81^bHHN16aYnS#~cpOe*G%|EWGw-3jmhE5 zG>rbK(iaVcv_Sb70M74(ctK@W;!yE18vvPA3M5SxOo8!G0oFeIvmO9ZN^3KuwXw=X zs7r02c1>M@s`4JxG@VgX4AYQIZ2n(!YrH71ik(3KY#{&?!{RK1roawmZ?;%*N{vT@ z8&kZA3Pi&_!2FT$#Ni46Sx^HG+i}h(Z%O2F-PXx>1K^OIs$Ye80|X8^FF9t^95M8i z#QZe!ud<9ed7+--Gzx(8$-nea53-NX{fY`uoPs(*0{~*D1*T#9vlakljZ#_?MvYZN zgt{#Lj8VcGRFz|9x3&Zi)YW7l_o|Qgepsta{oGKH0h$5^@FfXnzYqjgYU70P7R{2r zl(fL#Kn03>U`by}>b(ZSA&Auhq@msEVEqhtzuN$?Vzo7`LL9Htazh`i4R|t=Qu~Od z)l(Alqq<+>&jIN%-%kG7im&}r0PIf#ZMwFsg1^!Zdj8h|AnTIa8&C?-o?H+%5$Z(P z1d8_Ds0VZ)P)|d|N}L)B%Z1_h%pNcD->+2!zPy_243&R*Xw?ZEKG9zD z?$?vP1YzyV`{2I&btLEZYanQWnf2i}O&RHcs}cBH4S>8}_hj0l4^FpGfnqzZ3__quIOWGb!_gC3Eo&XiM>VH&( z`s4GVZ2}{yAwV$^9DUORj@1-;8iFs{C4nOrhrr8#U-e0NbXRW^k(hEV1S0)lDo{kT zGk_VP71{D0tct@Sl)j_~>mGPQXm21=I7V5RKzncB1%R?%2cD9ReQ4`D~^*?i8qzC7<`QLsS05YcvOcIDC?~=6uD1h(nt^&_rUf)i~uDJu5+DKy+ zZ-XDb1}67XL&zBU2Rg$dI!*;D&=U*7{RuGwX>o4Kf~xT%?q1{YcNbP>8CdM8A~5ql z)|65(j|msWdPz*l90tJNx*6_)sQ_6D10hvZptJjtM9IGm0GK)~h%*#9tcSo1GyZ{r zTQ}*G3jFudm&kjlIub@xysG~x#f#K^k{(#R`S|?$@>4^iUV$jQ2VQMCP6b}gcCe%^vJ*1J%FdcET94h3@k>w-<1H!Yt=f9ZnaO8?IVH-e)~vl zJMKvZ{%i6tI*}JsV8RMe3PBb>_OlDXehTcaht}IFz>>d$PkqIAKOOtr5>yT?TKDqj zREC0BGYugmNDYw_638n7@nT{$8b#I=V-T2I{?Jz=iOvvX`p67MS$QE)ode;^`SpH3 z^^tO8@aOiyIfp-WI)!?-1kUN2S0REK0Ph|^t8if*$ZRUGnufpS04VGA(M9o^vbd5x zN-tG~_JN6I$2}!6|C0PGqi00QVAk|Mg7gU<`;Yy~3XpB808!C&hT{{!*ZJ0Th|$CF zr4kJD7U#57&$^cy(#xYsa9TVD0IDet2ZlzeK%ivriSd4JUaA8ClGz3zCs9;N8g-h;%qXT5oR#+KjfqQRT0WgJL+AtNkU^`CSTIYzt@$`B1 z?1TTs?4umt%kG8HOQ^sI@rCyL{kL#{Y(@omSHLGJzBWmhl0MDLpSKj414^v2Q$u!t zVXM?p;tTEpBQ-^KGpV?FBsmE--$eYsI+I<`NLtXK31jd%UG^N%dHK-Qp+2Ag&)$_S zHmWN@TZe$zELJaZyd}Ar|Nq0Q76`PogupX-f~U#EIg=x#Qg?M%b!nvll{a2CnNRi$ zPkXp$zH{tYtXfjfPg?R|besyye)S_}sN+N?wgS?xlYhib5lKoAOa5bC{zgDGw*n#n zP*(-;2$1Ry_&5MU#OE`bn5Ssi;XUpS6*2RlJ)G!aYDhR*rK)X#{Re$BuZfr<><*WG zauUNTYQ}XzN{Lqzkh5@cQXMx^ff>qJCCJT^`j3$dj=SAGwdTIcn{WN#uX*1c+Q|%j z$iv&pd*&w(S$*{F75WTI~=@6X^~>TpQZv; z@=uSiwK*@a{%cFjZ)U%O{Bt3oss-lvnKS>%?TGStdnE;?v#Y=^aFfVCN1O&NF%-cn zP6Z|``xREHiP9G6Nqm{HW#Fg>50jJ5S7~xmIC=#t=9>eWMcGsy%(CCU1qy}00;TNp zDJl@kO6lx1XimFR6F4903xLPN?tLpLhKXprNLjG>PFd7{DrX3r(YHL_S#VJ}@JuaV ziN#-AVt(r(W9OeE2-M9G*l`K$EiojpZ~M=;x4*;zDoL;{0&IIFuOkS_QFVgySXyE_ ztO9i$*aC)XTVOCN`<>2%1r_)K9*xN4q|n4F`F#Rbg^m&!&4_y+Wa>Po;VN`cfuvZt z1_g&Li~&@_!f616c&?RIH;&>i+a{OYq2`YIcjU@jNCn14HX-t0cUEKGJ#j!*bo%$? zUqC{$6=E6yMZ$z${;q9L9t!}FD#I>$1V}w2z&}so#yH}YqMYd~pQ&VeG`+hTvxu*R()-W`GwY(!en4W zF97U`4Fm#XC!(kU;M837DJ2LU&xa)f{KK$FRAh>I|*m$#C z;_|e~NhVgAWv!Uvu?Uu;p%57LiW99R2EPgu@1@1ppnTsQDz_+P0st9?-UBmiN3X(Q z&w|BEA?fR?IaHt}y(UHGS%S~1Dl=9*$%~Z={FkjS=naFqWajKZVRb-*3WVbj*tfk} z0b$Ve`IrfSbRGxz2VnNc0(J9Jv<7X70V`MNr2n} z!;{m9JiQyCsS*KFFD1yI?w|U>@}GQGe}=UsM*LUgw!oO%W_>Ron_<$ZJ%A5gfzsrp zety#Vd>e})pft#kn~bW)OCf;Q#<~%VePB_?>0KTy*o52l)!A_@DHSa-SPtv}z~;2G z25?}-uSWOfoqgK$m}F7^C72bqBRw+SX{<#ZSF9~D|Kf=Q;*`dR#upT=0}?rXf^FZ0 zgqD*p1%Mory1#sS_5oV8z|pb- zf9X$KR1z@sIH{RX;%tF={74(;>7N2qg#(n$@~ImD-yWYzTt%jvBG9KqTVh0d&D#Q@ z9?btopO2Y_(TI+^D{RCDOdzSrPs1AL@AF7?dr{qYpXoSm4v*UmYT=U>x zApfw-UyT7`T)M;aA0U0yU?QVnCP!OhI==&k zs(Atv)4RUtB>aMM$P0O3hu3DITC8&-=s?>6iEwos_dBqWf7APbWdki@l$G-f&`=76 zFXq8)!A~gbYXLy`eC;7Tg!r0dH86dySxdnMseVnES{U5&^4W3y5hE)E{=L?hBJ!2S z8$@{CVv+!WKtvUwU;bpQeT+%*V}>EB)en$PM}R{f4Ec|{F>op?J0(MkpWE%?hy&!r4O*kB&0 z3gOnH{k}{m)p1X$_AyQWIoe>#0O=0&0Kg7=N!pYRk|DL;sZNTu#1!DmUyOax{q*1` zticOuAZ5R7>QcvnS+t5p@yEwUtYDQDo-k6s_*m`H7N36_ND)4WRf4SoKHbDbg2FGd# z3P>N4(`>>WUY{qXDrmB!25dS{agbMC2gV9rA$|6sd#rNEO_VE+gZFlk%#sihjz zowm)vUjUUKl}xMmh8q+oVtO!JzVAnmybuO~HQ$`jr%TmshSDZ}^|Rw{x3e-kPBqF3 zgFO`MVLCbO8|ec=<6z*EM{CB}OVc(J~y7`TI;CPB}SUg4Xhp)K^=Gg0w_AR#K5-iNFOl2-xoaqKEIM~gxup3 zX2?C*m*AQ2oeu^B`A34Z_K7K^*>TBL9-|~H}_2E)uu5)A4%GzG4G$v^(`>gu3%{$0n8%4>v0fzwprI5QI{;qs+C zILD9JN(C-{;$ZZ0HjRHR+unBhhgmn+N}Ugpu)qqxk>y`z;v)FEK?e_*>Q1)e0)fp`qlueVtueL$12x8NVdn5W!C2D z2-pXT6JuCRLU8AWSrCYUNr{WqiWa}ig^*^IKp z+-S9e|4JTgJ7OypxQP5SdJ_m@e{MlLTpHgVTT?_I7yB%kTL&aID$u72ydVM;N|cSOSt~y+Sb#KuaJ3HpgPu( z*`q@DxfTAZgL~fkF=4Ugu}aPDm8a=zR3J7oo&Xhi-)bo^=-#|b_*uhHtX3)M{K167m|O*LYiES%@4~QE0(YeuEL}{@sj+< zGARi!f^3n&iF76aaw;%C?nVcsNGkbxvPmpOV}}oMi7ER02S~rS$LH5Z^30z$Nhrz^ zoeq)*dp8x>&5q*^30xy`y~z?0vd>CXX@FdXd>@4E1pGlV+|o2_5XA`}fjj#uR0As1 z5oe?V8{G8D$teQ)r@nFlf4BJK8p@gQ)l>(wr$&sjNLHi9U)EWmerYT_Vq5%(t?IZr ztuFzwtT)H1P_6ws>cYxME0~WRTBJKrX0}j)4hVeuC_(6QC5ZVztXE+7JiYtW6MNFf zXo(S*Kjm(z-%kd7gJ*s+ZIVD>gb=X_gv+vU#1!4^I5@na-~>e|m}DtSQ&CgDjgD2_ zKB!MSpXbmf+$al~@yHVHWi2tFq_wWWV3gI^Ot0*`s%(12A3^?|Kce$_bNkOq1&%Oi z7-a=zX;YPVG|M6gbRV&m3iQo>1Bcoj_UNAYz@HW!Mrqc)LR=k}kTjiN2SoB@KNzwg)8-wkS>_yyGgIW&m7HsyR@BH3vYO=@s7k z0_0z+yy_wQ1>PVLUk!1fXo-mw94s#KKbo_!Ja={m)=OGqh&W=2<%q2U;f3U%1*=5o zpGS90obB^OlExzkDk=7}^B-E*j~N``ixHsYfTV8nNZj+zmY4#zJuzEiG_2w-F+v;C zn8?x9WK}ug%jPQ&ZkLP0wl>{duy^{yLMOkW* zz=5WIG|iGB4WJ?R???dvJRIX)z#SK&go9ThO=0rSeFI((hwBFNuar%%2HzWYGvHo7 zd6D4PipaTLrULs$S&{`=?)tBc;#(v!YmeA(&-?`rm98QRWFEXao<=nHJa6*yFa3|) zlm`zNsZ9kQk2hcRY(#+bRp91)fC2FDc``vkyBm)sKUfk2#3+$EU!go$m4O$VBxHQ$ z91$5?(z%$TQ_)}2S3?96Vj|A9`5u5x{X`yI=r(>K`tM4i2v8tksY4ZjvJd6KJ0KbbAz}QS+FNyJX2bRRgVytT9!Jv->g|c7Zhyiz26#wSI zL8K~TvXUr8A`f2KB7x*NC5!I3AzUmOD2Yh`ZNg-CwWz=^s~cQU2MASxr8NM=zfYt6 zISztgf6t!*NleK&wC!W4ON@vScwe^gnj|D*5$ou)^&^C6TF**i@LdNn9V)f9!-e-LFJ+1)MEdD zO0c{n#;Wk;e($=Akq2YPA&gnGUVW&jK+cvWG3toHr>}JHEToS3uR9J3)N~AumLYKX z2omQD@iZ!P%_#<~@gPr;<#?V7jQlE^l?VSlIu_8;3q0cl5eeznCz$;s8aU;ud2ZKQ8K@=4r#Rax1o4*}5>sz7CCm}W8eLCQBz5*2Y3E04MQaUmBcu^$}lD^ zAfN$d2xs|RT?ywCl^%A-0o*#9r2?}W+V++^?r+}>4yXbb@JtXXllTlsUrgVd>4GvU zevT+6J&%E%j8VpK_6qEOSQ2C8!Kz6DO62U|HwA-I9i6QuzFaD>o|D9geNZAiObw2@ zxu$;RJ|GMt|MbZ-w9#js5bGE)GeZ@q%$$Oq7X}F2!~odOfX-&Wsf^bJ0LC$EvQXb= zY;1#`1hlxo3wAPK+SF{n5dfF-VDpHqq--W0^x$}#f1#W$l3j7plYdyySS`i?B4UvDZqX`N-E-0ts=QPBudr1t< zO#w(F_$x>kRG4%hVJm5rK{S_Ufj7 zmdJg_{p^?Z|C3JecdYeGL%7%&Um=C7V&SVW=KR5DfJ4s!_uH#SM+Ja!!RvS|{5#>G zaT8wJ_R=L?pb8>BFX9c8J8dffN(kh^3ch>9;4q}09p{`rRMKDE(+?6MTJSe&c*s^sJSO*Y4Rf7f&2-`|x(?!gt&fFuU9Tpg-$V$vY^ za1-I8Y6@o`oLu;;dfYO#fm#qqZ&Qlwget&1rX*&dIuy@qiB~)WkPf92s`daF$7#)@ zK;6si2-EHiQ(Wg3PWBD&x7v09oT=a!Y+WiVXUF-$M-u;1fr@`QovQTv2UD;hEGJ@e^OUKRU%dvq z1VCgwkCTr{3H>hOz_$-vJ~=W&L^=NHXSF^}a(3n==^G?mx>hN1^ULRi^08s3nW{<)4_l6{<#h0#gmC(cA zaWl5zdISJ_4Lv0PSvx!qrGV*1GFU&J3Y2|0HdMvMg9Nym9myR@jQ7F}1R{h8hX&^C zD0ilY&|1E*!5#OdEubm_^g5z8dL&MO{JWaupWMrl!TMb8cYq3XxWK(7i5Zf?8;8cr z!IBt}k;MW4-*&15^7f%Rv6T4fl{QWeC|;PTK->{EIY||TWv{FbP*^bY-;Jejv6((o z4Z|$8^vT^AG31WhVdm^6@d9~zb{x(cC$17N7!PFvzyK9!b%nl!GXh%IZ2uep%B#?s z8qz1ms~crO&s8Zuel`_YRHt;?XGl&JKul^*I7*<9(D)M8?Cc&7qXwFBFVkpbs-4J0 z1+Ij^tLg?9i~#v9K>kVWtZh^?8K0!>Ij{#?03zy) z{Huz5*Y=`q`#GIViHGTVeW(d!Dsr;MnXKQVXReq z>UCK^bDP0lVEBfEk8d-c^YE>U=Zln0>WMBb_T zpdyPDBu_pAHI;9;5bQ^50ib}uhC@b3R~3kzL?E-^?->A89^y#X$4pW%c?tk`BWu_k{*6uhdiJ|8)`g^T-z>r1ZzJewFcpJL|e&5n5U!VPY?zmnRt+as(|6VXq zUK}~Q#`66VgXsg*Fk=P`?E}zpBGX=V6-LZvhS8G{nDun1M+uBoU_(pQq=bMIfw86n z@2)PiY}_$)PXM6AYZbP8WnuIT0E%at{lGjJrKc)>{F6Z{5a)TBvUMS~@{%A=V+O>Q z0{e;*m?6B03Ji2;4@zJ5KBqTc^N9-k?j!V>#Xo}KhL|^5`xMxBYuJ1W8^e7Ciy|T) zGWPi1C2~MW7^t6hdl>{V8vok+ow()2%nkt+*{8_Ab9yrqVxUz54(tP2W{yFcr36G* z&)x}Xon}x`Xo>(w-k3KbdOc>qKd3TXbv(wF8` z7yP*bkVsUVJ&K|{jbQoSx(HA%-yMfy9!ov!=0?HM`=3YF=bSD*{olnO7veD~nzL(MBgnMf zs>2Nap5 z10WKiD*Hj?A3If%EV1(Enq(K1DTNq(O9bAdp7bTC3bf12i7u&#zaR)q<`4MWLjYK) zk{>8+=_`JB+}bTbI%L|ST#hKNYrX{lU3c8ziGR@gHDd`=#8=u|zdQ&WpRulUC&@+i ziY6U)uR_JyodN(@gN*`jCKn7D#Y?MEmb_ibE@=IF6aoipAmFa_IKWX7lak=Vko4ud z05H;B9b;Ic-4E|-+Y2alJNx3+ z!(@`_WYRKN1PUnB>u(qgtN{Pi_y6i~c5uEX6aeu>(Wk=lj0sc&dw6Zr&>`_ZyD_2a zL+`-I0BscH<4pHT7juFuhZ%>zdjJp?Wa!k-+^>-dfk6N)=sdRM=Kx?kFXIkBM5I^u zKwzauA?)2njCVAuCbn}y|0}gRu3*+a5Xdd>qylpRaBKNt;k|-gb%0?&@x1>2PYD02 zQZYf-0U$iEFq+C0cB8Qx_}cYP;nQU4;b2Ql>OhsM29_I83WT#007cJe3G6pOpjG1N zA{kx{0L|jKCFl)ncSE4Da!0jT?fly10C|84+>7A#o~$wQ?@j>R+!D5t7<9uLyAlA4 zYMMDAKL!A+X_@UQ0zT5aAP`0&n76e9MA|G`{e}S1Xfrc!BLW%l`X_%RCK~|tJs%tX z*S-+8Dz3Dd@O2FAdH_@lqx;5PV3n?etBliBPmMbgQ^%>mmE~0EXS-)A&Tg!#&dm1S zHvOcV170dz5rl@?9Mt^t4?0l{Oq4(@UC?=Ao&BIk2@A2e_k zvTFcP+%PpEKL!9%-5UeDBJADdQ-RCmkG^asaA~x|slahLAsywL>YwW%Py?Wdc6HB} z?iFv0#5lXqT@_TnF+n!~pi;*ze#6{*EceVUZC_@3D3_!u zCJ2SmIKYxX8D4%v7+C?Iz6#R|226^<$^d;T@F;m=uLvgNG#vjn020~_e*AEJyFSYX zK-QcFqMv4beEbXO-USgy>lsAxA~@};78Tv`s1HCx`V7vh-DxFn!%ulCL+*HD2Q z0B$WmjOx}~VSXFy5q3}5(i;Q21pr0Z&zOgL)C358l-{E*!fj|HXdkevVB0H}|8(3l zw~w}kIG`Z-h+bcd=TVm9wevDMV zw*nv-WuXR1s1s7_`@j=LtMU8Ib2Oq zWD|q1Ynp-L1d{}Gcy5`X0&f98ztq|Bki4SR@BRhWS-2hmNiX-}~nLfiB8Av>h-1e|4}0fRt`6KTqXBeq$y$`U{*fuxkO3 zh}&Ip-UL_2@u3Sqpj;gtP|XEbtQ$2Tjg7=O?wN;Ixgr5f0^5zVQ{LQsI=x-a=Vzt< zJHIWH)|{r}V3P&6wf;Q;bzI%t&=u1+OGF)~-T^iU?b)qP+}iqbJpOF}YTLlW=r}@1gbXM z2ZF2P3hI~FDF_r{|DF$ke#;N5g}3@QO#a;lfDraugOXw%>e6Qz!>Zy-BmJjO1p=xj zddRS5B<90oxo66JkR8CsH|e3Sv8rInTtlBzCcGdGqSI*z6wP`mD(pp>_?h66}V=AJV*r^qyO3%(a!!5 z4e0pPTo?9Mj&elRaee1gfvc4|px}E^IK)?%qb2FbC>3b9XToHRD1%$u%o*QkC9#U7 z%7C3K@!ZjEKc?;T=GReynePB725M)39CMYCtuh%Lb-Mah0CltsGy z`bQv&8vDpm$C)MYR_dsQn3zS_pI2uO7D|MVPebkmGn8mv*!$`@J-%mQRV58HA@{3_ z?+?bDcaUQUsm4L@I(Xwq^qv^_@t;Fand5X2*l5K&TISkLmwoMRY1;jm>^c~|3h_8d zF#u*AJUlH(;)f)GBDUx}Wx?NZdYZE**?T?=dCMO~9h@3w+bTdNQq@M4zJ!PXdU*0r z>E1H2655M7vqey*Vt=YFUs~^T@x^V{duBa4-N5igS-JI(?4spUfer__P5<{!8^qxS zk@e>m0FGVW68E<0I+|3+?NngBB{bSVQ?q~0Gz=9LbH0WoV7a+M(jVm*{0fo~r44cX z<512{)WITM%Zh_mgctJBwljJaHknsppJo8ecW`;S41q~#I|-zD)vxs4I*<4f!r zziX8|Vk-bvpnF@NVS4y&sLd5CpP7_hb?NcSLL8@%8%yJEiGifM=o_BC{2AdQ?9nQz z3M^sGseeS+TV1qN9gG4Xlsd-LLm8Ta4QW6{3ETwR7Eag)@-*B#PS-#15q+uZR~~$6 zroKZVm0;_03AY87b5B#30rA;jg^2*@W2dw^S67`P=dvASOTphP6K~&Tm zkzdcpDpUxkG@#G~*|F-zupcdpk-h%uBvvs7&T;Zv*SX;L_m})>%e&m_*Bl4I&nZY0 zK^ZLF;}rP)L0tiD5A`(;`kKc15Xmi9!MOp3FUo3CS7Dpa02uFJdA97O&4@6~WttoS zoY#S-&=I?u{If5m5FKiCfr&-_b&n2zp6)FgcF8f@YAa9bGC?|?j?5c^AQO=kbl+qf~yG5i-1&El!dhyn;;b! z8ljIa$^ro7PMGg(kFhArS$h>F!pEoo{wrq|x*!z@ol(omiCVD5rKdCdMIXhwcfBtL{TK^E*@SJuAj6(EY>;}0SKvh;Rm#BpF?L@)j9+3oy1BD$$5+%NZR3O#` zHe(&AK%WC7Q&a;g0Pxob(efqi)e>IkS>4vkV#Ec;x>M;|b@*0|U;sn$Zi>&3?}FtY zchlhad};>toK}pnJf$RXb{>c^z$%8aAU?N@K_EV96AG{LTJq0%wLk?n!A=PLkjem| zlFmG0B46+Fb5)j2hc5Hjuf5-Te)#pzWX>FY2@MR$b4t7t4Y6bhf$A8+VNY|ar-g^oc&p;z&!HL06+mtrA|xT(o%%|GUw)LA$43Q%PJU!`B0F@ z#NV(0Vccilo)qedVi zO59o1fxbQEJPQ#A3K6wH{bN_=M0FBxP{%0%jJ5rarZ-NRr2$m{`0M@O_0{%v*aP&O z>)aC=P{--=nyGJFRKOn}pI?VU`UzKAz>3{E0&Q5*oHVeW7x2p$% z@C&9Toi!3;Uxg~Eywf|502LS*i%xNXqJ&18_t7JEOZ6K&Vq{uQ&8mAFe}wKWJH=(u zWh6%ZW3X5Gi?T5J64XC?i~{*bio^JfmAvcoVpL^Z`Y{o#{xPUPalCXgk?@ZDNH#$@ z28N@#5R+zTKs5kr2y87Xux;eh7w8h^UjDc`uGLhad=5In{rEcQ?r_Dvz9Xx$cH@rA zi+JhVCvs^_yp|wDN(@!`8WneX$F-A4|N^Zj7kEUHK$-buL<7Z$)Exa0Q8C-Zcu?`-U2uW0DoyJuwJo>aLJIv4B-o! z*x?VZjuQuJ{g@6)67v22OHRZTpWjL({2742b)WsS-i*0i;E@OjZDxHkMbfgDX!&ta zV;+JMt5}fy3nAeCuOW5Z7GAB{vAYujDRkhmtyr-PP$c$C>WJwA-9BPBRln#FYfh(W z9sO(Ohu6JT_mY2pPC<#UPRI^@T)w2+7L^M7>Kx+yDg*$fn+`t(Bmv^$gGP0q|9yHR zF_AGCtL#yWvW|Ho00RIwDp1CI5AL~(9a5ss+QX{EAuSCn{L)d_EcKVzK$UhVU?OZURj+57_kE(aNDs#x@ z?CDS*%>XC#lJX|m@gsHz{Ow1q=wVCkO8$*MVd>Q=TilWr4g}6AHlLCGDpC281Sw#F zV1_+}f6q0R0V)J6P<7n@$h)@oHgO;Ukt7t$FZGk`~QF8YJ>z58X*bqQ_WNB zFHI96jb_fAbA}bvrJhW98D;&vXIKYBP6A8rg%p;K68L>52f(oohAThIXkKv*l^N<+ zR}=_W#|7}VppzIqZePL|CK^S;Z{NEIz=A}7_u+fbQobn=bqsxAZIMC1Qw;puxDNsh zvy0#ktZ@Dc8Ui&q(73&FzP&uJ{MC&@b9Gf6w}t_>SGU{Dc*M3;zl$R#fqy~W1%G?` zm(uF@e*piMqNuL!!fn^kOohIq`fXyOj`5n7+4QQS3a{3N(>4lX zf0Og66o>J;7l(5EW|gLniwqf{{qmPX;19z7$D?5Fmj6MPr}bKHL0G^c(FAps$I*#v zO&upPz=})afDp|+5~9cV*G;$uE-HYEXMnky0XDV_&=(TUpUwasw-=THJ{ba8nc8r5 zT)CP7(sesKo-w}+(D}NF%BGnt?_a?53UqJrE&|wh?aQu{Il1dAUjrz%QXymv5G{aD z{JLW2@65(3vYtrR&nX~%$C;{*Ghl!&Auv#&z#p{bx2S+!10aZ?elWfbAmjn&sF0rA zDKRlUkWGMKfTO1ZIPlFL38Pu`+x;8*!i!MAFPCGYZ%;;tor4Jyfja?@AyaKUnOjPYA>3)z?-1J-}DAASJJ=IH2^}3_cs>)egoR4W`DE1 zxbvXI$m+Oi>H-5=+gyu&Nt{qGt8FvFJO8Fy0=J5uS{~a zgA!&sn8Z|48s7J6A$poIyf)n-x}0$a*3d1fFK)j4S!xdK<7&a!~&D+ z)hp`WMim6|EXvKnnwq=N)cvRsOPcx_n;HvSSDG&?fT{UVc4}_9Ck7>m@8vHWWw97# zmVmlsfN)OG>h}+X`SmCC$RpzJ_g(aehjW=OE$W zX(b4{y<&qBWFrScE``*sU()S4U|L`h4p%~m=?Ht4hqD43!o0}yJZHbm|j`&b&n(FUTDD?D<|Ds``7E<_PFki zZYJHdG}Efh)+GO4v8vb(1=2FLUMhLdYE{mw<)aj&dEul`VGhdgTBh`+=ja;5zIAZ$ zuL=eBCicHA0M=&%`1f<91*;tW^2@K!3z`KUoGCFh1w0zmabv8a86fk@Pj+|V7K229 zpmTDA8U3X;?}I3ZeSpyST-{*s&#~<#%Ijcz9c*&0s_2??qb!~Q!Yg;3dF15=;~xGL z-P``9b#K~cB9q!lia4m})rwge%_+9lKciXMQobZ(fCBux>TH-n{kcbO7iJ+JpcRstYRrcm(jjqBd##P1UwtjQhr2PaAlC`k5!VRoE|RRm%l9~%-8 z^7s~p(aU_t<_E~nDQdOovJXZS1UI*L@&iPZz|jv7H_9@#nTbo8TBo6D4S?*DO#u)w zy)si@eEb02sV|uX{$+nZ_y;L}vbNERO3C;egl~er@-23?HYjj%geHo7sh+DX4*tzZ z-^7MFEK9TbkW7xyWrLc}ghl8}iBXNReipNFN|zEC+zoK>uYYfp+V`T4R*#ov(>T|e zwQZ+&2gJeLsE(5yp+z5rGo!&M%X}GFoZE9KuQwEHlOwb`J7EriHb-bY1jh3AO7!== znBnRQ1=`l&%vkdf01c*Brl{(E#N5HZS@)I)VfPBUH%p33HsK3*AE zK-RbY4E_a@e2Mfb($^u>7af@rJ71m&0=Xqp{{&?bctU}64Q?tFNbmU)nGI_QfFOc0 zQD4roqjWq?R%g#AI%}yu;*^+UpNZbZxm;DpO*28D6Y}*PvFRZ+_}$IQVDn56DYrCS zg{%8Oo(X~)Wm#r|;B~Mv69n~PBajaGcEv*L8svNERi#~Pyr;Zx7WIgEfPXKidu#0M zZkS(0rc}S~G0zn>TX<$4Cy;EEFNRSyiY6`b~u*fC6W$x2g$R{ ztNvluqG}zi7-HIp&@?MBQePC^TmJda;<-q<-<$_F7_nj)yZ9*pQGrbzmkV=j7(G9l zq_TeSH!X+JEXXpwM68ry(grc3AnH^&+$f7rg^L{JA-WB6iI>+|=0%BdTLXXsuUB{R zj^9)`ckr*K6;M8bf8pOA{;FnZol;!z!=3VZjOQ>cQ*);r-Y~Qj4}r+A7D>Kzn&{8m z7OMfsPq1Mc>_;AVeyGm{Mvbzfgns4Dwf>pbKna2PY#b~AHYV`zkEDBp$QE4w(H{W? zs<|K-Wi9$uB{--83A*nBn>tR!D*lBK_TC=cG?fr+6%2EMF-X{>4kr3sV1v26VJ}Zdq1^f{X@%>oUF)~G&cn3`zBgJyyU*FSQ35-gy=tHS;- z(E!+)z`x&1%Urc zT$G*9KG4UG;*c<*H7N4J2xe{{#jB%NmD@E%r_3dIc^wv^TOFLWh4UH&;dI1oUkNvh z^S8~@)a{h#is{u0>E2vor5vkJ9uRBl!0t>E1@igI{ybM?Nl>OESZO7j*u5rvy80 zq1~UGL)Y%p(yWe?Vii57ti4DcY*5H!KKS6108L>?2gA1ZnxRqA?PcEwQI(nocWam( zyjMuuLwk)g4#P!fs8t?ZsyI0e_VZ#s%Ya^%- zZ4r#P5rP5=z{7WR4B!+NN(?Pe$`w$Y8wDpK`ai z@-<2fhXPF)pi%dBU7B9a3ExM=E8$d2(MEWwoX*@BuTu;&Jn!*0fTni{?r= zzGzUO8UrD*+lvl}SdimsKx@zz(`3rNm?&NYDaeSf##r95EuX z7_!wmxN2Tr_ht`&0;ecrboSaAfUj+Hqgn26s(+HKsEF;2@Zny2DKsDJR6NU?{YWEN zTx3d8iG@J87LoR1lGTA}rReT4tby4mOA3HS(<`99I+2)w3PJOc?r0r~dD^p1qLx&mP+*JJKXIC( zb#S$^2Eh^v9JByv*1esC?oBa$nFxR%2~YTVNc^QzV)Rm&=9x`?f<))s*;J?D8CA!H z@|`tuVpug?)#>i*>qeq&S0OL2wS5rK^Xsw?0J9@eq<8Sq8jysn_t9Q^^M2{isgY?z z^D2xIlh^h3A;QLVAP7BSsWL8@TBh} z{>2(0JOExkf={guj^$hsh5{R^UWCeV!qssjBs_6qm|Ygl4D-1#-gFfrH6V>BmFuR> z4DBnV2Bc9ncDXc9HR3}(=2aLZ9Mjv5b8_oJnj-j%pqO}7~$cSOt0ci;Gcjs zV5jt_ebt}=Fn;dQ>z|4i1G$G$7MCxTjcG@p*d7k*)I<%{A|Q>4B|4g&l-=U4th&;A z@)uCxsE!*0pwaXS(#1oFyIsXX1pFC)T>c~PYP%aZxuDI_A`1yi7-T#4ZnAN*{r^Al z0f8)DE*40BzQjH~?IumPtpvlp!<`vyx4^%DKbD4Uue%I}UV!R2S_^|!)t4R$Z2Fp3 zI4TfM+WoS<_EJOWUdXMVtY(O)uI86E#IE;Lkh*Z;F8)tkIX((7i3sdmF`MM*)rk@^bBm%9^6H`CjwN(=)?HtU~;7 zz1S=Jc)9kv@U7}cMWtH=>aXPC4{!#!s+qNzN>-a2<;N>KRL2bf$YZ8g8UPz1kSFiG ze@q|z_Qw&#{lOL(2Bl-$i+-0=u}8Awy79~!;xoUiwkuNv!n0F>-0A_5Tosw?09MBl z=h}h{+;Ro**~i29xgMRl0R$g~4YRJ9tGzj0*Jj7bWzjjKFWocp-iR~BP+7V+%EV7M zz1q~U=~Wu}XAMmpw*_!)_NxKV;nrJwhBU`0*y?{OS~PXr(XJj3e_ZTmdZ`Hlr(abZ zR2s-qa`a`d*@UVyhS7mhmIS~U`8WQQ3J_*CkLN()1buJ%^Zx$Znvr=cy=lNs2)4}w zz_Ki0b)3X1s+(Xv6^Jc?@Nx{W#z7SEg|a36FlSdaqoNyY7Gw*Se}`$u*hOHWBI$q$ z5nSGDOWatkI$2*1ps5u~?y2$I%;R0n%ubW;ZT}LwH%6AjpJjHwJJbIE0)YD9D~4t7 zzVa2>afX&24nFyp`QOO^A12P+5ZwO=wQO+9yNv8?j|w~=qNZ0C0LntKZ=%M}2JNbi znueC2_wX8Ix_s98tac=bs=v5+k4M<=?M) z04xg8cR}ih>xl49{85@R0cF)Q9kL@u>)V_nIDla@xD#2fGYykmQepg`##?&*r5Xd zJ%+yt0E?#QsETlbh6=QM-$@dD|KXw^eb<)&U`auB98zKBS|Gd`1j0lUff5PH7hYTj z(fvU{!*uzSu)DljuRGQ@cHvQjEwDns(sTpB@dRBIvJ^3dW-80C)pd5NB-CJ`y zFqn2d4sH zQEdBdI16L`tf|1a-*2!IME(R-F1gII}7u9~>cLfcjtUo0{ACiSY(h9);avw##sK)qV73^fSW+g9M!yAW*?H#8>x% z*q*eh_WSV`7~W!wYGBj~Hr@im&tO^iD_&0bR@mnsS_M%4l>&b;2%LK@53n<#q2Z4hCgM3ch|kKpRaps4=0xf?nHx^z#rrScs!Ja5nY@Q&3gFj>{kwc zW5Fl{-B6$Xl>JI%^I)7J5Jm$P*m^V&ZsAF)Cs2Vt0D4ql>0WgFd3$^R?fp$8dbWKY z0FLT7*`m1wz3GExefE8i%^YNM>Q}NhOoG9n?WMBqUoi3JP{OT*CVOywL&5{y?3s(T z!u^E7qf4d9>Qd!jh-2SElpyQ*;lsc}2JWs=~ z%WWUJ?)SU>D)F{B?ml?Jr@4{`uhI7%{PM-BK;}n1zK|yGP(2J z`8_}0dW=rJ?!tsY>&L2Kd4($}vc=N-UzrTeubLi8UFvtM&MZVZoZ+L|*TFDa{cjJ~z^W0}72#x`8Xtr=%94N-gx5#n zQAi5z=e%?-`4>i~p2LJe`EePNISS{>r*H#2a^?rj+NG z$k6=x)N|fnFW0WlnAi+oc@dw6XwkC%2X~6}0p&}ZlGhbZfIt2B+%FqJBkH)=Rr|U= zIBbCp6*#HmV43isd-KV^sRb?;o7Tw&O1|v&V|M{8FwC`ZIR>cTFd6MqXGUUB2VeS* zl<>j=2&)Nr#o5{8dl*oD)BySCuKTn7+ZMdQYSN~Kx7e~i7}5AMK*1JEC;zsGiTvCC zeDY7}-X1)hg16I#Ph%%84;<5sVMdl_$Cbae4+jHTyP*Fel<9$2P`)G|5u0>NbK$X; z!yFmM{y=ry!v5!RAFKhe4KvlZeGPWN)?^G1$UmC{%n9xBTD0LG+rnwXW*rOwD2u*9 z7bk&mFiIt}(yur-$XlAf4o2zMKnYtk?KxojtR;T3reWMHX={0DjEgKUiH;+)*v!Lxs{7y@#!+dACH{Y)lFX$7u_gX@r$Y}O(|39}4KwOJ%~2>(bu!l? z@ba<^G7|e>+VVMOcG_aeOgKg_rh8*4x;NwBl+4V|1E4~{$-5}Em~cj4;s+mZXoe~Z z6Yrh_lrI_YqIvimArKSC0xM>erD}tm!(TZecK?0mw7{)T1wwUT8;3syfPZfz#gJ3B zBf57uiSD#J15Rj#(0o-@lCMD)f(N9EN<&=xr}$_vuk+S{7h;8`@jZMTGf2MxQdc#W?hXC)@~=6a(ySe*Y&?_GMY{w5 zluIYmyJD{UouvXJ+1VWnC>YQvSh-i0BylN`DlkD+&I>^QgWHV$M?M2h`b4Qf8vxs& z5Y$v)*ade&Y`2d9P{PuGX8N+oNSO_o-sS_!jvLHTb$sS81A2xN1l&?X1JL-e0KB{i zT>6AVpOZR{PZ6m`)ucf0cnGFprcbNaLC4#)<<;3#)7(0kVF27dIKBLHb#Fi3^h)9% zPnP^AhZlAYza$ywnsP0zrnzfh;{X6407*naR7j749`-5Qr}aMs)itm_2xydrTUI1m z#86O5-1SiZL+%vA3>D~Dz~ABk*ai)7Yf*tsa0?tK|11EMjVaQq8z^fBxPFgZ)`Qt` z+8)0g1B7MW^mRvgT@X4fNcy}K9Jo;d_7{)^yrqr{@?F&n3a4Q#&tJW~_K>4EjxDdQ zN}_~S!a&9xW}OxlfnUt@YEw1sA@Oifr*M~)-ZG@L98v&qcQRZ}0=KR*(d#T%;+lb( zSR^2QB0;dBQLyS~$Xx2CIkan8On}|8t&X$)WnSzY-2ZfL2oNj{iQEEPAFkDL69#S{ zb-xw>{`z&(XI7A9%+5PM4lWkj=BeZCk(eRHr@~*iFFZ;^RDSTyCIVw2K#wTlVwMtq zkcoab%DRRC7)-<1j+cg8Mi{&_gMHX47^}Q>ko63zp|(wNwk%&u_qM@JK~N$9nibfJ zIjoX(nrhLSr0j%aedlUsEWFh#&vnpJ1|g3rAkX(axSDg_K@6OgteLq`lc>tO=zYng zj`N0a!007+>-&fT^})_1NDJNww}Kju)1ouAh7+|eKaS>`)X*>(7Za% z?SoBBD4C=orL2>z)DWGtnt2NySzh)!pkNy?adyM?)d2=TF!xo3Nvl+7jjG_-KnM9y z$6R=Eu*DML&!n9fv#kqZuVs3*DHQRQ27uNsoWZJld-9P~0$%NFi^tvX68UiPxc`ZC zzYZgEAK$aEEP_=Qr`SHSNC1tpbpP{U8Y~im9YWxI1z0H0{81f8ACrGR0FDr7)m2nN zA5%a&v3qFtk`~U6`yYANw&f^}1Z^!xSV$l+7c=Fq_HOU#o%#PSTnPl|l8J=vr#3HB zQ$1DF$4-YbLJ^s&yo@ErL_-!OaNZg+v)|*h0x;@QEUz%gejUwjy1vRrumc1(CFz@C zdkGvx*Z^d)L$IoNMgb_IGRxWxeOB)6l#A&jm;!)q|E>8?jf#IOMQhVqKoP+z^OiSH znx={3%^3eFh${OSFVI#9762KQyk@BQ$DJz`S^%fN>_Pqm(`)_ukSr2|wze$uqm8;DRAz2n6ovcR^`R$GxM@!Q(;FT%_Xm+ z#-`c6SJ*W8%9p1D_Jrv2$`N;HP+qb)$W~<=*I@4u6F( zZ+QX$ve0S_7LzLb%A#nGbQN&DuZOToi+@#}Kl{`guOSG;MW3KuJBPK46_aWvDuTEI z{o6JH-9t)yao60K>SBF6OBwx$NgS+s3XX?Nr8Qr27k@{GPrG~ zvBY$m^}1ma#fSUUeqEDh4)NdxtBPf72%EQt%)=7?{nQ#^@F=WhQ>A|1>sbmU`w%$X zn5qLd();ol46V6al~jwV0~&)D!=FipEn& z@X!P`>bm)PBVL;u1M#0yRH~^wG$w>4xLmhj}zmgx3anRptx+`xtPn9N@SkD0q2MPE0ATxd8Nn^= zzx9QOH%e>$Cv5aXPA51WC+70nwua0Z;r|wIItpEFIemAWW8W(jhvp(Hvq~6hNmZY@ zf2Crvfej4dm2{+9u&N+()0Sf$a0Jh3RvGF-S!)fw`heUUt$k$z2f&0vo3Tp!Gmv}v zGJX>>W(fZ=^yF=itfmi?f2EQ>Ax*;ZC9biG&h#Ym>MzlFy9vHsOP8LKp^X2~DU1fTv~UY_{(=jYz?P`1YaFl{SAS*i1s z@7>Gyu_kahL$1F{NM5}z{&~Hnf0;E{U(pBe{Qv({ZM}c*$&I^viXCl-Hr% z3BdK0ssU08ku-V|ssp6Fq{vt%0#q%Yhrj9LEkr5%&&s{Y4w$-#0AMoD5dF^ff$6}# zN0h)0&TltemcRSS+7=uplYIEY@ujj8U6wi`p#VHF;GirjTi%jm!mP)G{~ojyC~-UV zeh1iL;@{un<%aHA+8*`ux8(Qcdb#vqc}<~*%^3X_J*4Ttk-Etc$Xe+7DsOno9#z8z zfIX^kdDWeS_Nc;jkiPzbU+tk+e}3-mbZV9z_emXK(pgJ1O>HU}@%#_zw;<~!En?ex z>Ess;SBHO{zW{+@d}*SoT$X7kFPsb2={U~{QK)93_OF=_L!j!^t~*WO9ssrvO#IWW zo!#`6-^*SlZ|x?#UWP91&FO|=4AODaZB@f1*^JR|!M~}zL`GhygHW?mG?i!0tyn}2 zLDF%a(rn5rZGFsviTCaGvvO}50PCXZ-l_r49+l~8Ek7~rivgPCo4@;OJ4DJl7Vt9uh))3>0V!Hn#wd>iIS!qgV>MB;F*D>`PSRHuPd`U|r zj8(6c{rnY|FYS)A;!AVc8_g~v18$;^s(eP~>qAyp+WO+Op);y3U$MjQ%`f-1_QA2S zFbyvLJuvaFcTn!9yIVcD6XEa}d|eYu5WH4kJ6{8LmVYK&%1fVFZ#xQgdCf&t$`_Bq zZBUkue+%O-Q(wt_8PKT%y5qF5iwY~R;tBYpnI-;xLhj81KuTrbuy^Y!w6cj{ zl|-e$hZp;`F6YXH|Lk{mJo@q2P}Lwee~JGr@ui|v^kjW8bH#s(xCy4<{oOtehOb*& z8f|^%KMGaFW_>~E^)V^%1_$Gi4zT;e#Xmdu#&WY*-8|pnxP6n3g9O>p7y#@)Ckts- z{rYBsBGK|nRzYl9p?k}hj>0J&H`7$>t!MhoqOmLB9SEGlmot`HGa7OZme*1T>A39; zy~W21Oc#lTn1G9laBc3 zz@O&9KcFU%xP!9HbH7}!b6Gs>_a@V%d20SW2Q!y%p#hmkV)(0)@R%JGVOU&Drvyv%>}=DXy){dTSVgPv|g`-n{YKOi0F8KWTp zybS|leX(+H6VCnoSNIqy(8ItiH2Ue7(3scv&;)%}oIMzhM&jj_u7V_#S26_ZAX(7x z83{KIe^?WUl$T8n9BV-``~9@sn{%w_VHPt4)?J%FKMkfvYB8$SklTQ3fSA;?D+LZa znUbySp&y4n4hQ&8k@5Zp+xG&jnoFGB;->J1$@U6ZAzY4?Cu*3%T%TEa z$}|}G`wS^BOcG9nzk1ERl^>CN^RMnZP8SFi{k;F>_&h-3)70SJZchq)3`hr9<75}Z zs)qqh9 z`}>=1-}wduBCZtJ8|pudU-!N}XYQK~r{hSpyzm!d$5Wth#j@v)!uepzJ1{8jW7WlA5QeCModnL};x?lfAS>B)`^-PzM;f z?x;ltjs*06*HA(jM|LG~2pwYuMiz_%<4Y4d`s^2w{>JO_*o|?I4a(B=`miO&mjb>2Q{tWU+jk~1^Skl(i(&Bj#eIqhb<+O>7<0xaplh88)3gY zu2S;sQK-{8%xHFDP@Iy?Itq8-&;MgB0@L~9+RI3Jr9YGSSFYk;cKXbRcjeLq=51Hg zgF8XuG~}=pIHnk0V6CR~+sUC!FZ-vh5cVw}9A9E9R+*!{11~=M^#`ubfiO*8fRYAJ zNP(p|yVn>?IxYmj8=8B&__?=@{(jT#A|&6w{j>CDWKCbl)#$gi?y3d6t&=n|>$bPi zP3MmH>R<-4Om_(+9)*R)aw&(7!eW&IX$1eQ|3Wc{wR42$urWjm-2L^^-uKq?)5kun zY1q;Q)_t2D*$S8DNyYlA?L+BEfn%EG8R9>D&s3OZBhvF-_P&hHsl*>0$Dp7 z{mh`WVSK2j1ikVHN6PGp8hG@#PUsP<^i9%JBPJYslHMJcgHj-)arj#ya6(o47l3ov zR-b4p{1JQTRr_(`pQj>p!t{Vz3cTg%he%b)6bU9UF@(;4OA7oFX-BYICs>#9 zDrt-H#DCH)F=gm*sNq3bszy!O_Za@IhtJjPi49|skQ4}CGqXLF6G_Ke9iYy={YHjf zt+_Yb+_xa!4`w*8A;nui_}4dsE`%pk8@2d%?_BFrXrwB^-EqfwaB`5OM(Pl*gFHNn z2)O5EYTMI9JKd6mMk;U)+c6$oxVblS`t(P3nSsEf1h>?JZ@CUGq-!osYC7)5WumYh zYzKjuWn(`2=}}@mATW^!BW6_bfA+4myHOnp###a+ArLacaqLVUPVVmi|A(s=AfXlr z@ck6)1 zJ^yd)dz%1RF9W`*Xz=xo*gyV#L+`K@h{x_Ym-Pkb@gL<(1gk=B`;xuF@_12LC3!I10&})9j5vuN z08jkInW=5G^{^B|)!j0(2!UvF4Q`W)=5hqUsOgmq7tf-KnZ2M_+X46XH+L!U+uJAw zx`~tiSN`z_DshSI0|B{9FI0yXnCQ`Qh%JeKu|B!^eUQcWf!iH7@>-H+l4Vd8oFpAp zniEPmerey^Vm#N@#PVQe3sx)@5cPU*x+5D~oMl;d*K4=3xYffd24Tx_OAm1@%}o4j z5O+H7+n0PrTN#9a1X!V-&Pknh`QKF&VzsG_gY1b6+ImC^tQJaQ!d{oaD2o{lFlu_G z0q|0GE%JR{#!%2!zP$eR8{+cu_e}tsv}<3c02oe~ala`^vw*NFGAu@cpRz^#`o@VW z$T}%-D*jDS$v4Wf8v#eoEDxwk)9(}OjsvPg<@Q>NEhl|%_^i%JLo^_8A^D)p;~kZD{=JmamzpU>qEjv`Vh0TVjJUN(F3Z^vbXE$96jQHgz z)h5hh6$&~*W?SM?VoL^>#9)$@S&GxS9_}?6fEkqPoCfhPB*eo@mx;Cx^B=Gj0<-wZ zf~H1U5z{Ll0JDsWKnfi4!IlTVlfOYBj1QK@!A@a!0kE15LX9cLP4tT_1DUcrp0gl4R6cT^lTTLE#2SwiP&>hE) zP{om%`IBCG2Buh{1SQ?&@Mh^XIC%5KH}{Le{D*Oh61C&7eW`=1pV=j-hH<+^PC*_o z%+>4hiva$WtV!*T`{$qMBmm5^vg1W02*g$bbh&Tu4(K!lQwx6{0FEQj7w>qaJI-z| zY$gYPssG*WNy7Z6+@hN&h%^+c=FjS~N`#T#C~KOwfUpXF#Ef}v$b*6PA{X@PIPdLu z_PwR8Z0L^DGot)EfjC7mU!N6NHK}<(>Dzv;Dv+2B=dS1;~HO3I8FimK1G8cU&p}f>Pjsh1L|>X%Lr!hh z76yUxl?qz&!wTxZBDo0tP4nLNEYlsQeX{(L7+lVtcqE-#a(5I_#_Q}3?v_Z^`iawm z!{7#bUAz2em?yl-DA)R>6*jVbaWn79`NIsoWNfq~`U zJJ<%&{}^iBraKFO5>Zx)!2Apinj5gn1bn$3Ag8eKwr7sqL<$_!N^54v0cTd>O--MF z#C!;ZSh0H)VrL(8L*>h_GriidobI@0e9YANqMVrSgq7ZrZd=Kw(E^6 zHXQ!QO(&!q1$*sF4mF=DeSS5Jm&hS*L7|N;L8Gi7)&g<#N0LjpoBmn5Y*r{ z@rHh%e)}-6-w^;M4cM>`IyH|Dlvz-JU||J-Ke?gAw;hw@l+2P1D#)0qPCg3Z?l_56 z{ph%aM{EQ=GT%;z<$Kv5AY!O|`HiMmTUMovvUEO}P%8yib%NfzLJF)~YT0jdk6~}! z`F!~h6}zU58eYqP3=f`mCnRqrw|%KEZazK*N}naffKh5O80hD|6bO_;Ad?3qwDuB| zD~z&I0kD?>9e67ODe!%g`oOdk(|JopLECzh3WAO&z+JkRS!A3>ezXestrbDZgXK+I zkAb?8Xyn1!Bvws63K3Yo*JD-6Bi8SOe0kb>!RpFnU~2T}dt1Ahe_E3MTS3_a=!48vt~t$sIa6WxxZ`{rlR_$Arbd0RUOcH>)73Dv&~9J7@CT zYV-tQl?8eJ{Lall;ZdqI|&Gnx(trv_pdKA55P!kj8^i-Ins}9{A^keIME7u4&+*JE5)1w%#>3X=q;x zn&H$|4XR-r3Sac14@7)qOu>SEU$e4CtMARS6zBn99|cmOy5kC<&vy!p6Y*~Vz*5w^ zh=2%fJ=Re=yKVOR(krGstyB4hGPlmurcbqxLIAfe8y@Nal z)mPGww%w5Wee}I;SSz~Y)|SMeQj{_@Z)&lBxL4u&>s{&T{t=*}Xz~zMpLq`i_U%hA zApi!|Fs`fXsCh%&XX=tkC~xFXqq1H-I}R;Lfj|Ukq`<>m=w}z}L}cc=y0mmxfd1ke zWmRT-DVaszAzcjfrQq-LC&wl+Y$U-VULAOJXO&*Yqv=|e6DVn=eJPw>sOh2Vk^(;obzB0#KtVxZMa6{i>G zr|WZH z=c;WVeQ*1g?v5jUcU+l1bRG_N|!nPSR9IZ zQuWWE><3GMM-hlfDyCRJLp>@58aEi11^ZH9iJ|mI)``Pm766|=|EncP4okwR8ItDu z=W-15DVfD8tkl51AVxx!js*=91`;dR$t9caC`5N=#ne>ZIsnX^wxCN)>ORmtCwvh9 z4iD3-J1nm|?wEc>0blmEg$iC3|Jhs#aRvNeE_ZN)QHg4(PA7)fsfRzQJDs-3<{|LZ zktojr+(B~Gg7y_8Xg5uOKopSzK?>}Y1s=LApulnQ&jG+50<*TQlA3-a2-uy0k{B7b zw-y#{LKqKr37N&1!oCbB^>(!(0?O%`r z$L_eQdl6fuKqTo2ikKOcVyhGw;>bBGj_3Gz^i=Ur#9aIl6TI3g1v1g(wmYs6F$_Gv zl=CvrG!KYNj&g7wQ;sRG?j!|H0gwSwpznJyC;oW=D0wN;aaC6b30@f10Bu-QpH7#K!FzPM1whRHy{$aB5#~%r z7OTjiyIx$zu4Y4uESm0s4}U^i6PJVYn4WOGK?;O;umgZBq6SjI3(xWXa7YBeXDtQ( zpS^4CjuTgcW5N}KKs*MRVcvdao&Wy}uUZlyi57^l6Z-=?XOpbg$2mKQ>h9{U>L|)| zDiDnS_|Ivum6+B&P$3l5afkz2QPrUYr?=NPLSAI`OT|dcJDnJ=x~s4zO{E~N-i;{v zSXV5lWV<7%v9ukcq-VRs{I8`({;j&VuRZx-lvO@562lQ?rpgT;z^Wjr(Ba|p6BO3! zJC+L#SXs^3^NVz2Y4DHB!HCjW?nR>_hc$aVW9sUkdA(Ej9fXyA#-Q5Fqnx5#JQ5SJ zr{69PFrP>QHKVK<@^1uyvJQ?|RTupz@>yn-_)1JXDG$%s?l|aYDYw+kdp8mzX98cb z6fo$s^LE!H$>3z$GV1~^4~0~VNT^Yk_8tEBfWM+T`M2uczMKlIm~u;n-Eq+(2n>Ak zJ7}>k%+dr!y#N3p07*naRMR+Sp70{_E&vJ)Tnc<< zUe~^-0(pL?L!P;hX~+@rI+#u|0L-QWtr9pNe#`YQNGi}Yz48J;UIz;Z1UJeO=ER=& zKz>(Tce=M=)xG_l6IWSB1%}OGi8?N663@(Q$tVObYjVdX5P^a1>D0FS_5e7!fC|iO z&*5xd(=X@)7NZ@kG3E}kA!%Ls$~_5*RIKo*FOs zgDu3y-yx{LC~4B&c3&HOV!g*;P3Yc6w+647i!KaPki<~c2Hudj_jjZj5+3$QXCA#U zOQ=9r+u3-G8fC%wg{SBZ1ah734HPh|?(J`#P8~;uI_}By>97T}&e-IM%S}OQJx{aa zBqw2IRd^?rp??8hr+yGff-=EJAyS;P^ZRINSf%*FG<{V-TU`@v2p-(sp;&Qucc(~k zcXyW*cbDSs4n>O=3Pp-bad&s;=KJrxZ+S>g_MVwDv-TcY#MB!hN%-{vaNu-~>=TX` zl4D0qRW&f_Za(jPz)F_Q{!R7VgQHh#2{pex#@AmbaGs)En**ypsJwn_5FGYaul z+BBT4<_XM?A7(<#EEGI{vl9Dkl?U40DpKh!8rv%hdoiDD4fYNHRVs6O;PeHvSQE3h zfd-oub4LtTSDwYpOTXH)B74Y57_NVr=S|{%O6Vga_!rVPC%$Vt=NSn!ty?Tw@cn}{ zfSi{q(k64!>j$rSW=>K*RCU&`r@+p9v-7EBfFyp(7!guWO zb0#CT3+Kdzn9@J0tnjn_ z`MW`P^QjjoQFW{K*ka)Ce@a7cBK?^IX zSS`~I;s2ZIM1VKG6H%TN1W`U&&WI5Se8Q^Rsi_>+g6d?h5I{9uq8mPhNRy1<`*)Wp zg5RAL!M!VYpkAZ6R;i64lHKzo_cw`~s!Zb45wEYzeJQ^yy8{bcNqV2*a2_KT3wN>> zm4QO+ea|gICVq#;a$R}%QkE3sYB#JJy^gL!Oi;%*xYp1yaTEz#+yT#+>?hqx?WVXk+z+;pqe9(ql;nefJb$_-b*9ckH_|G)nC#L)uoa}c3oUiU0#uHz> z^t&_TcalVK5Hb)0@Fq%KlS*GbT0>))AB(@@+?GsA&0AxV_;_?|~0-E=0qzEOwr0UL~S zzPvR9jjsZAT5Bsjc?BCJ@W;F-JLR7hqGEm;VX90tzM$L9a2o=aV!nm6v}h%QnNG@2 zc{N9EnLJYwaXg?3jA0U{6<5A}S`cO^au8gNeF>*hRlH(bq2@Bf1*7~>fH+cb689RBLPv~T#g4UA+FdVRx$x2jsdGbfT}Fk@ zPuiO*%z#!K%Ua~J?~cBj^}8FrBI+bEY5dANC3GrmpGbKPoWHkdN(v^X<4=sgs`uq; zeN^1-N5AUvK5s`X);Q6+i@NQ3dmXXd;^6nkthM5s;i!z?K1aA7x(2o2!hom(tU0J& ztmC>}f~%Uu_?iX&mH<^$D)`3Tu}TC5{`58@dY*FIFT_fFIR7=7n+8_OlL4^5(p*4& zRJu>W_jcU&=RNc-5%FABd(H2NLOrAD|;(bSUGz42rt1s*H6NX$e&SqGeZlWwgp{Ih}N?n!|oS_;35u zt}9jr9`x(lV1(3De#Ang1c{8r8??`|eSUqL>{N;vWQqfgDISx|JoR-TH&_bxT-w4} z7%Eul1V7+kYg&|}ZDXh4uw5+5?_h;-R1Q}gHxa<;>^*tIU4LHn&MFn#+MVgTA%PnJ z0q*%5X4!HN4{9sp(?f2=okoG`aKEl&J^Xe+^S6faYs&9AO1vyKA@0Zq-$PBb_Qh_~ zBCejx8;Ir!VeWS~q~`k8*j&YJSnwku(bMCRwG%>S|`` z?PNPFL~WI(V67QtqRmB>C`<0z zytngr#Na2vx?biZP=d`VvhBJd?W#|^EMA2FV&_5Z1AvnVv}vrQd?o;d0{$JXFF|}c zMTEH~>uG#7!ILh6xnSb;b6ck3e3lQ4y_;QL=lCi2%|XJFgyjR262i~Xt&EELkR&yP zY!J0t-qrKsnB7+?suBkgTaFulAtkKv?vzFmoc4`883l1d#fN>$4@|6ooR0$Qjy&0* z7O~4es>6S>8$=vb@*0)&>)Dy$0$91UhruzoW1kFt(Y$fS74S}dlZK%ZVLY0T*8|tt z=3E4VgyLmLh(&!cB~8*xQ+W6ttmOJ;e%=%G%K3cvw?RR!7?pg(QdZcB;LprDM=)3s z=eeU&%M3NgDwo6X+sZd%V0A*K)gvsSPtd<)GlX`27fv=u#9%$S17BLZ^RwBR_8B(MeGzLgpN%1sdp)@s@I+8}y2Er#jd5@*2e2~3 zU)O(Cst=qwifyu-bJ3ypR$k2FL$GflzzID2P8cOOQ2$L-_R~PTo{W)@ z?PCA&p0Hu-CJq26;n%nq zCIHBlve=DJdfLboU~#KZ;XL#YQNUP*T$^Bb>lt<#8@>i+4#mxm-uq|K!oDiwd-=&< z)EvKCZa_nJ&8X=JaJhqA)TG3eX zRm-AqOj72iO(@T7I7a;JD_cP~ubQEJ%MXIwxIWa$(dpK7fz1uE(8RCjctYCYm&zkYP}FR`i`4R~FN)1X|sGDbA#H+w0Fp$af=#HC>On1RwXHP6beNnA(JDYTAbN zwtKNbwc5e z-NC~H;!6@JjGFe_x5V1!zl9)fZt;Fp5_^ccSD6{2l{tW;d6_GhNwabd%s*+VJtCC} zP`;2ei_xxdj`!dCnud3l#aE{xOj-Kgy}$E~)Umj74%_r`^e_)|c&Pbz<qnk+19>B${bt^ zt-i8A%YWbcjJD^|-(AKqzq(*%@MRjZs1AED&oTmo%Ue;%<5-^KHN)$!o-pO;FP~m5 z?V>2ib-l{_d6eeAq94^~s^-MS7Y`(g7`4;vPiknpXX>rojp-)vd|`yI;AzxuI(Nu@ zxHmE?=4S>SsDiJNJyN52XEH&h*vbsfb_8kPFNmk)46&DbN;Efb(Ab|>mlkdkCb&Jlg00Bs4} zkGmGy;&wiWZK}bY+iDI|rTll!putreP9jII-Z}2jFh*B~%0a3ARMGcqFvOUlp&2Ko z)R@wl@!X4E;;YdR#Uy@hT$#S1cIs?-v!V&(;lWWEl8LW8RxIfnB2l0KB$4*h}m1w&qB| zVs0yx1G7`&UQ?i0kyq*(yf>PCotye6rmPJTxGkJ>3aFFrR}_K9!NR5qXG4wAlEHJ7 zgeORHf(QiEip5ou&Fy!BH6TUd1qaQIx*r*kR}H?E0q>n$0lTA4gCm3J?F`M}dt7U@#}M zH$(W2@bfDTLqv;#BJU6H=TN~Epf%Gm0J=P^p?WvgzV~#|P^+255-n82<*H%hNQj)m z_~OID(&W52t0cO!;LljK0BeYokfk;l1vnLb)G z6uPX1a(ZAE!Ajn5>@GwPVmCRVJdF2gPL+3ssW}$R?@bPzbM*iE0FTp+O+v=0I5_JX z#xy^?M>IA?E-nHA_&|+VmWmmYK^GSfPWS8Q8`&?s z!dUHavy@5fX#SL<;{V-kX&XwCWVm|d!21!=?DG1J#j|Qn6avxmXv9549&oK{ZDxi? zf>sT{HOR3-M|Dn4O#!#RziA#Gm!*PYnH+*@BFgx1hyn19L@oui@^s3I49>bFDzd@gFuJytj4G_U233VyQZ(`o44 zkY!K8DgX^Mdprjh%|6)Qk3zq~hPAbCgqL`y3=+CTrTLl*f;y-iDZ1sX0hCz-+# zIkpk0ME&XR+A?jHPEL=U6YlUdgkt{w4JME6Iz#tj{&TnZECYMHDd!JPmds4!f&{?$KG9I7A$4S;GP z@CUN?efYviZ^_zX)y`%^&E3#H%r8@fm$}_+Gei?>>Ide%$-oLaw`v;*ZFs>KcPEI& zSAPLZ5^k#x%PEnNI^gkZn=wfyn!50a<~>5p#0L;kZGp?476XdCp^Q!DS-_8^@87wq zDl{Hfd`s18L@)-Z+&ZSn61>uOL2f!e8v!&wmIj6i?Z~JfAMLcQ`^uJ0=z(Mhb~AU z^HcN4S)o&KIDtJXi*jaicJRZ5{W~DCz8Iay4bTH+dv7&uc|tpY>}eVzh=f81BONXq7Q2&toaUp+S$ZS+;|IZ#Bv#Vc zxWCsjK**a5{kCdZww-LZT4rGuDlvNSh0+ALn43{@?us)QUNDA{ir&R0Y0{spYx&`E zS1dw8mym@^Lmx}>Mo?9ABr`!UgcjH11gbu}{kov>%0AJ7c6@5s@5b$CwtenF0J?Bf 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 literal 76689 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