-- Autogenerated with DRAKON Editor 1.33
require('strict').on()



local table = table
local string = string
local pairs = pairs
local ipairs = ipairs
local io = io
local pcall = pcall
local xpcall = xpcall
local debug = debug
local math = math
local tostring = tostring
local clock = require("clock")
local log = require("log")
local digest = require("digest")
local fiber = require("fiber")
local json = require("json")
local fio = require("fio")
local log = require("log")
local os = os
local error = error
local print = print

local utf8 = require("lua-utf8")

local vud = require("vud")
local space = require("space")
local utils = require("utils")
local trans = require("trans")
local mail = require("mail")
local ej = require("ej")
local lic = require("lic")

local get_tarantool_status = get_tarantool_status

-- configuration
local global_cfg = global_cfg
local price_cfg = price_cfg
local external_creds = external_creds

local http = require("http.server")
local http_client = require('http.client').new({max_connections = 5})

local http_router = require('http.router')

local httpd = nil
local router = nil

local greq = nil

local api_handlers = {}
local file_cache = {}
local ping_files = {}

local g_content = {}
g_content["ru"] = {}
g_content["en-us"] = {}

local language_list = {}
language_list["en"] = "en-us"
language_list["en-us"] = "en-us"
language_list["en-gb"] = "en-us"
language_list["ru"] = "ru"
language_list["ru-ru"] = "ru"

local WARNING_BEFORE = utils.days_to_secs(7)
local AUTOPAY_RETRIES = 3
local AUTOPAY_RETRY_DELAY_SEC = 3600

local db = require(global_cfg.db)

setfenv(1, {}) 

local module = nil

function add_diagram(data)
    if ((data.section == "deditor") and (not (utils.is_empty(data.spaceId)))) and (not (utils.is_empty(data.folderId))) then
        local ok, result = space.get_folder(
        	data.spaceId,
        	data.folderId,
        	false,
        	"system",
        	true
        )
        if ok then
            data.diagram = result
        end
    end
end

function already_logged_handler(req, session, headers)
    return make_json_error(400, headers, "ERR_ALREADY_LOGGED_ON")
end

function api(api_method, method, must_logon, must_admin, handler)
    local key = api_method .. "/" .. method
    local handler_info = {
    	must_logon = must_logon,
    	must_admin = must_admin,
    	handler = handler
    }
    api_handlers[key] = handler_info
end

function api_access(req, session, headers)
    local data = req:json()
    local grant = (req:method() == "POST")
    local msg = space.access(
    	data,
    	grant,
    	session.user_id,
    	session.admin
    )
    if msg then
        return result_from_message(headers, msg)
    else
        return make_empty_response(headers)
    end
end

function api_account(req, session, headers)
    local user = vud.get_user(session.user_id)
    if user then
        local data = {
        	user_id = user.user_id,
        	name = user.name,
        	email = user.email,
        	block_email = user.block_email,
        	is_admin = user.admin,
        	is_active = user.enabled
        }
        get_access_for_user(
        	session.user_id,
        	data
        )
        return make_json_success(headers, data)
    else
        return make_json_error(404, headers, "ERR_USER_NOT_FOUND")
    end
end

function api_backup(req, session, headers)
    local space_id = req:stash("first")
    local ok, result = space.backup(
    	space_id,
    	session.user_id,
    	session.roles
    )
    if ok then
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_check_trans(req, session, headers)
    local trans_id = req:stash("first")
    local trans = get_completed_trans(
    	session.user_id,
    	trans_id
    )
    local data = {}
    if trans then
        if trans.status == "success" then
            data.result = "ok"
        else
            data.result = "ERR_PAYMENT_FAILED"
        end
    else
        data.result = "ERR_NOT_FOUND"
    end
    return make_json_success(headers, data)
end

function api_clear_trash(req, session, headers)
    local space_id = req:stash("first")
    local msg = space.clear_trash(
    	space_id,
    	session.user_id,
    	session.admin
    )
    if msg then
        return result_from_message(headers, msg)
    else
        return make_empty_response(headers)
    end
end

function api_confirm_coupon(req, session, headers)
    local message
    db.begin()
    local data = req:json()
    if data.coupon then
        local coupon = lic.get_coupon(data.coupon)
        if coupon then
            local ok, details = calculate_payment(
            	coupon.product_id,
            	coupon.max_users,
            	nil
            )
            details.coupon = data.coupon
            if ok then
                local trans_id = create_transaction(
                	session.user_id,
                	"coupon",
                	details
                )
                details.trans_id = trans_id
                db.commit()
                ej.info(
                	"confirm_coupon",
                	{user_id=user_id, trans_id=trans_id,
                	coupon=data.coupon,
                	session_id=session.session_id}
                )
                return make_json_success(headers, details)
            else
                message = details
                db.rollback()
                return result_from_message(headers, message)
            end
        else
            message = "ERR_COUPON_NOT_FOUND"
            db.rollback()
            return result_from_message(headers, message)
        end
    else
        message = "ERR_COUPON_NOT_FOUND"
        db.rollback()
        return result_from_message(headers, message)
    end
end

function api_confirm_paid(req, session, headers)
    local message
    db.begin()
    local data = req:json()
    if (data.users) and (data.product_id) then
        local user = vud.get_user(session.user_id)
        local old_license = get_old_license(user.license)
        local ok, details = calculate_payment(
        	data.product_id,
        	data.users,
        	old_license
        )
        if ok then
            local trans_id = create_transaction(
            	session.user_id,
            	"payment",
            	details
            )
            details.trans_id = trans_id
            db.commit()
            ej.info(
            	"confirm_paid",
            	{user_id=user_id, trans_id=trans_id,
            	product_id=data.product_id, users=data.users,
            	session_id=session.session_id}
            )
            return make_json_success(headers, details)
        else
            message = details
            db.rollback()
            return result_from_message(headers, message)
        end
    else
        message = "ERR_ERROR"
        db.rollback()
        return result_from_message(headers, message)
    end
end

function api_create_folder(req, session, headers)
    local space_id = req:stash("first")
    local data = req:json()
    local ok, result, count = space.create_folder(
    	space_id,
    	data,
    	session.user_id,
    	session.admin
    )
    if ok then
        local rdata = {
        	user_name = session.name,
        	folder_id = result
        }
        return make_json_success(headers, rdata)
    else
        if (result == "ERR_DIAGRAM_LIMIT") and (count) then
            local suggested = suggest_product(
            	session,
            	count,
            	get_product_diagram_limit
            )
            local error_data = {
            	error = result,
            	suggested = suggested
            }
            return make_json_response(
            	400,
            	headers,
            	error_data
            )
        else
            return result_from_message(headers, result)
        end
    end
end

function api_create_space(req, session, headers)
    db.begin()
    local message
    local suggested = nil
    local user_id = session.user_id
    local user = vud.get_user(user_id)
    if user then
        local limit = get_space_limit(user_id)
        local actual = calc_own(user_id)
        if actual >= limit then
            message = "ERR_SPACE_LIMIT"
            suggested = suggest_product(
            	session,
            	actual + 1,
            	get_product_space_limit
            )
            db.rollback()
            if suggested then
                local error_data = {
                	error = message,
                	suggested = suggested
                }
                return make_json_response(400, headers, error_data)
            else
                return result_from_message(headers, message)
            end
        else
            local data = req:json()
            local space_id = utf8.lower(data.name)
            space_id = utils.trim(space_id)
            if space.space_exists(space_id) then
                message = "ERR_SPACE_EXISTS"
                db.rollback()
                if suggested then
                    local error_data = {
                    	error = message,
                    	suggested = suggested
                    }
                    return make_json_response(400, headers, error_data)
                else
                    return result_from_message(headers, message)
                end
            else
                if #space_id < 2 then
                    message = "ERR_NAME_TOO_SHORT"
                    db.rollback()
                    if suggested then
                        local error_data = {
                        	error = message,
                        	suggested = suggested
                        }
                        return make_json_response(400, headers, error_data)
                    else
                        return result_from_message(headers, message)
                    end
                else
                    if #space_id > 70 then
                        message = "ERR_NAME_TOO_LONG"
                        db.rollback()
                        if suggested then
                            local error_data = {
                            	error = message,
                            	suggested = suggested
                            }
                            return make_json_response(400, headers, error_data)
                        else
                            return result_from_message(headers, message)
                        end
                    else
                        if utils.good_id_symbols(space_id) then
                            space.create_space(
                            	space_id,
                            	user_id
                            )
                            space.update_space_limits(space_id)
                            db.commit()
                            return make_empty_response(headers)
                        else
                            message = "ERR_NAME_INVALID"
                            db.rollback()
                            if suggested then
                                local error_data = {
                                	error = message,
                                	suggested = suggested
                                }
                                return make_json_response(400, headers, error_data)
                            else
                                return result_from_message(headers, message)
                            end
                        end
                    end
                end
            end
        end
    else
        message = "ERR_USER_NOT_FOUND"
        db.rollback()
        if suggested then
            local error_data = {
            	error = message,
            	suggested = suggested
            }
            return make_json_response(400, headers, error_data)
        else
            return result_from_message(headers, message)
        end
    end
end

function api_create_user(req, session, headers)
    local msg, ok, result
    local data = req:json()
    if (((data.name) and (data.password)) and (data.email)) and (data.lump) then
        if user_exists(data.name) then
            msg = "ERR_USER_ID_NOT_UNIQUE"
            return make_json_error(
            	400,
            	headers,
            	msg
            )
        else
            if user_exists(data.email) then
                msg = "ERR_USER_EMAIL_NOT_UNIQUE"
                return make_json_error(
                	400,
                	headers,
                	msg
                )
            else
                if send_capture(session, data.lump) then
                    ok, result, new_session = create_user_core(
                    	req,
                    	session,
                    	headers,
                    	data,
                    	data.password
                    )
                    if ok then
                        set_session_cookie(
                        	headers,
                        	new_session
                        )
                        return make_json_success(headers, result)
                    else
                        msg = result
                        return make_json_error(
                        	400,
                        	headers,
                        	msg
                        )
                    end
                else
                    msg = "ERR_BAD_REQUEST"
                    return make_json_error(
                    	400,
                    	headers,
                    	msg
                    )
                end
            end
        end
    else
        msg = "ERR_BAD_REQUEST"
        return make_json_error(
        	400,
        	headers,
        	msg
        )
    end
end

function api_create_user_email(req, session, headers)
    local msg, ok, result
    local ip = get_client_ip(req)
    local data = req:json()
    if (data.name) and (data.email) then
        if user_exists(data.name) then
            msg = "ERR_USER_ID_NOT_UNIQUE"
            return make_json_error(
            	400,
            	headers,
            	msg
            )
        else
            if user_exists(data.email) then
                msg = "ERR_USER_EMAIL_NOT_UNIQUE"
                return make_json_error(
                	400,
                	headers,
                	msg
                )
            else
                local password = utils.random_password(8)
                ok, result, new_session = create_user_core(
                	req,
                	session,
                	headers,
                	data,
                	password
                )
                if ok then
                    set_session_cookie(
                    	headers,
                    	new_session
                    )
                    send_welcome_email_later(
                    	result.id,
                    	result.language,
                    	password
                    )
                    return make_json_success(headers, result)
                else
                    msg = result
                    return make_json_error(
                    	400,
                    	headers,
                    	msg
                    )
                end
            end
        end
    else
        msg = "ERR_BAD_REQUEST"
        return make_json_error(
        	400,
        	headers,
        	msg
        )
    end
end

function api_def_query(req, session, headers)
    local data = req:json()
    local ok, result = space.def_query(
    	data,
    	session.user_id,
    	session.admin
    )
    if ok then
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_delete_diatest(req, session, headers)
    if global_cfg.diatest then
        local id = req:stash("first")
        local path = make_fixture_name(id)
        os.remove(path)
        return make_empty_response(headers)
    else
        return result_from_message(headers, "ERR_NOT_FOUND")
    end
end

function api_delete_search(req, session, headers)
    space.search_delete(
    	session.session_id
    )
    return make_empty_response(headers)
end

function api_delete_space(req, session, headers)
    local space_id = req:stash("first")
    db.begin()
    local message
    local user = vud.get_user(session.user_id)
    if user then
        local ok, access = space.read_access(
        	space_id,
        	session.user_id,
        	session.admin
        )
        if ok then
            message = space.delete_space(
            	space_id,
            	session.user_id,
            	session.admin
            )
            if message then
                db.rollback()
                return result_from_message(headers, message)
            else
                db.commit()
                ej.info(
                	"delete_space",
                	{user_id=session.user_id,
                	space_id=space_id,
                	session_id=session.session_id}
                )
                return make_empty_response(headers)
            end
        else
            message = access
            db.rollback()
            return result_from_message(headers, message)
        end
    else
        message = "ERR_USER_NOT_FOUND"
        db.rollback()
        return result_from_message(headers, message)
    end
end

function api_delete_user(req, session, headers)
    delete_user(
    	session.user_id
    )
    return make_empty_response(headers)
end

