-- =================================================================== -- /orgs.lua -- Org-related functions for Modular Politics -- Called by modpol/modpol/api.lua -- =================================================================== -- preserve records -- if true, will store org ledgers when they are closed, for later reference. local preserve_records = true -- policies assigned to any org can go here modpol.default_org_policies = modpol.default_org_policies or {} -- initiate the table of orgs, if it doesnt already exist from storage! modpol.orgs = modpol.orgs or {} if preserve_records then modpol.old_ledgers = modpol.old_ledgers or {} -- a backup of all legders of all closed orgs. end -- this counter will increment every time a new org is created, and the current value will be used as the org id -- we set it to 1 for init, and then run through all existing orgs and take only the greatest... which we then add 1 to. -- this ensures a unique id for each org. modpol.id_counter = 1 if #modpol.orgs > 0 then for id,org in pairs(modpol.orgs) do if id > modpol.id_counter then modpol.id_counter = id end end for id,ledg in pairs(modpol.old_ledgers) do if id > modpol.id_counter then modpol.id_counter = id end end modpol.id_counter = modpol.id_counter +1 end --define the template of a ledger entry modpol.ledger_entry_temp = { timestamp = '', entrytype = nil, action_msg = '', org_name = nil, org_id = nil, } -- define the basic template of an org. modpol.org_temp = { id = nil, name = nil, policies = {}, members = {}, ledger = {}, parent = nil, children = {}, properties = {}, old_names = {}, } -- =================================================================== -- Function: modpol.create_ledger_entry -- Params: strings: action_msg, type, number: org_id -- Outputs: -- returns a ledger entry table, with a timestamp -- This function accepts a message and an optional org_id and type specification -- and returns a valid ledger entry table modpol.create_ledger_entry = function(action_msg, org_id, entry_type) local entry = modpol.ledger_entry_temp if action_msg and type(action_msg) == 'string' then entry.action_msg = action_msg end if org_id and type(org_id) == 'number' and modpol.orgs [org_id] then entry.org_id = org_id if modpol.orgs[org_id].name then entry.org_name = modpol.orgs[org_id].name end end if entry_type and type(entry_type) == 'string' then entry.entrytype = entry_type end entry.timestamp = os.time() return entry end -- =================================================================== -- Function: modpol.record -- Params: strings msg, type, number org_id -- Outputs: -- "msg" specifies an event and/or status message. -- "org" specifies an "org" id. -- "type" -- optional type of legder entry. Could be e.g. 'election_result', 'add_member', etc -- This function adds the message to a global ledger and, if "org" -- specifies a valid "org", to an "org"-specific ledger. Both the mem- -- ory-resident and on-disk copies of the data structures used are up- -- dated. modpol.record = function (org_id, msg, type) local entry = modpol.create_ledger_entry(msg, org_id, type) -- Record in global ledger table.insert (modpol.ledger, entry) -- Record in "org"-specific ledger if modpol.orgs [org_id] ~= nil then local org_ledger = modpol.orgs [org_id]["ledger"] if org_ledger == nil then modpol.orgs [org_id]["ledger"] = { entry } else modpol.orgs [org_id]["ledger"] = table.insert (org_ledger, entry) end end modpol.store_data() -- Copy data to disk end -- =================================================================== -- Function: modpol.get_org_id_by_name(org_name) -- Params: org_name -- Output: an org id or nil -- modpol.get_org_id_by_name = function(org_name) local id = nil for org_id,org in pairs(modpol.orgs) do if org.name == org_name then id = org_id end end return id end -- =================================================================== -- Function: modpol.add_org -- Params: string name, table members, string parent, table properties -- Output: -- -- This function creates an "org". It returns a boolean success indicator and -- either an error message starting with "Error:" or a success message. -- -- The string parameter specifies the "org" name. -- -- The members table should be an integer-indexed array of member -- names. -- parent should be nil or a valid existing org name. If nil, defaults to -- 'instance' -- properties defaults to an empty table. Use it to initialize the org -- with arbitrary properties modpol.add_org = function(org_name, members, parent_id, properties) local str if modpol.ocutil.str_empty (org_name) then return false,"Error: Org needs a name" end if parent_id and not modpol.orgs[parent_id] then return false,"Error: Parent_id must be a valid existing org id" end if properties and not type(properties) == 'table' then return false,"Error: Properties must be a table" end modpol.ocutil.log ("Attempting to add new org " .. org_name) if modpol.get_org_id_by_name(org_name) ~= nil then str = "Error: Org " .. org_name .. " already exists" modpol.ocutil.log (str) return false,str end local org_id = modpol.id_counter modpol.id_counter = modpol.id_counter + 1 -- the instance will have a parent of nil if it is the instance. -- otherwise, it will have a parent of instance's id if not otherwise defined local parent_org_id = nil if parent_id and not(modpol.get_org_id_by_name('instance') == org_id) then parent_org_id = parent_id end if not parent_id and not(modpol.get_org_id_by_name('instance') == org_id) then parent_org_id = modpol.get_org_id_by_name('instance') end local props = properties or {} -- actually add the org local org = modpol.org_temp local default_policies = modpol.default_org_policies or {} org.id = org_id org.name = org_name -- do not change this without the proper api function. This is only a reference, not the only place this is recorded. org.members = members org.parent = parent_org_id org.properties = props org.policies = default_policies modpol.orgs [org_id] = org --record the child org in the parent's children table if parent_org_id then if modpol.orgs[parent_org_id].children then table.insert(modpol.orgs[parent_org_id].children, org_id) else modpol.orgs[parent_org_id].children = {org_id} end end local msg = "New org: " .. org_name .. " ["..org_id.."], parent = ".. dump(parent_org_id)..", members = ".. " (" .. table.concat (members, ", ") .. ")" modpol.record (org_id, msg, 'org_init') return true, msg end -- =================================================================== -- Function: modpol.remove_org -- Params: number id, opt string reason -- Output: -- -- This function removes an "org". It returns a boolean -- success indicator and either an error message -- starting with "Error:" or a success message. It also recursively -- removes all child orgs, with 'remove parent org' as reason. If -- preserve_records is enabled, it logs the removal in the org ledger, -- and stores the ledger in modpol.old_ledgers -- The number parameter specifies the "org" id. -- -- The reason is an optional string to additionally include in the -- log as reason for removal modpol.remove_org = function (org_id, reason) local str if not reason then reason = '' end if not type(org_id) == 'number' or not modpol.orgs[org_id] then str = "Error: Specify an existing org id to remove" modpol.ocutil.log (str) return false,str end local org_name = modpol.orgs[org_id] if org_name == 'instance' then return false,"Error: You cannot remove instance" end --log msg local msg = 'Removing org '..org_name ..' ['..org_id..'], reason: '.. reason modpol.ocutil.log (msg) -- actually remove the org -- TODO: if callbacks are implemented, do so here -- if preserve_records then modpol.old_ledgers[org_id] = modpol.orgs[org_id].ledger end -- also remove all children if modpol.orgs[org_id].children and #modpol.orgs[org_id].children > 0 then for _,child_name in pairs(modpol.orgs[org_id].children) do --this is recursion. If it gets to be a problem, we will need a separate function modpol.remove_org(child_name, 'remove parent org') end end --also remove references to this org in parent orgs if modpol.orgs[org_id].parent then local parent_id = modpol.orgs[org_id].parent modpol.orgs[parent_id].children[org_id] = nil end modpol.orgs[org_id] = nil modpol.record (org_id, msg, 'org_remove') return true,msg end -- =================================================================== -- Function: modpol.list_orgs -- Params: None -- Output: -- This function returns a text-format list of "orgs". The list shows -- one "org" per line in the following format: -- org_name (member, member, ...) -- If there are no "orgs", the output is an empty string. modpol.list_orgs = function() local outbuf = "" local str for org_id, org_data in pairs (modpol.orgs) do -- Process next "org" -- Build string version of member list local memcat = org_data ["members"] local org_name = org_data ["name"].."["..org_id.."]" if modpol.ocutil.str_empty (memcat) then memcat = "(empty)" else memcat = "(" .. table.concat (memcat, ", ") .. ")" end -- Build output string outbuf = outbuf .. org_name .. " " .. memcat .. "\n" end return outbuf end -- =================================================================== -- Function: modpol.reset_orgs -- Params: None -- Removes all orgs and recreates blank org "instance" with all -- current users -- returns confirmation message modpol.reset_orgs = function() local users = modpol.list_users() --store the ledgers in modpol.old_ledgers, and note that each org was closed in each record. if preserve_records then for id, org in pairs(modpol.orgs) do local old_ledger = org.ledger table.insert(old_ledger,modpol.create_ledger_entry('Removing org '.. id, id, 'org_purge')) modpol.old_ledgers[id] = old_ledger end end modpol.orgs = {} local message = "Orgs purged" modpol.record(nil, message, 'org_purge') modpol.add_org("instance", users) return message end -- =================================================================== -- Function: modpol.add_member -- Params: org_id (number), member (string) -- Output: -- Adds the specified member to the specified org -- Returns a boolean success indicator and -- either a confirmation or error message -- TODO, check that member is a user, if the org_name is not instance modpol.add_member = function(org_id, member) if (modpol.orgs[org_id] == nil) then return false,"Error: No such org" elseif type(member) ~= 'string' then return false,"Error: member is wrong type" else local org_name = modpol.orgs [org_id].name if modpol.is_member(org_id,member) then return false,"Error: " .. member .. " is already a member of " .. org_name else table.insert(modpol.orgs[org_id]["members"], member) local message = member .. " added to org " .. org_name .. " ["..org_id.."]" modpol.record(org_id, message, 'add_member') return true,message end end end -- =================================================================== -- Function: modpol.is_member -- Params: org (number), member (string) -- Output: Boolean depending on membership or nil/error message modpol.is_member = function(org, member) if (modpol.orgs[org] == nil) then return nil, "Error: No such org" else local member_table = modpol.orgs[org]["members"] if member_table then for index, value in pairs(member_table) do if value == member then -- match found return true end end -- no match found return false end return false end end -- =================================================================== -- Function: modpol.remove_member -- Params: org (number), member (string) -- Output: -- Removes the specified member from the specified org -- Returns confirmation or error message function modpol.remove_member(org, member) local message = "Error: No such org" if (modpol.orgs[org] == nil) then return false, message else local member_table = modpol.orgs[org]["members"] if member_table then for index, value in pairs(member_table) do if value == member then -- match found, set to nil modpol.orgs[org]["members"][value] = nil -- TODO: add callbacks here, for such as revoking privs, etc -- message = member .. " removed from org " .. org modpol.record(org, message, 'remove_member') return true, message end end end -- no match found (or org has no members) message = member .. " not found in org " .. org return false, message end end -- =================================================================== -- TKTK -- rename_org(old_name, new_name) -- Renames an org -- =================================================================== -- POLICIES -- Each policy is a table with a string for a key. Contents: -- KEY - name of the policy (string) -- [member orgs]: external orgs allowed to call policy -- [function]: the function that initiates the policy -- =================================================================== -- =================================================================== -- modpol.add_policy -- Adds a policy to an org's [org].policies table -- Params: org_name (string), -- policy_name (string), -- policy_table (table) -- Output: true if successful, nil if error -- TKTK NEEDS TO BE REWRITTEN modpol.add_policy = function(org_name, policy_name, policy_table) -- first, basic checks local message = "Error: No such org" if (modpol.orgs[org_name] == nil) then return nil, message elseif (modpol[target_function] == nil) then message = "Error: No such target function" return nil, message elseif (modpol[policy_function]) then message = "Error: No such policy function" return nil, message else -- okay, proceed modpol.orgs[org_name].policies[target_function] = policy_function message = "In org " .. org_name .. ", policy for " .. target_function .. " set to " .. policy_function record(org_name, message) return true, message end end -- =================================================================== -- End of file.