local util = {} local table_is_empty = function(t) return next(t) == nil end local is_in = function(value, list) for _,el in ipairs(list) do if value == el then return true end end return false end util.hash = function(v) -- TODO: maybe use Argon2 [https://luarocks.org/modules/thibaultcha/argon2] --xxhash:update(v) --return xxhash:digest() return v end --[[ Uses 'schema' to verify: Existence of required value in 'req' They have proper types The DB references actually exist ]] --[[ schema = { user_id = {'db_ref', to='user', required=true, load_as='user_record'} -- `load_as` so a DB search doesn't have to be done again after validation price = {'number', required=true} } ]] util.validate = function(req, schema, db, sessions) local errors = {} local loaded_records = {} for property, rules in pairs(schema) do local expected_value_type = rules[1] local value = req[property] if rules.required == true then if value == nil then table.insert(errors, "Expected '"..property.."' to be provided.") end end if expected_value_type == 'number' or expected_value_type == 'string' or expected_value_type == 'table' then if type(value) ~= expected_value_type then table.insert(errors, "Expected '"..property.."' to be a "..expected_value_type..".") elseif rules.is_any_of ~= nil then if not is_in(value, rules.is_any_of) then table.insert(errors, "Expected '"..property.."' to be one of (".. table.concat(rules.is_any_of,',') ..").") end end elseif expected_value_type == 'db_ref' then local expected_record_type = rules.to local record = db:findOne(function(record) return record.id == value and record.type == expected_record_type end) if record == nil then table.insert(errors, "Expected '"..property.."' to point to a '"..expected_record_type.."' in the database.") else loaded_records[rules.load_as] = record end elseif expected_value_type == 'base64' then local _, start_of_base64_string_index = string.find(value, "data:.-/.-;base64,") start_of_base64_string_index = start_of_base64_string_index + 1 local base64_adjusted = string.sub(value, start_of_base64_string_index) -- must remove "data:*/*;base64," from the beginning of the string loaded_records[rules.load_as] = base64_adjusted elseif expected_value_type == 'session' then local session = sessions[value] if session == nil then table.insert(errors, "No such session.") else if rules.is_admin == true and db:getrecord(session.user_id).is_admin ~= true then table.insert(errors, "Current user is not admin.") else loaded_records.session = session end end end end return errors, loaded_records end util.run_every = function(interval_in_s, fn) local co = coroutine.create(function() local t1 = os.time() while true do if os.time()-t1 > interval_in_s*1000 then fn() t1 = os.time() end coroutine.yield() --posix.unistd.sleep(interval_in_s) end end) return co end util.setdefaults = function(t, defaults) for k,v in pairs(defaults) do if t[k] == nil then t[k] = v end end end util.merge = function(dest, source) for k,v in pairs(source) do dest[k] = v end end util.findOne = function(t, fn) for i,v in ipairs(t) do if fn(v, i) then return v end end return nil end util.removefromtable = function(t, v) local i = 1 while t[i] ~= v do i = i + 1 end table.remove(t, i) return i end return util