function api_download(req, session, headers)
    local filename = req:stash("first")
    local data = req:read()
    data = decode_data_url(data)
    local down_info = {
    	type = "simple",
    	image = data
    }
    local result = put_download(
    	session,
    	down_info,
    	filename
    )
    return make_json_success(headers, result)
end

function api_download_svg(req, session, headers)
    local data = req:json()
    local down_info = {
    	type = "svg",
    	image = data.image,
    	fonts = data.fonts
    }
    local result = put_download(
    	session,
    	down_info,
    	data.filename
    )
    return make_json_success(headers, result)
end

function api_edit(req, session, headers)
    local space_id = req:stash("first")
    local folder_id = req:stash("second")
    local data = req:json()
    local msg = space.edit(
    	space_id,
    	folder_id,
    	data,
    	session.user_id,
    	session.admin
    )
    if msg then
        return result_from_message(headers, msg)
    else
        return make_empty_response(headers)
    end
end

function api_feedback(req, session, headers)
    local user
    local type = ""
    if session.user_id == "" then
        user = "nouser"
    else
        user = session.user_id
    end
    local data = req:json()
    data.user = user
    if data.type then
        type = data.type
    end
    local d = clock.time()
    local date = os.date("%Y%m%d-%H%M%S", d)
    local filename = type .. date .. "-" .. user .. ".json"
    local path = global_cfg.feedback_dir .. "/" ..filename
    if session.user_id == "" then
        
    else
        add_diagram(data)
    end
    data.user_agent = req:header("user-agent")
    save_report(path, data, session.email)
    return make_empty_response(headers)
end

function api_find_folder(req, session, headers)
    local data = req:json()
    local ok, result = space.find_folder_by_name(
    	data.space_id,
    	data.name,
    	session.user_id,
    	session.admin
    )
    if ok then
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_find_users(req, session, headers)
    local data = req:json()
    if data then
        local result = vud.find_users(
        	data
        )
        return make_json_success(headers, result)
    else
        return make_json_error(
        	400,
        	headers,
        	"ERR_BAD_REQUEST"
        )
    end
end

function api_gen_coupons(req, session, headers)
    local data = req:json()
    local tag = data.tag
    local count = data.count
    local product_id = data.product_id
    local max_users = data.max_users
    if (((tag) and (product_id)) and (count)) and (max_users) then
        local coupons = lic.create_coupons(
        	count,
        	product_id,
        	max_users,
        	tag,
        	session.user_id
        )
        local data = {
        	coupons = coupons
        }
        return make_json_success(headers, data)
    else
        return result_from_message(headers, "ERR_BAD_OP")
    end
end

function api_get_access(req, session, headers)
    local space_id = req:stash("first")
    local ok, result = space.read_access(
    	space_id,
    	session.user_id,
    	session.admin
    )
    if ok then
        result.readers = to_user_names(result.readers)
        result.writers = to_user_names(result.writers)
        result.admins = to_user_names(result.admins)
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_get_adm(req, session, headers)
    local op = req:stash("first")
    if op == "stats" then
        local rdata = db.stats_get()
        return make_json_success(headers, rdata)
    else
        return result_from_message(headers, "ERR_BAD_REQUEST")
    end
end

function api_get_diatest(req, session, headers)
    if global_cfg.diatest then
        local paths = fio.glob(
        	global_cfg.diatest .. "/*.txt"
        )
        local fixtures = {}
        for _, path in ipairs(paths) do
            local content = utils.read_all_bytes(path)
            if content then
                local base = fio.basename(path, ".txt")
                local data = json.decode(content)
                data.id = base
                table.insert(
                	fixtures,
                	data
                )
            end
        end
        local result = {
        	fixtures = fixtures
        }
        return make_json_success(headers, result)
    else
        return result_from_message(headers, "ERR_NOT_FOUND")
    end
end

function api_get_download(req, session, headers)
    local _sw15520000_ = 0
    local download_id = req:stash("first")
    local record = take_download(download_id)
    if (record) and (record.session_id == session.session_id) then
        _sw15520000_ = record.data.type
        if _sw15520000_ == "simple" then
            local body = record.data.image
            local status = 200
            local filename = record.filename
            set_content_type(headers, filename)
            headers["content-disposition"] =
              "attachment; filename=" .. filename
            local resp = {
            	status = status,
            	body = body,
            	headers = headers
            }
            return resp
        else
            if _sw15520000_ == "svg" then
                
            else
                error(_sw15520000_)
            end
            return download_svg(
            	req,
            	headers,
            	record
            )
        end
    else
        return result_from_message(headers, "ERR_NOT_FOUND")
    end
end

function api_get_folder(req, session, headers)
    local space_id = req:stash("first")
    local folder_id = req:stash("second")
    local ok, result = space.get_folder(
    	space_id,
    	folder_id,
    	false,
    	session.user_id,
    	session.admin
    )
    if ok then
        result.user_name = session.name
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_get_for_free(req, session, headers)
    local language = choose_language(
    	req,
    	session,
    	""
    )
    local message
    db.begin()
    local data = req:json()
    local transaction = get_uncompleted_trans(
    	session.user_id,
    	data.trans_id
    )
    if transaction then
        local tdata = transaction.details
        local coupon = lic.get_coupon(tdata.coupon)
        if coupon then
            redeem_coupon(
            	session.user_id,
            	tdata.coupon
            )
            complete_transaction(
            	session.user_id,
            	data.trans_id,
            	"success"
            )
            local agreement_id = nil
            create_license(
            	session.user_id,
            	tdata.product_id,
            	tdata.max_diagrams,
            	tdata.max_spaces,
            	tdata.users,
            	tdata.expiry,
            	data.trans_id,
            	agreement_id,
            	"coupon"
            )
            db.commit()
            local make_receipt = function()
              return build_receipt(
                  data.trans_id,
                  tdata,
                  language
              )
            end
            local response = db.invoke_no_throw(
            	make_receipt,
            	"build receipt"
            ) or {}
            response.type = "coupon"
            response.trans_id = data.trans_id
            return make_json_success(headers, response)
        else
            db.rollback()
            message = "ERR_TRANS_NOT_FOUND"
            return result_from_message(headers, message)
        end
    else
        db.rollback()
        message = "ERR_TRANS_NOT_FOUND"
        return result_from_message(headers, message)
    end
end

function api_get_license(req, session, headers)
    local user = vud.get_user(session.user_id)
    if user then
        local license = lic.get_raw_license(
        	user.license
        )
        if license then
            local language = get_user_language(
            	session.user_id
            )
            local now = os.time()
            local left = license.expiry - now
            local left_years = left / 3600 / 24 / 365
            if left_years >= 5 then
                license.expiry_str = "MES_NEVER"
            else
                license.expiry_str = localize_time(
                	language,
                	license.expiry
                )
            end
            local product = utils.get_product(
            	license.product_id
            )
            license.product_name = product.name
            license.renew = lic.should_show_banner(
            	license.expiry
            )
            if license.agreement_id then
                local agreement = db.agreement_get(license.agreement_id)
                if is_agreement_active(agreement) then
                    license.currency = agreement.currency
                    license.total = agreement.total
                    license.due = localize_time(
                    	language,
                    	agreement.due
                    )
                else
                    license.total = 0
                end
            else
                license.total = 0
            end
            return make_json_success(headers, license)
        else
            return make_json_error(
            	400,
            	headers,
            	"ERR_NO_LICENSE"
            )
        end
    else
        return make_json_error(
        	400,
        	headers,
        	"ERR_USER_NOT_FOUND"
        )
    end
end

function api_get_own_spaces(req, session, headers)
    local user = vud.get_user(session.user_id)
    if user then
        local spaces = get_only_own_spaces(
        	session.user_id
        )
        local data = {
        	spaces = spaces
        }
        return make_json_success(headers, data)
    else
        return make_json_error(400, headers, "ERR_USER_NOT_FOUND")
    end
end

function api_get_search(req, session, headers)
    local resp = space.search_items_get(
    	session.session_id
    )
    if resp then
        return make_json_response(200, headers, resp)
    else
        return result_from_message(headers, "ERR_NOT_FOUND")
    end
end

function api_get_summary(req, session, headers)
    local summaries = ej.get_summaries()
    local rdata = {
    	summaries = summaries,
    	status = get_tarantool_status()
    }
    return make_json_success(headers, rdata)
end

function api_get_tag(req, session, headers)
    local space_id = req:stash("first")
    local folder_id = req:stash("second")
    local time = req:stash("third")
    local ok, result = space.get_tag(
    	space_id,
    	folder_id
    )
    if ok then
        local data = {
        	user_name = session.name,
        	tag = result,
        	time = time
        }
        return make_json_success(headers, data)
    else
        return result_from_message(headers, result)
    end
end

function api_get_theme(req, session, headers)
    local ok, result = space.get_theme(session.user_id)
    if ok then
        local user = vud.get_user(session.user_id)
        if user then
            result.id = session.user_id
            result.name = user.name
            return make_json_success(headers, result)
        else
            return make_json_error(400, headers, "ERR_USER_NOT_FOUND")
        end
    else
        return make_json_error(400, headers, result)
    end
end

function api_get_transactions(req, session, headers)
    local transactions = lic.get_user_transactions(
    	session.user_id
    )
    local user_payments = db.payment_get_by_user(
    	session.user_id
    )
    for _, tran in ipairs(user_payments) do
        local payment = tran[3]
        payment.type = "payment"
        payment.details.currency
         = payment.currency
        payment.details.total
         = payment.total
        table.insert(
        	transactions,
        	payment
        )
    end
    local by_when_created = function(left, right)
    	return left.when_created < right.when_created
    end
    table.sort(
    	transactions,
    	by_when_created
    )
    local data = {
    	transactions = transactions
    }
    return make_json_success(headers, data)
end

function api_get_trash(req, session, headers)
    local space_id = req:stash("first")
    if space_id then
        local ok, result = space.get_trash(
        	space_id,
        	session.user_id,
        	session.admin
        )
        if ok then
            result.space_id = space_id
            return make_json_success(headers, result)
        else
            return result_from_message(headers, result)
        end
    else
        return result_from_message(headers, "ERR_BAD_REQUEST")
    end
end

function api_get_whole_project(req, session, headers)
    local space_id = req:stash("first")
    local ok, result = space.get_whole_project(
    	space_id,
    	session.user_id,
    	session.roles
    )
    if ok then
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_handler(req)
    local ok, result = xpcall(
    	function()
    		return api_handler_kernel(req)
    	end,
    	on_error
    )
    if ok then
        return result
    else
        db.clean_up()
        return {
        	status = 500,
        	body = '{"error": "ERR_SERVER"}'
        }
    end
end

function api_handler_kernel(req)
    local api_method = req:stash("method")
    local method = req:method()
    local headers = {}
    expires_now(headers)
    local session = handle_cookie(req, headers)
    local handler = find_api_handler(
    	api_method,
    	method,
    	session
    )
    local resp = handler(req, session, headers)
    return resp
end

function api_logon(req, session, headers)
    local data = req:json()
    if ((data) and (data.user)) and (data.password) then
        local ok, msg, user_id, email, new_session = vud.logon(
        	session.session_id,
        	data.user,
        	data.password
        )
        if ok then
            set_session_cookie(
            	headers,
            	new_session
            )
            local data = {
            	id = user_id,
            	name = msg,
            	email = email
            }
            return make_json_success(headers, data)
        else
            return make_json_error(
            	401,
            	headers,
            	msg
            )
        end
    else
        return make_json_error(
        	400,
        	headers,
        	"ERR_BAD_REQUEST"
        )
    end
end

function api_logout(req, session, headers)
    vud.logout(session.session_id)
    return make_empty_response(headers)
end

function api_many(req, session, headers)
    local _sw8300000_ = 0
    local data = req:json()
    local message = nil
    _sw8300000_ = data.operation
    if _sw8300000_ == "delete" then
        message = space.delete_many(
        	data.items,
        	session.user_id,
        	session.admin
        )
        if message then
            return result_from_message(headers, message)
        else
            return make_empty_response(headers)
        end
    else
        if _sw8300000_ == "copy" then
            message = space.copy_many(
            	data.items,
            	data.target.space_id,
            	data.target.folder_id,
            	session.user_id,
            	session.admin
            )
            if message then
                return result_from_message(headers, message)
            else
                return make_empty_response(headers)
            end
        else
            if _sw8300000_ == "move" then
                local ok, result = space.move_many(
                	data.items,
                	data.target.space_id,
                	data.target.folder_id,
                	session.user_id,
                	session.admin
                )
                if ok then
                    return make_json_success(headers, result)
                else
                    message = result
                    return result_from_message(headers, message)
                end
            else
                message = "ERR_BAD_OP"
                return result_from_message(headers, message)
            end
        end
    end
end

function api_multi_access(req, session, headers)
    local data = req:json()
    local msg, count = space.multi_access(
    	data,
    	session.user_id,
    	session.admin
    )
    if msg then
        if (msg == "ERR_USER_LIMIT") and (count) then
            local suggested = suggest_product(
            	session,
            	count,
            	get_product_user_limit
            )
            suggested = suggested or "team_more"
            local error_data = {
            	error = msg,
            	suggested = suggested
            }
            return make_json_response(400, headers, error_data)
        else
            return result_from_message(headers, msg)
        end
    else
        return make_empty_response(headers)
    end
end

function api_pass(req, session, headers)
    local data = req:json()
    if data then
        local msg = vud.change_password(
        	session.user_id,
        	data.old_password,
        	data.new_password
        )
        if msg then
            return make_json_error(
            	400,
            	headers,
            	msg
            )
        else
            return make_empty_response(headers)
        end
    else
        return make_json_error(
        	400,
        	headers,
        	"ERR_BAD_REQUEST"
        )
    end
end

function api_payex_callback(req, session, headers)
    local text = req:read()
    local ip = get_client_ip(req)
    if (ip == external_creds.payex_callback_ip) or (ip == "127.0.0.1") then
        ej.info("payex_callback", {text = text})
        db.run_in_fiber(function()
        		px2_callback(text)
        	end,
        	"payex_callback"
        )
    else
        ej.info("unauthorized_payex_callback", {text = text, ip=ip})
    end
    local resp = {
    	status = 200,
    	body = "OK",
    	headers = headers
    }
    return resp
end

function api_post_adm(req, session, headers)
    local op = req:stash("first")
    if op == "reset" then
        local data = req:json()
        local user = data.user
        local password = data.password
        local msg = vud.set_password(
        	session.user_id,
        	user,
        	password
        )
        if msg then
            return result_from_message(headers, msg)
        else
            return make_empty_response(headers)
        end
    else
        return result_from_message(headers, "ERR_BAD_OP")
    end
end

function api_px2_complete(req, session, headers)
    local data = req:json()
    local order_ref = data.order_ref
    local user_id = session.user_id
    local message = nil
    local receipt = {}
    local agr_ok, agreement_id, agreement
     = find_agreement_by_order_ref(order_ref, user_id)
    if (agr_ok) and (agreement.user_id == user_id) then
        local user = vud.get_user(user_id)
        if user then
            local language = get_user_language(
            	user_id
            )
            if agreement.state == "active" then
                ej.info(
                  "skipping_complete",
                  {
                    user_id = user_id,
                    reason = "callback called before, agreement active",
                    agreement_id = agreement_id,
                    order_ref = order_ref
                  }
                )
                receipt = build_receipt(
                      agreement_id,
                      agreement,
                      language
                )
                receipt.type = "card"
                return make_json_success(headers, receipt)
            else
                local ok, result = payex.complete(
                	agreement_id,
                	order_ref,
                	user_id
                )
                if ok then
                    receipt = on_px2_complete(
                    	user_id,
                    	agreement_id,
                    	agreement,
                    	result,
                    	language
                    )
                    receipt.type = "card"
                    return make_json_success(headers, receipt)
                else
                    message = "ERR_COMPLETE_PAYMENT"
                    return result_from_message(headers, message)
                end
            end
        else
            message = "ERR_USER_NOT_FOUND"
            return result_from_message(headers, message)
        end
    else
        message = "ERR_TRANS_NOT_FOUND"
        return result_from_message(headers, message)
    end
end

function api_px2_start(req, session, headers)
    local message
    local data = req:json()
    if (data.users) and (data.product_id) then
        local ok, pay_details = px2_calculate_payment(
        	data.product_id,
        	data.users,
        	session.user_id
        )
        if ok then
            local user = db.user_get(
            	session.user_id)[3]
            local language = get_user_language(
            	session.user_id
            )
            pay_details.trace = {}
            pay_details.trace.buy = data.ref
            pay_details.trace.reg = user.reg
            pay_details.trace.path = user.path
            pay_details.trace.ref = user.ref
            pay_details.trace.ip = user.ip
            pay_details.language = language
            local agreement_id = create_agreement(
            	pay_details
            )
            local ok2, ca_details = payex.create_agreement(
            	pay_details,
            	agreement_id,
            	session.user_id
            )
            if ok2 then
                update_agreement(
                  agreement_id,
                  {
                    agreement_ref = ca_details.agreement_ref
                  }	
                )
                local ip = get_client_ip(req)
                local payment = {
                  agreement_id = agreement_id,
                  total = pay_details.total,
                  currency = pay_details.currency,
                  product_code = pay_details.product_code,
                  product_pay_name = pay_details.product_pay_name,
                  ip = ip,
                  language = "en-US",
                  user_id = session.user_id,
                  agreement_ref = ca_details.agreement_ref
                }
                local ok3, cr_result = payex.initialize(
                	payment,
                	session.user_id
                )
                if ok3 then
                    db.agreement_update_order(
                    	agreement_id,
                    	cr_result.order_ref
                    )
                    local details = {
                    	redirect = cr_result.redirect,
                    	agreement_id = agreement_id
                    }
                    return make_json_success(headers, details)
                else
                    message = "ERR_ERROR"
                    return result_from_message(headers, message)
                end
            else
                message = "ERR_ERROR"
                return result_from_message(headers, message)
            end
        else
            message = "ERR_ERROR"
            return result_from_message(headers, message)
        end
    else
        message = "ERR_ERROR"
        return result_from_message(headers, message)
    end
end

function api_recent(req, session, headers)
    local ok, result = space.get_recent(
    	session.user_id
    )
    if ok then
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_reset_pass(req, session, headers)
    local data = req:json()
    if data then
        local language = choose_language(
        	req,
        	session,
        	""
        )
        local ok, data = vud.reset_password(
        	data.user_email,
        	session.session_id,
        	language
        )
        if ok then
            return make_json_success(headers, data)
        else
            return make_json_error(
            	400,
            	headers,
            	data
            )
        end
    else
        return make_json_error(
        	400,
        	headers,
        	"ERR_BAD_REQUEST"
        )
    end
end

function api_restore(req, session, headers)
    local space_id = req:stash("first")
    local folder_id = req:stash("second")
    local msg = space.restore(
    	space_id,
    	folder_id,
    	session.user_id,
    	session.admin
    )
    if msg then
        return result_from_message(headers, msg)
    else
        return make_empty_response(headers)
    end
end

function api_restore_backup(req, session, headers)
    local space_id = req:stash("first")
    local body = req:read()
    local ok, result = space.restore_backup(
    	space_id,
    	body,
    	session.user_id,
    	session.roles
    )
    if ok then
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_save_diatest(req, session, headers)
    if global_cfg.diatest then
        local data = req:json()
        if is_null(data.oldId) then
            
        else
            local epath = make_fixture_name(data.oldId)
            os.remove(epath)
        end
        local content = json.encode(data.fixture)
        local path = make_fixture_name(data.id)
        utils.write_all_bytes(path, content)
        return make_empty_response(headers)
    else
        return result_from_message(headers, "ERR_NOT_FOUND")
    end
end

function api_save_try(req, session, headers)
    local data = req:json()
    local ok, result, count = space.save_try(
    	data,
    	session.user_id
    )
    if ok then
        local response = {
        	diagram_id = result
        }
        return make_json_success(headers, response)
    else
        if (result == "ERR_DIAGRAM_LIMIT") and (count) then
            local suggested = suggest_product(
            	session,
            	count,
            	get_product_diagram_limit
            )
            local error_data = {
            	error = result,
            	suggested = suggested
            }
            return make_json_response(
            	400,
            	headers,
            	error_data
            )
        else
            return result_from_message(headers, result)
        end
    end
end

function api_set_ref(req, session, headers)
    local data = req:json()
    vud.set_session_ref(
    	session.session_id,
    	data.ref
    )
    return make_empty_response(headers)
end

function api_set_theme(req, session, headers)
    local data = req:json()
    if data then
        local user = vud.get_user(session.user_id)
        if user then
            space.set_theme(session.user_id, data)
            return make_empty_response(headers)
        else
            return make_json_error(400, headers, "ERR_USER_NOT_FOUND")
        end
    else
        return make_json_error(
        	400,
        	headers,
        	"ERR_BAD_REQUEST"
        )
    end
end

function api_start_search(req, session, headers)
    local _sw41710000_ = 0
    local msg, ok, result
    local data = req:json()
    if (data.spaces) and (data.needle) then
        local space_id = data.spaces[1]
        if space_id then
            _sw41710000_ = data.type
            if _sw41710000_ == "folders" then
                ok, result = space.find_folders(
                	space_id,
                	data.needle,
                	session.user_id,
                	session.admin
                )
                if ok then
                    return make_json_success(headers, result)
                else
                    msg = result
                    return make_json_error(
                    	400,
                    	headers,
                    	msg
                    )
                end
            else
                if _sw41710000_ == "items" then
                    ok, result = space.search_items_start(
                    	session.session_id,
                    	space_id,
                    	data.needle,
                    	session.user_id,
                    	session.admin
                    )
                    if ok then
                        return make_json_success(headers, result)
                    else
                        msg = result
                        return make_json_error(
                        	400,
                        	headers,
                        	msg
                        )
                    end
                else
                    msg = "ERR_BAD_REQUEST"
                    return make_json_error(
                    	400,
                    	headers,
                    	msg
                    )
                end
            end
        else
            msg = "ERR_BAD_REQUEST"
            return make_json_error(
            	400,
            	headers,
            	msg
            )
        end
    else
        msg = "ERR_BAD_REQUEST"
        return make_json_error(
        	400,
        	headers,
        	msg
        )
    end
end

function api_stop_subscription(req, session, headers)
    db.begin()
    local message
    local user_id = session.user_id
    local user = vud.get_user(user_id)
    if user then
        local license_id = user.license
        local license = lic.get_raw_license(
        	license_id
        )
        if (license) and (license.active) then
            local agreement_id = license.agreement_id
            if agreement_id then
                local agreement = db.agreement_get(
                	agreement_id
                )
                if is_agreement_active(agreement) then
                    update_agreement(
                      agreement_id,
                      {
                        state = "cancelled"
                      }	
                    )
                    lic.update_license(
                    	license_id,
                    	{active = false}
                    )
                    update_users_spaces(user_id)
                    db.commit()
                    local payex_action = function()
                      payex.delete_agreement(
                        agreement.agreement_ref,
                        user_id,
                        agreement_id
                      )
                    end
                    db.run_in_fiber(
                    	payex_action,
                    	"delete agreement on stop"
                    )
                    ej.info(
                    	"stop_subscription",
                    	{
                    		user_id = user_id,
                    		agreement_id = agreement_id,
                    		license_id = license_id,
                    		agreement_ref = agreement.agreement_ref
                    	}
                    )
                    return make_empty_response(headers)
                else
                    message = "ERR_NO_LICENSE"
                    db.rollback()
                    return result_from_message(headers, message)
                end
            else
                message = "ERR_NO_LICENSE"
                db.rollback()
                return result_from_message(headers, message)
            end
        else
            message = "ERR_NO_LICENSE"
            db.rollback()
            return result_from_message(headers, message)
        end
    else
        message = "ERR_USER_NOT_FOUND"
        db.rollback()
        return result_from_message(headers, message)
    end
end

function api_tree(req, session, headers)
    local space_id = req:stash("first")
    local ok, result = space.get_tree(
    	space_id,
    	session.user_id,
    	session.admin
    )
    if ok then
        result.user_name = session.name
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function api_trial(req, session, headers)
    local message = nil
    local data = req:json()
    db.begin()
    local user_id = session.user_id
    local user = vud.get_user(user_id)
    local product_id = data.product
    if user then
        if user.had_trial then
            message = "ERR_HAD_TRIAL"
            db.rollback()
            return result_from_message(headers, message)
        else
            if utils.is_empty(product_id) then
                message = "ERR_PRODUCT_MISSING"
                db.rollback()
                return result_from_message(headers, message)
            else
                local license = lic.get_raw_license(
                	user.license
                )
                if ((license) and (license.active)) and (not (license.product_id == "basic")) then
                    message = "ERR_TRIAL_NOT_ALLOWED"
                    db.rollback()
                    return result_from_message(headers, message)
                else
                    start_trial(
                    	session.session_id,
                    	user_id,
                    	user.license,
                    	product_id
                    )
                    db.commit()
                    return make_empty_response(headers)
                end
            end
        end
    else
        message = "ERR_USER_NOT_FOUND"
        db.rollback()
        return result_from_message(headers, message)
    end
end

function api_unsubscribe(req, session, headers)
    local data = req:json()
    if data then
        local msg = vud.unsubscribe(data)
        if msg then
            return make_json_error(
            	400,
            	headers,
            	msg
            )
        else
            return make_empty_response(headers)
        end
    else
        return make_json_error(
        	400,
        	headers,
        	"ERR_BAD_REQUEST"
        )
    end
end

function api_update_folder(req, session, headers)
    local space_id = req:stash("first")
    local folder_id = req:stash("second")
    local data = req:json()
    local msg = space.update_folder(
    	space_id,
    	folder_id,
    	data,
    	session.user_id,
    	session.admin
    )
    if msg then
        return result_from_message(headers, msg)
    else
        return make_empty_response(headers)
    end
end

function api_update_user(req, session, headers)
    local data = req:json()
    if data then
        local msg = vud.update_user(
        	session.user_id,
        	data
        )
        if msg then
            return make_json_error(
            	400,
            	headers,
            	msg
            )
        else
            return make_empty_response(headers)
        end
    else
        return make_json_error(
        	400,
        	headers,
        	"ERR_BAD_REQUEST"
        )
    end
end

function api_use_coupon(req, session, headers)
    local message = nil
    local data = req:json()
    db.begin()
    if data.coupon then
        local coupon = lic.get_coupon(data.coupon)
        if coupon then
            local ok, details = calculate_payment(
            	coupon.product_id,
            	coupon.max_users,
            	nil
            )
            if ok then
                details.coupon = data.coupon
                local trans_id = create_transaction(
                	session.user_id,
                	"coupon",
                	details
                )
                details.trans_id = trans_id
                redeem_coupon(
                	session.user_id,
                	data.coupon
                )
                complete_transaction(
                	session.user_id,
                	trans_id,
                	"success"
                )
                local agreement_id = nil
                create_license(
                	session.user_id,
                	details.product_id,
                	details.max_diagrams,
                	details.max_spaces,
                	details.users,
                	details.expiry,
                	trans_id,
                	agreement_id,
                	"coupon"
                )
                db.commit()
                ej.info(
                	"confirm_coupon",
                	{user_id=user_id, trans_id=trans_id,
                	coupon=data.coupon,
                	users=details.users,
                	product_id=details.product_id,
                	session_id=session.session_id}
                )
                local language = get_user_language(
                	session.user_id
                )
                local response = build_receipt(
                      trans_id,
                      details,
                      language
                )
                response.type = "coupon"
                response.trans_id = trans_id
                return make_json_success(headers, response)
            else
                message = details
                db.rollback()
                return result_from_message(
                	headers,
                	message
                )
            end
        else
            message = "ERR_COUPON_NOT_FOUND"
            db.rollback()
            return result_from_message(
            	headers,
            	message
            )
        end
    else
        message = "ERR_COUPON_NOT_FOUND"
        db.rollback()
        return result_from_message(
        	headers,
        	message
        )
    end
end

function api_visit_folder(req, session, headers)
    local space_id = req:stash("first")
    local folder_id = req:stash("second")
    local ok, result = space.get_folder(
    	space_id,
    	folder_id,
    	true,
    	session.user_id,
    	session.admin
    )
    if ok then
        result.user_name = session.name
        return make_json_success(headers, result)
    else
        return result_from_message(headers, result)
    end
end

function build_pay_description(details)
    return "Subscription: "
    .. tostring(details.product_id) .. ", "
    .. tostring(details.users) .. " user(s)"
end

function build_product_code(details)
    if details.product_id == "extended" then
        return details.product_id
    else
        return tostring(details.product_id) .. "-"
        .. tostring(details.users)
    end
end

function build_product_name(language, product_id, max_users)
    local product = utils.get_product(product_id)
    local name = translate(
    	language,
    	product.name
    )
    if product_id == "team" then
        local users = translate(
        	language,
        	"MES_NUM_USERS"
        )
        name = name .. ". " .. users .. ": "
          .. tostring(max_users)
    end
    name = name:gsub("<br />", " ")
    return name
end

function build_receipt(agreement_id, agreement, language)
    local currency = agreement.currency
    local date = localize_time(
    	language,
    	os.time()
    )
    local product_name = translate(
    	language,
    	agreement.product_name
    )
    local result = {}
    result.product_name = product_name
    result.max_diagrams = get_maxed_number(language, agreement.max_diagrams)
    result.max_spaces = get_maxed_number(language, agreement.max_spaces)
    result.users = tostring(agreement.users)
    result.price = print_amount(currency, agreement.price)
    result.total = print_amount(currency, agreement.total)
    result.total_num = agreement.total
    result.trans_id = agreement_id
    result.date = date
    result.mva = print_amount(currency, agreement.mva)
    result.currency = currency
    result.trans_number = agreement.payment1
    return result
end

function calc_own(user_id)
    local spaces = get_own_spaces(user_id)
    return #spaces
end

function calc_period(product)
    local period_sec = utils.months_to_secs(product.period_mon)
      + utils.days_to_secs(1)
    return period_sec
end

function calculate_due_time()
    return utils.add_months(
    	os.time(),
    	1
    )
end

function calculate_payment(product_id, users, old_license)
    local product = utils.get_product(
    	product_id
    )
    if (product) and (not (users > product.max_users)) then
        local now = os.time()
        local details = pay.calculate_payment(
        	users,
        	product_id,
        	old_license,
        	price_cfg,
        	now
        )
        if details.error then
            return false, details.error
        else
            details.users = users
            details.product_id = product_id
            details.product_name = product.name
            details.max_diagrams = product.max_diagrams
            details.max_spaces = product.max_spaces
            details.currency = price_cfg.currency
            return true, details
        end
    else
        return false, "MES_BAD_PRODUCT"
    end
end

function check_license(user_id)
    local action = function()
    	check_license_core(user_id)
    end
    db.invoke_no_throw(
    	action,
    	"check_license " .. user_id
    )
end

function check_license_core(user_id)
    if user_id == "admin" then
        
    else
        local user = vud.get_user(user_id)
        if user then
            local license_id = user.license
            local license = lic.get_raw_license(license_id)
            if license then
                local agreement_id = license.agreement_id
                if license.active then
                    local now = os.time()
                    local expiry = license.expiry
                    if now > expiry then
                        db.begin()
                        lic.update_license(
                        	license_id,
                        	{active = false}
                        )
                        update_users_spaces(user_id)
                        db.commit()
                        ej.info(
                        	"license_expiration",
                        	{user_id=user_id,
                        	license_id=license_id}
                        )
                        module.send_expiration_notice(user_id)
                    else
                        local left = expiry - now
                        if (left > WARNING_BEFORE) or (license.warning_sent) then
                            
                        else
                            lic.update_license(
                            	license_id,
                            	{warning_sent = true}
                            )
                            module.send_expiration_soon(
                            	user_id,
                            	expiry
                            )
                        end
                        if license.agreement_id then
                            local agreement = db.agreement_get(
                            	license.agreement_id
                            )
                            if ((agreement) and (agreement.state == "active")) and (now > agreement.due) then
                                try_autopay(agreement_id)
                            end
                        end
                    end
                end
            end
        end
    end
end

function check_scheduled(agreement_id)
    local action = function()
    	try_autopay(agreement_id)
    end
    db.invoke_no_throw(
    	action,
    	"try_autopay " .. agreement_id
    )
end

function choose_language(req, session, url_language)
    local acc_lang = req:header("accept-language")
    local chosen = req:cookie("language")
    local language = find_language(
    	session.user_id,
    	chosen,
    	acc_lang,
    	url_language
    )
    local norm = norm_language(language)
    return norm
end

function complete_transaction(user_id, trans_id, status)
    lic.update_transaction(
    	trans_id,
    	{completed=true, status=status}
    )
    ej.info(
    	"complete_transaction",
    	{user_id=user_id, trans_id=trans_id, status=status}
    )
end

function cookie_date(time)
    return os.date("!%a, %Y %b %d %H:%M:%S GMT", time)
end

function count_admins(space_id)
    local rights = db.rights_get_by_space(
    	space_id
    )
    local count = 0
    for _, right in ipairs(rights) do
        local access = right[3]
        if access == "admin" then
            count = count + 1
        end
    end
    return count
end

function create_agreement(details)
    local id
    while true do
        id = utils.random_string()
        id = id:sub(1, 8)
        local old = db.agreement_get(id)
        if old then
            
        else
            break
        end
    end
    local data = utils.copy(details)
    data.state = "created"
    data.when_created = clock.time()
    data.when_updated = data.when_created
    local year = 366 * 24 * 3600
    data.expiry = data.when_created + year
    db.agreement_insert(
    	id,
    	"",
    	data
    )
    return id
end

function create_cinfo(page_code, content_path, content, title, description)
    local self = {}
    self.page_code = page_code or ""
    self.content_path = content_path or ""
    self.content = content or ""
    self.title = title or ""
    self.description = description or ""
    return self
end

function create_content_cinfo(language, content_path)
    local page_code
    if content_path == "" then
        page_code = "main"
    else
        page_code = content_path
    end
    local content = load_content(
    	global_cfg.content_dir,
    	language,
    	page_code,
    	"html"
    )
    local title = get_content_element(language, "title", page_code)
    local description = get_content_element(language, "desc", page_code)
    local cinfo = create_cinfo(
    	page_code,
    	content_path,
    	content,
    	title,
    	description
    )
    return cinfo
end

function create_default_license(user_id, tag)
    local product_id
    local max_users
    local period
    local product = utils.get_product(
    	global_cfg.create_license
    )
    if global_cfg.on_premises then
        max_users = 500
        period = 20 * 12 * utils.days_to_secs(30)
        product_id = "team"
    else
        max_users = product.max_users
        product_id = global_cfg.create_license
        
        period = utils.months_to_secs(
        	product.period_mon
        )
    end
    local user = vud.get_user(user_id)
    if (user) and (not (user.license)) then
        local product = utils.get_product(product_id)
        local start = os.time()
        local expiry = start + period
        local trans_id = nil
        local agreement_id = nil
        create_license_for_user(
        	user_id,
        	product_id,
        	product.max_diagrams,
        	product.max_spaces,
        	max_users,
        	expiry,
        	trans_id,
        	agreement_id,
        	tag
        )
    end
end

function create_empty_cinfo(req, language)
    return create_cinfo(
    	nil,
    	nil,
    	nil,
    	nil,
    	nil
    )
end

function create_land_cinfo(req, language)
    local page_code = req:stash("id")
    local content_path = "land/" .. page_code
    local info_json = load_content(
    	global_cfg.read_dir,
    	language,
    	page_code,
    	"json"
    )
    if info_json then
        local page_info = json.decode(info_json)
        local cinfo = create_cinfo(
        	page_code,
        	content_path,
        	content,
        	page_info.title,
        	page_info.description
        )
        cinfo.skip_header = page_info.skip_header
        cinfo.skip_footer = page_info.skip_footer
        cinfo.blocks = page_info.blocks
        return cinfo
    else
        return nil, "ERR_NOT_FOUND"
    end
end

function create_license(user_id, product_id, max_diagrams, max_spaces, users, expiry, trans_id, agreement_id, tag)
    local user = vud.get_user(user_id)
    local old = lic.delete_license(user.license)
    create_license_for_user(
    	user_id,
    	product_id,
    	max_diagrams,
    	max_spaces,
    	users,
    	expiry,
    	trans_id,
    	agreement_id,
    	tag
    )
    return old
end

function create_license_for_user(user_id, product_id, max_diagrams, max_spaces, users, expiry, trans_id, agreement_id, tag)
    local limits = {
    	max_diagrams = max_diagrams,
    	max_spaces = max_spaces,
    	max_users = users
    }
    local license_id = lic.create_license(
    	user_id,
    	product_id,
    	expiry,
    	limits,
    	trans_id,
    	agreement_id,
    	tag
    )
    vud.set_user_prop(
    	user_id,
    	"license",
    	license_id
    )
    update_users_spaces(user_id)
    ej.info(
    	"create_license",
    	{user_id=user_id, license_id=license_id, tag=tag}
    )
    return license_id
end

function create_payment(user_id, agreement_id, total, mva, currency, trans_number, method, users, product_id, product_name)
    db.payment_insert(
    	user_id,
    	{
    		user_id = user_id,
    		agreement_id = agreement_id,
    		total = total,
    		mva = mva,
    		currency = currency,
    		trans_number = trans_number,
    		method = method,
    		when_created = os.time(),
    		details = {
    			users = users,
    			product_id = product_id,
    			product_name = product_name
    		}
    	}
    )
    ej.info(
    	"payment",
    	{
    		user_id = user_id,
    		agreement_id = agreement_id,
    		total = total,
    		mva = mva,
    		currency = currency,
    		trans_number = trans_number
    	}
    )
end

function create_read_cinfo(req, language)
    local page_code = req:stash("id")
    local content_path = "read/" .. page_code
    local content = load_content(
    	global_cfg.read_dir,
    	language,
    	page_code,
    	"html"
    )
    local info_json = load_content(
    	global_cfg.read_dir,
    	language,
    	page_code,
    	"json"
    )
    if info_json then
        local page_info = json.decode(info_json)
        local cinfo = create_cinfo(
        	page_code,
        	content_path,
        	content,
        	page_info.title,
        	page_info.description
        )
        cinfo.skip_header = page_info.skip_header
        cinfo.skip_footer = page_info.skip_footer
        return cinfo
    else
        return nil, "ERR_NOT_FOUND"
    end
end

function create_transaction(user_id, type, details)
    local now = os.time()
    local timeout = 20 * 60
    local tdata = {
    	details = details,
    	expiry = now + timeout
    }
    local trans_id = lic.create_transaction(
    	user_id,
    	type,
    	tdata
    )
    return trans_id
end

function create_user_core(req, session, headers, data, password)
    local msg, language
    local ip = get_client_ip(req)
    db.begin()
    msg = vud.create_user(
    	data.name,
    	data.email,
    	password,
    	session.session_id,
    	data.src,
    	ip
    )
    if msg then
        db.rollback()
        return false, msg
    else
        local user_id = data.name:lower()
        space.create_space(user_id, user_id)
        if data.language then
            language = data.language
        else
            language = choose_language(req, session, "")
        end
        local theme = {language = language}
        space.set_theme(user_id, theme)
        ej.info(
        	"create_user",
        	{user_id=user_id, src=data.src,
        	session_id=session.session_id}
        )
        local ok, msg, user_id, email, new_session = vud.logon(
        	session.session_id,
        	data.name,
        	password
        )
        db.commit()
        local result = {
        	id = user_id,
        	name = data.name,
        	language = language,
        	email = data.email
        }
        return true, result, new_session
    end
end

function decode_data_url(data)
    local start = "data:image/png;base64,"
    if #data < #start then
        return data
    else
        local head = data:sub(
        	1,
        	#start
        )
        if head == start then
            local body = data:sub(
            	#start + 1,
            	#data
            )
            return digest.base64_decode(body)
        else
            return data
        end
    end
end

function delete_agreement(agreement_id)
    if agreement_id then
        local agreement = db.agreement_get(agreement_id)
        if agreement then
            db.agreement_delete(agreement_id)
            local delayed_call = function()
            	payex.delete_agreement(
            		agreement.agreement_ref,
            		agreement.user_id,
            		agreement_id
            	)
            end
            db.run_in_fiber(
            	delayed_call,
            	"delete agreement in pay ex"
            )
            ej.info(
            	"delete_agreement",
            	{
            		user_id = agreement.user_id,
            		agreement_id = agreement_id,
            		agreement_ref = agreement.agreement_ref
            	}
            )
        end
    end
end

function delete_download(download_id)
    local timeout_min = 10
    local timeout = timeout_min * 60
    fiber.sleep(timeout)
    take_download(download_id)
end

function delete_user(user_id)
    db.begin()
    local user = vud.get_user(user_id)
    if user then
        space.delete_recent(user_id)
        local own_spaces = get_own_spaces(user_id)
        for _, space_id in ipairs(own_spaces) do
            db.rights_delete(
            	space_id,
            	user_id,
            	"admin"
            )
            if count_admins(space_id) == 0 then
                space.delete_space(
                	space_id,
                	user_id,
                	true
                )
            else
                space.update_space_limits(space_id)
            end
        end
        space.delete_theme(user_id)
        vud.logout_all(user_id)
        vud.delete_user(user_id)
    end
    db.commit()
end

function delete_user_payments(user_id)
    local all = db.payment_get_by_user(user_id)
    for _, tran in ipairs(all) do
        local id = tran[1]
        db.payment_delete(id)
    end
end

function download_svg(req, headers, record)
    local original = record.data.image
    local status = 200
    local filename = record.filename
    local output = original
    set_content_type(headers, filename)
    headers["content-disposition"] =
      "attachment; filename=" .. filename
    local start = original:find("<defs")
    if start == nil then
        
    else
        local head = original:sub(1, start-1)
        local tail = original:sub(start)
        local tmp_filename = global_cfg.tmp .. "/"
         .. utils.random_string() .. ".down"
        local tmp_file = io.open(
        	tmp_filename,
        	"wb"
        )
        if tmp_file then
            tmp_file:write(head)
            tmp_file:write(
             "<style xmlns=\"http://www.w3.org/1999/xhtml\">\n"
            )
            for _, font in ipairs(record.data.fonts) do
                local font_filename = global_cfg.static_dir
                 .. "/fonts/" .. font.file:gsub(".ttf", ".woff.b64")
                local font_data = utils.read_all_bytes(
                	font_filename
                )
                write_font_header(font, tmp_file)
                tmp_file:write(font_data)
                write_font_footer(font, tmp_file)
            end
            tmp_file:write("\n\n</style>")
            tmp_file:write(tail)
            tmp_file:close()
            output = utils.read_all_bytes(
            	tmp_filename
            )
            os.remove(tmp_filename)
        end
    end
    local resp = {
    	status = status,
    	body = output,
    	headers = headers
    }
    return resp
end

function expires_later(headers)
    local header = string.format(
     "max-age=%d",
     global_cfg.static_timeout
    )
    headers["cache-control"] = header
end

function expires_now(headers)
    headers["cache-control"] = 
     "no-cache, private, no-store, must-revalidate, max-age=0"
    headers["expires"] = "0"
end

function find_agreement_by_order_ref(order_ref, user_id)
    local found = db.agreement_get_by_order_ref(order_ref)
    for _, row in ipairs(found) do
        local fields = row[3]
        if fields.user_id == user_id then
            return true, row[1], fields
        end
    end
    ej.info(
      "agreement_not_found",
      {
        order_ref = order_ref,
        user_id = user_id
      }
    )
    return false
end

function find_api_handler(api_method, method, session)
    local key = api_method .. "/" .. method
    local handler_info = api_handlers[key]
    if handler_info then
        if handler_info.must_admin then
            if session.admin then
                return handler_info.handler
            else
                ej.info(
                	"admin api failed",
                	{user_id = session.user_id, key=key}
                )
                return wrong_api_handler
            end
        else
            if handler_info.must_logon then
                if session.user_id == "" then
                    return not_logged_handler
                else
                    return handler_info.handler
                end
            else
                return handler_info.handler
            end
        end
    else
        log.error("api hander not found: " .. key)
        return wrong_api_handler
    end
end

function find_in_block(language_list, block)
    local bparts = utils.split(block, ";")
    for _, bpart in ipairs(bparts) do
        local found = language_list[bpart]
        if found then
            return found
        end
    end
    return nil
end

function find_language(user_id, chosen, acc_lang, url_language)
    local language = "en-us"
    local success, uset, language_blocks
    local found
    if url_language == "" then
        if user_id == "" then
            if chosen then
                language = chosen
            else
                if (acc_lang) and (not (acc_lang == "")) then
                    acc_lang = string.lower(acc_lang)
                    language_blocks = utils.split(
                    	acc_lang,
                    	","
                    )
                    for _, block in ipairs(language_blocks) do
                        found = find_in_block(
                        	language_list,
                        	block
                        )
                        if found then
                            language = found
                            break
                        end
                    end
                end
            end
        else
            success, uset = space.get_theme(user_id)
            if (success) and (uset.language) then
                language = uset.language
            else
                if chosen then
                    language = chosen
                else
                    if (acc_lang) and (not (acc_lang == "")) then
                        acc_lang = string.lower(acc_lang)
                        language_blocks = utils.split(
                        	acc_lang,
                        	","
                        )
                        for _, block in ipairs(language_blocks) do
                            found = find_in_block(
                            	language_list,
                            	block
                            )
                            if found then
                                language = found
                                break
                            end
                        end
                    end
                end
            end
        end
    else
        language = url_language
    end
    return language
end

function find_payment_by_user_trans_num(user_id, trans_number)
    local all = db.payment_get_by_user(user_id)
    for _, tran in ipairs(all) do
        local payment = tran[3]
        if payment.trans_number == trans_number then
            return payment
        end
    end
    return nil
end

function find_relevant_agreement(order_ref)
    local found = db.agreement_get_by_order_ref(order_ref)
    for _, row in ipairs(found) do
        local fields = row[3]
        if (fields.state == "active") or (fields.state == "created") then
            return true, row[1], fields
        end
    end
    ej.info(
      "agreement_not_found",
      {
        order_ref = order_ref
      }
    )
    return false
end

function ga()
    return [[
    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
    
      ga('create', 'UA-85987998-1', 'auto');
      ga('send', 'pageview');
    
    </script>
    ]]
end

function get_access_for_user(user_id, output)
    local rights = db.rights_get_by_user(
    	user_id
    )
    local space_list = {}
    local access_list = {}
    local by_space = utils.group_by(
    	rights,
    	1
    )
    for space_id, group in pairs(by_space) do
        local access = get_max_access(group)
        local space_info = {
        	space_id = space_id,
        	access = access
        }
        table.insert(
        	access_list,
        	space_info
        )
        table.insert(
        	space_list,
        	space_id
        )
    end
    output.spaces_access = access_list
    output.spaces = space_list
end

function get_client_ip(req)
    local ip = req:header("x-real-ip")
    if ip then
        
    else
        ip = req:peer().host
    end
    return ip
end

function get_completed_trans(user_id, trans_id)
    if trans_id then
        local transaction = lic.get_transaction(trans_id)
        if ((transaction) and (transaction.user_id == user_id)) and (transaction.completed) then
            return transaction
        else
            return nil
        end
    else
        return nil
    end
end

function get_content_element(language, prefix, path)
    if path then
        local key = prefix .. "-" .. path
        return trans.translate(
        	language,
        	"index",
        	key
        )
    else
        return ""
    end
end

function get_license_info(user_id)
    local result = {
    	current_plan = "none",
    	had_trial = false,
    	is_trial = false,
    	ref = ""
    }
    if utils.is_empty(user_id) then
        
    else
        local user_row = vud.find_user(user_id)
        if user_row then
            local user = user_row[3]
            result.had_trial = user.had_trial or false
            result.current_plan = "basic"
            result.ref = user.ref or ""
            local license = lic.get_license(user.license)
            if license then
                result.is_trial = license.is_trial or false
                result.current_plan = license.product_id
            end
        end
    end
    return result
end

function get_max_access(rows)
    local access = {}
    for _, row in ipairs(rows) do
        local right = row[3]
        access[right] = true
    end
    if access.admin then
        return "admin"
    else
        if access.write then
            return "write"
        else
            return "read"
        end
    end
end

function get_maxed_number(language, number)
    if number >= 10000 then
        return translate(language, "MES_UNLIMITED")
    else
        return tostring(number)
    end
end

function get_next_product(product_id)
    if product_id == "basic" then
        return "extended"
    else
        if product_id == "trial" then
            return "team"
        else
            if product_id == "extended" then
                return "team"
            else
                if product_id == "team" then
                    
                else
                    error(product_id)
                end
                return nil
            end
        end
    end
end

function get_old_license(license_id)
    if license_id then
        local license = lic.get_license(license_id)
        if license then
            license.expiry = license.expiry or os.time()
            license.sum = license.sum or 0
            license.period = license.period or 366 * 24 * 3600
            return license
        else
            return nil
        end
    else
        return nil
    end
end

function get_only_own_spaces(user_id)
    local spaces = get_own_spaces(user_id)
    return utils.filter(
    	spaces,
    	has_one_admin
    )
end

function get_own_spaces(user_id)
    local result = {}
    local rights = db.rights_get_by_user(
    	user_id
    )
    for _, right in ipairs(rights) do
        local space_id = right[1]
        local access = right[3]
        if access == "admin" then
            table.insert(
            	result,
            	space_id
            )
        end
    end
    table.sort(result)
    return result
end

function get_product_diagram_limit(product_id)
    local product = utils.get_product(product_id)
    return product.max_diagrams
end

function get_product_space_limit(product_id)
    local product = utils.get_product(product_id)
    return product.max_spaces
end

function get_product_user_limit(product_id)
    local product = utils.get_product(product_id)
    return product.max_users
end

function get_renew_path(language)
    local lp = utils.make_language_path(language)
    return global_cfg.my_site .. "/" .. lp .. "/buy"
end

function get_req()
    return greq
end

function get_space_limit(user_id)
    local limits = space.get_user_limits(user_id)
    return limits.max_spaces
end

function get_trans(user_id, trans_id)
    if trans_id then
        local transaction = lic.get_transaction(trans_id)
        if (transaction) and (transaction.user_id == user_id) then
            return transaction
        else
            return nil
        end
    else
        return nil
    end
end

function get_uncompleted_trans(user_id, trans_id)
    if trans_id then
        local transaction = lic.get_transaction(trans_id)
        if ((transaction) and (transaction.user_id == user_id)) and (not (transaction.completed)) then
            local now = os.time()
            if transaction.expiry < now then
                return nil
            else
                return transaction
            end
        else
            return nil
        end
    else
        return nil
    end
end

function get_user_language(user_id)
    local success, uset = space.get_theme(user_id)
    local language
    if (success) and (uset.language) then
        language = norm_language(uset.language)
    else
        language = "en-us"
    end
    return language
end

function handle(filename)
    local handler = function(req)
    	return handle_file_for_path(req, filename)
    end
    return handler
end

function handle_cookie(req, headers)
    local ip = get_client_ip(req)
    local referer = req:header("referer")
    local path = req:path()
    local session_id = req:cookie("session_id")
    local session = vud.get_create_session(
    	session_id,
    	ip,
    	referer,
    	path,
    	true
    )
    if session_id == session.session_id then
        
    else
        set_session_cookie(headers, session.session_id)
    end
    return session
end

function handle_file_for_path(req, filename)
    local headers = {}
    handle_cookie(req, headers)
    return serve_file(req, headers, filename)
end

function handle_template(req, page, url_language, caching_allowed, cinfo_maker)
    local headers = {}
    if caching_allowed then
        
    else
        expires_now(headers)
    end
    local session = handle_cookie(req, headers)
    local referer = req:header("referer") or ""
    local language = choose_language(
    	req,
    	session,
    	url_language
    )
    log_page_request(req, session, language)
    local cinfo, error_message = cinfo_maker(req, language)
    if error_message then
        return result_from_message(headers, error_message)
    else
        local license_info = get_license_info(session.user_id)
        local vars = {
        	math = math,
        	my_site = global_cfg.my_site,
        	feedback_email = global_cfg.feedback_email,
        	content_path = cinfo.content_path,
        	page_code = cinfo.page_code,
        	title = cinfo.title,
        	description = cinfo.description,
        	content = cinfo.content,
        	skip_header = cinfo.skip_header or false,
        	skip_footer = cinfo.skip_footer or false,
        	blocks = cinfo.blocks or {},
        	language = language,
        	short_language = language:sub(1, 2),
        	page = page,
        	trans = make_trans(language, page),
        	messages = global_cfg.messages[language],
        	user_id = session.user_id or "",
        	user_name = session.name or "",
        	admin = session.admin or false,
        	debug = session.debug,
        	url_language = url_language,
        	try_me = (req:path() == "/try-me"),
        	build_id = global_cfg.build_id,
        	use_capture = global_cfg.use_capture,
        	include = include,
        	referer = referer,
        	yandex_metrika = global_cfg.yandex_metrika or false,
        	carrot = global_cfg.carrot or false,
        	capterra = global_cfg.capterra or false,
        	on_premises = global_cfg.on_premises or false,
        
        	currency = price_cfg.currency,
        	min_payment = price_cfg.min_payment,
        
        	basic_max_diagrams = price_cfg.products.basic.max_diagrams,
        	basic_max_spaces = price_cfg.products.basic.max_spaces,
        	basic_period = price_cfg.products.basic.period_mon,
        
        
        	extended_price = price_cfg.products.extended.price,
        	extended_max_diagrams = price_cfg.products.extended.max_diagrams,
        	extended_max_spaces = price_cfg.products.extended.max_spaces,
        	extended_period = price_cfg.products.extended.period_mon,
        	extended_min_users = price_cfg.products.extended.min_users,
        	extended_max_users = price_cfg.products.extended.max_users,
        
        	team_price = price_cfg.products.team.price,
        	team_max_diagrams = price_cfg.products.team.max_diagrams,
        	team_max_spaces = price_cfg.products.team.max_spaces,
        	team_period = price_cfg.products.team.period_mon,
        	team_min_users = price_cfg.products.team.min_users,
        	team_max_users = price_cfg.products.team.max_users,
        
        	current_plan = license_info.current_plan,
        	had_trial = license_info.had_trial,
        	is_trial = license_info.is_trial,
        	ref = license_info.ref,
        
        	mva = price_cfg.mva,
        
        	application = global_cfg.application,
        	paysys = external_creds.paysys,
        	complete_delay = global_cfg.complete_delay
        }
        if global_cfg.google_anal then
            vars.google_anal = ga()
        else
            vars.google_anal = ""
        end
        local resp = req:render(vars)
        for key, value in pairs(headers) do
            resp.headers[key] = value
        end
        resp.headers["final-language"] = language
        resp.headers["content-type"] = "text/html; charset=utf-8"
        resp.status = 200
        return resp
    end
end

function has_one_admin(space_id)
    local count = count_admins(space_id)
    return count == 1
end

function include(name)
    local result = '<script src="/static/' .. name
      .. global_cfg.build_id .. '.js"></script>'
    return result
end

function inject_send_mail(injected)
    send_mail = injected
end

function is_agreement_active(agreement)
    if (agreement) and ((agreement.state == "active") or (agreement.state == "retrying")) then
        return true
    else
        return false
    end
end

function is_null(item)
    if (item == nil) or (item == json.null) then
        return true
    else
        return false
    end
end

function land_route(path)
    local root_path = path
    local path_ru = "/ru" .. root_path
    local path_en = "/en" .. root_path
    local caching_allowed = true
    local file = "land.html.el"
    local cinfo_maker = create_land_cinfo
    router:route(
    	{path=path_ru, file=file},
    	make_template("index", "ru", caching_allowed, cinfo_maker)
    )
    router:route(
    	{path=path_en, file=file},
    	make_template("index", "en", caching_allowed, cinfo_maker)
    )
    router:route(
    	{path=root_path, file=file},
    	make_template("index", "", caching_allowed, cinfo_maker)
    )
end

function load_content(content_dir, language, page_code, extension)
    if page_code then
        local content
        local localized = localize_page_code(
        	page_code,
        	language
        )
        local path = content_dir .. "/" ..
          localized .. "." .. extension
        content = utils.read_all_bytes(path)
        return content
    else
        return ""
    end
end

function localize_page_code(page_code, language)
    if language == "ru" then
        return "ru-" .. page_code
    else
        return page_code
    end
end

function localize_time(language, time)
    local parts = os.date("*t", time)
    local month_id = "MES_MONTH_" .. tostring(parts.month)
    local month = translate(language, month_id)
    return tostring(parts.day) .. "-"
     .. month .. "-" .. tostring(parts.year)
end

function log_page_request(req, session, language)
    if req:path() == "/try-me" then
        ej.info(
        	"try-me",
        	{path=req:path(),
        	session_id=session.session_id,
        	language=language}
        )
    end
end

function make_empty_response(headers)
    local resp = {
    	status = 204,
    	headers = headers
    }
    return resp
end

function make_fixture_name(id)
    local path = global_cfg.diatest .. "/" .. id .. ".txt"
    return path
end

function make_json_error(status, headers, message)
    local data = {
    	error = message
    }
    return make_json_response(status, headers, data)
end

function make_json_response(status, headers, data)
    headers["content-type"] =
     "application/json; charset=utf8"
    local body = json.encode(data)
    local resp = {
    	status = status,
    	body = body,
    	headers = headers
    }
    return resp
end

function make_json_success(headers, data)
    return make_json_response(200, headers, data)
end

function make_template(page, url_language, caching_allowed, cinfo_maker)
    if cinfo_maker then
        
    else
        cinfo_maker = create_empty_cinfo
    end
    local result = function(req)
    	return handle_template(req, page, url_language, caching_allowed, cinfo_maker)
    end
    return result
end

function make_trans(language, page)
    return function(text)
    	return trans.translate(language, page, text)
    end
end

function norm_language(language)
    local norm = language_list[language]
    if norm then
        return norm
    else
        return "en-us"
    end
end

function not_logged_handler(req, session, headers)
    return make_json_error(401, headers, "ERR_NOT_LOGGED_ON")
end

function on_autopay_success(user_id, agreement_id, agreement, trans_number, method, language)
    db.begin()
    local payment = find_payment_by_user_trans_num(
    	user_id,
    	trans_number
    )
    if payment then
        db.rollback()
        return false
    else
        db.scheduled_delete(agreement_id)
        agreement = update_agreement(
          agreement_id,
          {
            state = "active",
            due = calculate_due_time()
          }	
        )
        create_payment(
        	user_id,
        	agreement_id,
        	agreement.total,
        	agreement.mva,
        	agreement.currency,
        	trans_number,
        	method,
        	agreement.users,
        	agreement.product_id,
        	agreement.product_name
        )
        db.commit()
        local receipt = build_receipt(
              agreement_id,
              agreement,
              language
        )
        receipt.trans_number = trans_number
        send_autopay_email(
        	user_id,
        	receipt,
        	language,
        	true
        )
        return true
    end
end

function on_error(err)
    local data = {
    	err = err,
    	stack = debug.traceback()
    }
    log.error(err)
    log.info(data.stack)
    ej.info("error", data)
end

function on_px2_complete(user_id, agreement_id, agreement, complete_result, language)
    local expiry = utils.date8_to_expiry(
    	complete_result.expiry
    )
    local trans_number = complete_result.trans_number
    db.begin()
    agreement = update_agreement(
      agreement_id,
      {
        state = "active",
        expiry = expiry,
        due = calculate_due_time(),
        payment1 = trans_number
      }	
    )
    create_payment(
    	user_id,
    	agreement_id,
    	agreement.total,
    	agreement.mva,
    	agreement.currency,
    	trans_number,
    	complete_result.method,
    	agreement.users,
    	agreement.product_id,
    	agreement.product_name
    )
    local trans_id = nil
    local old_agreement_id = nil
    local old_license = create_license(
    	user_id,
    	agreement.product_id,
    	agreement.max_diagrams,
    	agreement.max_spaces,
    	agreement.users,
    	expiry,
    	trans_id,
    	agreement_id,
    	"card"
    )
    if old_license then
        old_agreement_id = old_license.agreement_id
        if old_agreement_id then
            update_agreement(
              old_agreement_id,
              {
                state = "inactive"
              }	
            )
        end
    end
    db.commit()
    local make_receipt = function()
      return build_receipt(
          agreement_id,
          agreement,
          language
      )
    end
    local receipt = db.invoke_no_throw(
    	make_receipt,
    	"build receipt"
    ) or {}
    local send_email = function()
      send_congrat_email(
        user_id,
        receipt,
        language
      )
    end
    db.run_in_fiber(
    	send_email,
    	"send order success email"
    )
    if old_agreement_id then
        local delete_action = function()
        	delete_agreement(old_agreement_id)
        end
        db.run_in_fiber(
        	delete_action,
        	"delete old payex agreement"
        )
    end
    ej.info(
    	"payment_completed",
    	{agreement_id = agreement_id,
    	user_id = user_id,
    	total = agreement.total,
    	currency = agreement.currency,
    	trans_number = trans_number,
    	trace = agreement.trace,
    	product_id = agreement.product_id,
    	users = agreement.users,
    	language = agreement.language}
    )
    return receipt
end

function print_amount(currency, amount)
    if currency then
        return currency .. " " .. utils.print_amount(amount)
    else
        return ""
    end
end

function put_download(session, data, filename)
    ej.info(
    	"export",
    	{type = data.type}
    )
    local download_id = utils.random_string()
    local record = {
    	session_id = session.session_id,
    	download_id = download_id,
    	data = data,
    	filename = filename
    }
    ping_files[download_id] = record
    db.run_in_fiber(
    	function()
    		delete_download(download_id)
    	end,
    	"delete download"
    )
    local result = {
    	download_id = download_id
    }
    return result
end

function px2_calculate_payment(product_id, users, user_id)
    local product = utils.get_product(
    	product_id
    )
    if (product) and (not (users > product.max_users)) then
        local details = pay.px2_calculate_payment(
        	users,
        	product_id,
        	price_cfg
        )
        if details.error then
            ej.info(
            	"px2_calculate_payment_error",
            	{product_id=product_d, users=users, user_id=user_id,
            	error=details.error}
            )
            return false, details.error
        else
            details.users = users
            details.product_id = product_id
            details.product_name = product.name
            details.max_diagrams = product.max_diagrams
            details.max_spaces = product.max_spaces
            details.currency = price_cfg.currency
            details.user_id = user_id
            details.product_code = build_product_code(details)
            details.product_pay_name
             = "DrakonHub subscription: "
             .. details.product_code
            return true, details
        end
    else
        ej.info(
        	"px2_calculate_payment_error",
        	{product_id=product_d, users=users, user_id=user_id}
        )
        return false, "MES_BAD_PRODUCT"
    end
end

function px2_callback(text)
    local data = utils.parse_query(text)
    local agreement_id = data.orderId
    if agreement_id then
        local agreement, order_ref
         = db.agreement_get(agreement_id)
        if agreement then
            local user_id = agreement.user_id
            local language = get_user_language(
            	user_id
            )
            if agreement.state == "created" then
                local ok, result = payex.complete(
                	agreement_id,
                	order_ref,
                	user_id
                )
                if ok then
                    receipt = on_px2_complete(
                    	user_id,
                    	agreement_id,
                    	agreement,
                    	result,
                    	language
                    )
                end
            else
                local paid_now = on_autopay_success(
                	user_id,
                	agreement_id,
                	agreement,
                	data.transactionNumber,
                	"autopay",
                	language
                )
                if paid_now then
                    
                else
                    ej.info(
                      "ignore_callback",
                      {
                        user_id = user_id,
                        reason = 
                    "Complete/Autopay called before, agreement active",
                        agreement_id = agreement_id,
                        order_ref = order_ref
                      }
                    )
                end
            end
        end
    else
        ej.info("missing orderId in callback", data)
    end
end

function read_file_cached(filename)
    local fullname = global_cfg.static_dir
      .. "/" .. filename
    local file = file_cache[filename]
    local now = clock.time()
    if (file) and (not (now > file.expiry)) then
        return file.data
    else
        local data = utils.read_all_bytes(fullname)
        if data then
            local expiry = now + global_cfg.file_timeout
            local file_info = {
            	expiry = expiry,
            	data = data
            }
            file_cache[filename] = file_info
            return data
        else
            return nil
        end
    end
end

function redeem_coupon(user_id, code)
    lic.update_coupon(
    	code,
    	{redeemed_by=user_id}
    )
    ej.info(
    	"redeem_coupon",
    	{user_id=user_id, coupon=code}
    )
end

function redirect(from, to)
    local handler = function(req)
    	return redirect_handler(to)
    end
    router:route({ path = from }, handler)
end

function redirect_handler(location)
    local headers = {
    	Location = location
    }
    local resp = {
    	status = 301,
    	headers = headers
    }
    return resp
end

function replace_many(text, what)
    for key, value in pairs(what) do
        text = text:gsub(key, value)
    end
    return text
end

function result_from_message(headers, message)
    local code
    if message == "ERR_NOT_FOUND" then
        code = 404
    else
        if message == "ERR_ACCESS_DENIED" then
            code = 403
        else
            if message == "ERR_PAYMENT_FAILED" then
                code = 500
            else
                code = 400
            end
        end
    end
    return make_json_error(code, headers, message)
end

function route(path, handler)
    router:route({ path = path }, handler)
end

function save_report(path, report, email)
    local data = json.encode(report)
    utils.write_all_bytes(path, data)
    local subject = report.type .. "-" .. tostring(report.user)
    local text = "Feedback report\n\n"
    text = text .. "type: " .. tostring(report.type) .. "\n"
    text = text .. "url: " .. tostring(report.url) .. "\n"
    text = text .. "user: " .. tostring(report.user) .. "\n"
    text = text .. "errorUrl: " .. tostring(report.errorUrl) .. "\n"
    text = text .. "section: " .. tostring(report.section) .. "\n"
    text = text .. "errorMsg: " .. tostring(report.errorMsg) .. "\n"
    text = text .. "message: " .. tostring(report.message) .. "\n"
    if email then
        text = text .. "email: " .. email .. "\n"
    end
    if report.type == "crash" then
        ej.info(
        	"crash",
        	{errorMsg=report.errorMsg}
        )
    end
    if report.trace then
        text = text .. "trace:\n"
        for _, step in ipairs(report.trace) do
            local stepj = json.encode(step)
            text = text .. stepj .. "\n"
        end
    end
    if (report.type == "feedback") or (global_cfg.email_on_crash) then
        send_mail(
        	"system",
        	global_cfg.feedback_email,
        	subject,
        	text,
        	nil,
        	path
        )
    end
end

function send_autopay_email(user_id, receipt, language, success)
    local user = vud.get_user(user_id)
    local html_file, text_file, sub_id
    if success then
        html_file = "auto_ok.html"
        text_file = "auto_ok.txt"
        subj_id = "MES_AUTO_OK_SUBJECT"
    else
        html_file = "auto_err.html"
        text_file = "auto_err.txt"
        subj_id = "MES_AUTO_ERR_SUBJECT"
    end
    local html = mail.get_template(
    	language,
    	html_file
    )
    local text = mail.get_template(
    	language,
    	text_file
    )
    local subject = trans.translate(
    	language,
    	"index",
    	subj_id
    )
    local renew = global_cfg.my_site
      .. "/buy"
    local variables = {
    
    	VALUE_PRODUCT_NAME = receipt.product_name,
    	VALUE_PRICE_PER_USER = receipt.price,
    	VALUE_NUM_USERS = receipt.users,
    	VALUE_TOTAL = receipt.total,
    	VALUE_ORDER_ID = receipt.trans_id,
    	VALUE_MVA = receipt.mva,
    	RENEW_LINK = renew,
    
    	MES_PRODUCT_NAME = translate(language, "MES_PRODUCT_NAME"),
    	MES_PRICE_PER_USER = translate(language, "MES_PRICE_PER_USER"),
    	MES_NUM_USERS = translate(language, "MES_NUM_USERS"),
    	MES_TOTAL = translate(language, "MES_TOTAL"),
    	MES_ORDER_ID = translate(language, "MES_ORDER_ID"),
    	MES_MVA = translate(language, "MES_MVA")
    }
    text = replace_many(text, variables)
    html = replace_many(html, variables)
    text = text:gsub("<br />", " ")
    send_mail(
    	user_id,
    	user.email,
    	subject,
    	text,
    	html,
    	nil
    )
end

function send_capture(session, lump)
    if global_cfg.use_capture then
        local url = "https://www.google.com/recaptcha/api/siteverify"
        local data = string.format(
        	"secret=%s&response=%s",
        	external_creds.cap_secret,
        	lump
        )
        local mime = "application/x-www-form-urlencoded"
        local body = {
        	data = data,
        	url = url,
        	mime = mime,
        	result = "json"
        }
        local result = utils.msgpack_call(
        	"localhost",
        	global_cfg.https_sender_port,
        	body
        )
        if result then
            if result.success then
                return true
            else
                ej.info("captcha error", result)
                return false
            end
        else
            ej.info("captcha error - no result", {})
            return false
        end
    else
        ej.info(
        	"skipping captcha",
        	{session_id = session.session_id}
        )
        return true
    end
end

function send_congrat_email(user_id, receipt, language)
    local user = vud.get_user(user_id)
    local html = mail.get_template(
    	language,
    	"congrat.html"
    )
    local text = mail.get_template(
    	language,
    	"congrat.txt"
    )
    local subject = trans.translate(
    	language,
    	"index",
    	"MES_PAYMENT_CONFIRMATION"
    )
    local completed = translate(
    	language,
    	"MES_PURCHASE_COMPLETED_TEXT"
    )
    local variables = {
    	USER_NAME = user.name,
    	MES_PURCHASE_COMPLETED_TEXT = completed,
    	VALUE_PRODUCT_NAME = receipt.product_name,
    	VALUE_MAX_DIAGRAMS = receipt.max_diagrams,
    	VALUE_MAX_SPACES = receipt.max_spaces,
    	VALUE_PRICE_PER_USER = receipt.price,
    	VALUE_NUM_USERS = receipt.users,
    	VALUE_TOTAL = receipt.total,
    	VALUE_ORDER_ID = receipt.trans_id,
    	VALUE_DATE = receipt.date,
    	VALUE_MVA = receipt.mva,
    
    	MES_PRODUCT_NAME = translate(language, "MES_PRODUCT_NAME"),
    	MES_MAX_DIAGRAMS = translate(language, "MES_MAX_DIAGRAMS"),
    	MES_MAX_SPACES = translate(language, "MES_MAX_SPACES"),
    	MES_PRICE_PER_USER = translate(language, "MES_PRICE_PER_USER"),
    	MES_NUM_USERS = translate(language, "MES_NUM_USERS"),
    	MES_TOTAL = translate(language, "MES_TOTAL"),
    	MES_ORDER_ID = translate(language, "MES_ORDER_ID"),
    	MES_ORDER_DATE = translate(language, "MES_ORDER_DATE"),
    	MES_MVA = translate(language, "MES_MVA")
    }
    text = replace_many(text, variables)
    html = replace_many(html, variables)
    text = text:gsub("<br />", " ")
    send_mail(
    	user_id,
    	user.email,
    	subject,
    	text,
    	html,
    	nil
    )
end

function send_expiration(user_id, filename, subj_id)
    local user = vud.get_user(user_id)
    if user then
        local license = lic.get_raw_license(user.license)
        if license then
            local language = get_user_language(user_id)
            local product_name = build_product_name(
            	language,
            	license.product_id,
            	license.limits.max_users
            )
            local expiry = localize_time(
            	language,
            	license.expiry
            )
            local html = mail.get_template(
            	language,
            	filename .. ".html"
            )
            local text = mail.get_template(
            	language,
            	filename .. ".txt"
            )
            local renew = global_cfg.my_site
              .. "/buy"
            local variables = {
            	USER_NAME = user.name,
            	PRODUCT_NAME = product_name,
            	EXPIRY_DATE = expiry,
            	RENEW_LINK = renew
            }
            text = replace_many(text, variables)
            html = replace_many(html, variables)
            local subject = translate(
            	language,
            	subj_id
            )
            log.info("send mail to: " .. user_id)
            send_mail(
            	user_id,
            	user.email,
            	subject,
            	text,
            	html,
            	nil
            )
        end
    end
end

function send_expiration_notice(user_id)
    send_expiration(
    	user_id,
    	"exp",
    	"MES_LICENSE_EXPIRED"
    )
end

function send_expiration_soon(user_id, expiry)
    send_expiration(
    	user_id,
    	"exp_warning",
    	"MES_LICENSE_WILL_EXPIRE"
    )
end

function send_mail(user_id, to, subject, text, html, attach)
    mail.send_mail(
    	user_id,
    	to,
    	subject,
    	text,
    	html,
    	attach
    )
end

function send_welcome_email(user_id, language, password)
    local user = vud.get_user(user_id)
    local html = mail.get_template(
    	language,
    	"email-welcome.html"
    )
    local text = mail.get_template(
    	language,
    	"email-welcome.txt"
    )
    local subject = trans.translate(
    	language,
    	"index",
    	"MES_WELCOME"
    )
    local variables = {
    	SUPPORT_EMAIL = global_cfg.feedback_email,
    	APP_URL = global_cfg.my_site,
    	APP_NAME = global_cfg.application,
    	VALUE_USER = user.name,
    	VALUE_PASSWORD = password
    }
    text = replace_many(text, variables)
    html = replace_many(html, variables)
    text = text:gsub("<br />", " ")
    send_mail(
    	user_id,
    	user.email,
    	subject,
    	text,
    	html,
    	nil
    )
end

function send_welcome_email_later(user_id, language, password)
    local task = function()
    	send_welcome_email(user_id, language, password)
    end
    fiber.create(task)
end

function serve_file(req, headers, filename)
    local method = req:method()
    if method == "GET" then
        local data = read_file_cached(filename)
        if data then
            set_content_type(
            	headers,
            	filename
            )
            expires_later(headers)
            return {
            	status = 200,
            	headers = headers,
            	body = data
            }
        else
            expires_now(headers)
            return make_json_error(
            	404,
            	headers,
            	"ERR_FILE_NOT_FOUND"
            )
        end
    else
        expires_now(headers)
        return make_json_error(
        	400,
        	headers,
        	"ERR_BAD_HTTP_METHOD"
        )
    end
end

function set_content_type(headers, filename)
    local type = utils.get_mime(
    	filename,
    	"text/html; charset=utf-8"
    )
    headers["content-type"] = type
end

function set_session_cookie(headers, session_id)
    local max_age = 3600 * 24 * 30 * 6
    local expires_time = clock.time() + max_age
    local expires = cookie_date(expires_time)
    local format = "session_id=%s; Expires=%s; Max-Age=%d; Path=/; HttpOnly; SameSite=Strict;"
    if global_cfg.insecure_cookie then
        
    else
        format = format .. " Secure;"
    end
    local cookie = string.format(
      format,
      session_id,
      expires,
      max_age
    )
    headers["set-cookie"] = cookie
end

function start()
    local options = {
    	log_errors = true
    }
    utils.add_set(
    	global_cfg.http_options,
    	options
    )
    httpd = http.new(
    	global_cfg.host,
    	global_cfg.port,
    	options
    )
    local roptions = {
    	charset = "utf8"
    }
    router = http_router.new(roptions)
    httpd:set_router(router)
    route("/static/:filename", static_handler)
    route("/static/fonts/:filename", static_handler)
    route("/static/libs/:filename", static_handler)
    route("/static/images/:filename", static_handler)
    route("/api/:method/:first/:second/:third", api_handler)
    route("/api/:method/:first/:second", api_handler)
    route("/api/:method/:first", api_handler)
    route("/api/:method", api_handler)
    route("/robots.txt", handle("robots.txt"))
    route("/favicon.ico", handle("favicon.ico"))
    temp_content_route("")
    temp_content_route("logon")
    temp_content_route("team")
    temp_route("/welcome", "welcome.html.el", false)
    if global_cfg.dead then
        route("/signup", handle("dead.html"))
    else
        temp_content_route("signup")
    end
    temp_content_route("start-drakon")
    temp_content_route("start-mind-map")
    if global_cfg.on_premises then
        
    else
        temp_route("/terms", "terms.html.el", true)
        temp_content_route("drakonhub-source")
        redirect("/docs", "/read/docs")
        redirect("/read/sitemap", "/read/docs")
        land_route("/land/:id")
        temp_content_route("drakon")
        temp_content_route("drakon-examples")
        temp_content_route("drakon-reference")
        temp_content_route("how-to-flowchart")
        temp_content_route("how-to-mind-map")
        temp_content_route("video-how-to-flowchart")
        temp_content_route("video-how-to-mind-map")
        temp_content_route("video-drakon-part-1")
        temp_content_route("video-drakon-part-2")
        temp_land_route("/flowchart/ultra", "flowchart_land1.html.el")
        temp_read_route("/read/:id")
        temp_route(
        	"/googlee28387d91f342a24.html",
        	"googlee28387d91f342a24.html.el",
        	true
        )
        temp_route("/landbus", "landbus.html.el", true)
        temp_route_local("/landbus", "landbus.html.el", true)
        
        temp_route("/welcome-trial", "welcome-trial.html.el", false)
        temp_route_local("/unsubscribe/:user_id/:unid", "unsub.html.el", true)
    end
    temp_route("/account", "account.html.el", true)
    temp_route("/reset", "reset.html.el", true)
    temp_route("/test", "cprobe.html.el", true)
    temp_route("/try-me", "ide3.html.el", false)
    temp_route("/ide2/doc/:space_id/:folder_id", "ide2.html.el", false)
    temp_route("/ide2/trash", "ide2.html.el", false)
    temp_route("/ide2/spaces", "ide2.html.el", false)
    temp_route("/ide2/recent", "ide2.html.el", false)
    temp_route("/ide/doc/:space_id/:folder_id", "ide3.html.el", false)
    temp_route("/ide/trash", "ide3.html.el", false)
    temp_route("/ide/spaces", "ide3.html.el", false)
    temp_route("/ide/recent", "ide3.html.el", false)
    temp_route("/ide/dashboard", "ide3.html.el", false)
    api("account", "GET", true, false, api_account)
    api("own_spaces", "GET", true, false, api_get_own_spaces)
    api("license", "GET", true, false, api_get_license)
    api("logon", "POST", false, false, api_logon)
    api("logout", "POST", false, false, api_logout)
    api("create_user", "POST", false, false, api_create_user)
    api("create_user_email", "POST", false, false, api_create_user_email)
    api("update_user", "POST", true, false, api_update_user)
    api("delete_user", "POST", true, false, api_delete_user)
    api("pass", "POST", true, false, api_pass)
    api("set_ref", "POST", false, false, api_set_ref)
    api("find_folder", "POST", false, false, api_find_folder)
    api("visit", "GET", false, false, api_visit_folder)
    api("folder", "GET", false, false, api_get_folder)
    api("folder", "POST", true, false, api_create_folder)
    api("folder", "PUT", true, false, api_update_folder)
    api("many", "POST", true, false, api_many)
    api("trash", "GET", true, false, api_get_trash)
    api("trash", "DELETE", true, false, api_clear_trash)
    api("restore", "POST", true, false, api_restore)
    api("tree", "GET", true, false, api_tree)
    api("access", "POST", true, false, api_access)
    api("access", "DELETE", true, false, api_access)
    api("access", "GET", true, false, api_get_access)
    api("recent", "GET", true, false, api_recent)
    api("save_try", "POST", true, false, api_save_try)
    api("find_users", "POST", false, false, api_find_users)
    api("space", "POST", true, false, api_create_space)
    api("space", "DELETE", true, false, api_delete_space)
    api("multi_access", "POST", true, false, api_multi_access)
    api("reset_pass", "POST", false, false, api_reset_pass)
    api("def_query", "POST", false, false, api_def_query)
    api("search", "POST", false, false, api_start_search)
    api("search", "GET", false, false, api_get_search)
    api("search", "DELETE", false, false, api_delete_search)
    api("unsubscribe", "POST", false, false, api_unsubscribe)
    api("stop_subscription", "POST", true, false, api_stop_subscription)
    api("download", "GET", false, false, api_get_download)
    api("download", "POST", false, false, api_download)
    api("download_svg", "POST", false, false, api_download_svg)
    api("backup", "GET", false, false, api_backup)
    api("restore_backup", "POST", true, false, api_restore_backup)
    api("get_whole_project", "GET", true, false, api_get_whole_project)
    api("adm", "GET", true, true, api_get_adm)
    api("adm", "POST", true, true, api_post_adm)
    api("summary", "GET", true, true, api_get_summary)
    api("gen_coupons", "POST", true, true, api_gen_coupons)
    api("feedback", "POST", false, false, api_feedback)
    api("edit", "POST", true, false, api_edit)
    api("tag", "GET", false, false, api_get_tag)
    api("theme", "GET", true, false, api_get_theme)
    api("theme", "POST", true, false, api_set_theme)
    api("diatest", "GET", true, true, api_get_diatest)
    api("diatest", "POST", true, true, api_save_diatest)
    api("diatest", "DELETE", true, true, api_delete_diatest)
    httpd:start()
end

function start_trial(session_id, user_id, old_license_id, product_id)
    local product = utils.get_product(
    	product_id
    )
    local details = {}
    details.users = product.min_users
    details.product_id = product_id
    details.product_name = product.name
    details.max_diagrams = product.max_diagrams
    details.max_spaces = product.max_spaces
    local expiry = os.time() + 
    	utils.days_to_secs(price_cfg.trial_days)
    local trans_id = create_transaction(
    	user_id,
    	"trial",
    	details
    )
    complete_transaction(
    	user_id,
    	trans_id,
    	"success"
    )
    lic.delete_license(old_license_id)
    local agreement_id = nil
    local license_id = create_license_for_user(
    	user_id,
    	details.product_id,
    	details.max_diagrams,
    	details.max_spaces,
    	details.users,
    	expiry,
    	trans_id,
    	agreement_id,
    	"trial"
    )
    lic.update_license(
    	license_id,
    	{is_trial=true}
    )
    vud.set_user_prop(
    	user_id,
    	"had_trial",
    	true
    )
    ej.info(
    	"trial",
    	{user_id=user_id, product_id=product_id,
    	users=details.users,
    	session_id=session_id}
    )
end

function static_handler(req)
    local parts = utils.split(req:path(), "/")
    local filename
    if #parts == 3 then
        filename = parts[2] .. "/" .. parts[3]
    else
        filename = parts[2]
    end
    local headers = {}
    return serve_file(req, headers, filename)
end

function suggest_product(session, count, get_limit)
    local user = vud.get_user(session.user_id)
    local license = lic.get_raw_license(user.license)
    local product_id
    if license then
        product_id = license.product_id
    else
        product_id = "basic"
    end
    while true do
        if product_id then
            
        else
            return nil
        end
        local limit = get_limit(
          product_id
        )
        if limit >= count then
            return product_id
        end
        product_id = get_next_product(product_id)
    end
end

function take_download(download_id)
    local record = ping_files[download_id]
    if record then
        ping_files[download_id] = nil
    end
    return record
end

function temp_content_route(path)
    local root_path = "/" .. path
    local path_ru = "/ru" .. root_path
    local path_en = "/en" .. root_path
    local caching_allowed = true
    local file = "content.html.el"
    local cinfo_maker = function(req, language)
    	return create_content_cinfo(language, path)
    end
    router:route(
    	{path=path_ru, file=file},
    	make_template("index", "ru", caching_allowed, cinfo_maker)
    )
    router:route(
    	{path=path_en, file=file},
    	make_template("index", "en", caching_allowed, cinfo_maker)
    )
    router:route(
    	{path=root_path, file=file},
    	make_template("index", "", caching_allowed, cinfo_maker)
    )
end

function temp_land_route(path, file)
    local path_ru = "/ru" .. path
    local path_en = "/en" .. path
    local caching_allowed = true
    local cinfo_maker = function(req, language)
    	return create_content_cinfo(language, path)
    end
    router:route(
    	{path=path_ru, file=file},
    	make_template("index", "ru", caching_allowed, cinfo_maker)
    )
    router:route(
    	{path=path_en, file=file},
    	make_template("index", "en", caching_allowed, cinfo_maker)
    )
    router:route(
    	{path=path, file=file},
    	make_template("index", "", caching_allowed, cinfo_maker)
    )
end

function temp_read_route(path)
    local root_path = path
    local path_ru = "/ru" .. root_path
    local path_en = "/en" .. root_path
    local caching_allowed = true
    local file = "content.html.el"
    local cinfo_maker = create_read_cinfo
    router:route(
    	{path=path_ru, file=file},
    	make_template("index", "ru", caching_allowed, cinfo_maker)
    )
    router:route(
    	{path=path_en, file=file},
    	make_template("index", "en", caching_allowed, cinfo_maker)
    )
    router:route(
    	{path=root_path, file=file},
    	make_template("index", "", caching_allowed, cinfo_maker)
    )
end

function temp_route(path, file, caching_allowed)
    router:route(
    	{path=path, file=file},
    	make_template("index", "", caching_allowed, nil)
    )
end

function temp_route_local(path, file, caching_allowed)
    local path_ru = "/ru" .. path
    local path_en = "/en" .. path
    router:route(
    	{path=path_ru, file=file},
    	make_template("index", "ru", caching_allowed, nil)
    )
    router:route(
    	{path=path_en, file=file},
    	make_template("index", "en", caching_allowed, nil)
    )
end

function to_user_names(ids)
    local result = {}
    for _, user_id in ipairs(ids) do
        local user = vud.get_user(user_id)
        if user then
            table.insert(
            	result,
            	user.name
            )
        end
    end
    return result
end

function translate(language, text)
    local result = trans.translate(
    	language,
    	"index",
    	text
    )
    -- log.info(text .. " -> " .. result
    -- .. " (" .. language .. ")")
    return result
end

function try_autopay(agreement_id)
    local now = os.time()
    local agreement = db.agreement_get(
    	agreement_id
    )
    if is_agreement_active(agreement) then
        if now > agreement.due then
            local user_id = agreement.user_id
            local user = vud.get_user(user_id)
            if user then
                local language = get_user_language(
                	user_id
                )
                agreement.agreement_id = agreement_id
                local pay_ok, result = payex.autopay(
                	agreement,
                	user_id
                )
                if pay_ok then
                    on_autopay_success(
                    	user_id,
                    	agreement_id,
                    	agreement,
                    	result.trans_number,
                    	result.method,
                    	language
                    )
                else
                    local receipt = build_receipt(
                          agreement_id,
                          agreement,
                          language
                    )
                    local due = now + AUTOPAY_RETRY_DELAY_SEC
                    db.begin()
                    local sfields = db.scheduled_get(agreement_id)
                    if sfields then
                        local retries = sfields.retries - 1
                        if retries > 0 then
                            db.scheduled_upsert(
                              agreement_id,
                              {due = due,
                               retries = retries}
                            )
                            agreement = update_agreement(
                              agreement_id,
                              {
                                state = "retrying"
                              }	
                            )
                            db.commit()
                            ej.info(
                              "schedule_autopay_retry",
                              {user_id = user_id,
                              agreement_id = agreement_id}
                            )
                        else
                            db.scheduled_delete(agreement_id)
                            lic.update_license(
                            	user.license,
                            	{active = false}
                            )
                            update_users_spaces(user_id)
                            agreement = update_agreement(
                              agreement_id,
                              {
                                state = "unpaid"
                              }	
                            )
                            db.commit()
                            ej.info(
                              "license_unpaid",
                              {user_id=user_id,
                              license_id=user.license,
                              agreement_id = agreement_id}
                            )
                            send_autopay_email(
                            	user_id,
                            	receipt,
                            	language,
                            	false
                            )
                        end
                    else
                        db.scheduled_upsert(
                          agreement_id,
                          {due = due,
                           retries = AUTOPAY_RETRIES - 1}
                        )
                        agreement = update_agreement(
                          agreement_id,
                          {
                            state = "retrying"
                          }	
                        )
                        db.commit()
                        ej.info(
                          "schedule_autopay_retry",
                          {user_id = user_id,
                          agreement_id = agreement_id}
                        )
                    end
                end
            end
        end
    else
        db.scheduled_delete(agreement_id)
    end
end

function update_agreement(agreement_id, fields)
    local data, order_ref = db.agreement_get(
    	agreement_id
    )
    if data then
        for key, value in pairs(fields) do
            data[key] = value
        end
        data.when_updated = clock.time()
        db.agreement_update(
        	agreement_id,
        	order_ref,
        	data
        )
        return data
    else
        return nil
    end
end

function update_users_spaces(user_id)
    local spaces = get_own_spaces(user_id)
    for _, space_id in ipairs(spaces) do
        space.update_space_limits(space_id)
    end
end

function user_exists(id_email)
    if vud.find_user(id_email) then
        return true
    else
        return false
    end
end

function write_font_footer(font, fhandle)
    fhandle:write("\") format('woff');\n}\n")
end

function write_font_header(font, fhandle)
    local weight, style
    if font.bold then
        weight = "bold"
    else
        weight = "normal"
    end
    if font.italic then
        style = "italic"
    else
        style = "normal"
    end
    fhandle:write("@font-face {\n")
    fhandle:write("  font-family: '" .. font.family .. "';\n")
    fhandle:write("  font-style: " .. style .. ";\n")
    fhandle:write("  font-weight: " .. weight .. ";\n")
    fhandle:write("  src: url(\"data:font/woff;base64,")
end

function wrong_api_handler(req, session, headers)
    return make_json_error(400, headers, "ERR_BAD_API")
end


module = {
	start = start,
	check_license = check_license,
	check_scheduled = check_scheduled,
	send_expiration_notice = send_expiration_notice,
	send_expiration_soon = send_expiration_soon,
	create_default_license = create_default_license,
	update_users_spaces = update_users_spaces,
	delete_user = delete_user,
	inject_send_mail = inject_send_mail
}

return module
