Added networking and basic handlers.
This commit is contained in:
parent
85dbe20cd1
commit
da47304eec
211
bank.lua
211
bank.lua
|
@ -10,7 +10,6 @@ like pocket computers and ATMs, for managing funds.
|
|||
local USERS_DIR = "users"
|
||||
local USER_DATA_FILE = "data.json"
|
||||
local ACCOUNTS_FILE = "accounts.json"
|
||||
local TRANSACTIONS_FILE = "transactions.json"
|
||||
|
||||
local HOST = "central-bank"
|
||||
|
||||
|
@ -29,6 +28,23 @@ local function log(msg)
|
|||
g.appendAndDrawConsole(term, console, textutils.formatTime(os.time()) .. ": " .. msg, 1, 3)
|
||||
end
|
||||
|
||||
-- Helper functions
|
||||
|
||||
local function readJSON(filename)
|
||||
local f = io.open(filename, "r")
|
||||
if not f then error("Cannot open file " .. filename .. " to read JSON data.") end
|
||||
local data = textutils.unserializeJSON(f:read("*a"))
|
||||
f:close()
|
||||
return data
|
||||
end
|
||||
|
||||
local function writeJSON(filename, data)
|
||||
local f = io.open(filename, "w")
|
||||
if not f then error("Cannot open file " .. filename .. " to write JSON data.") end
|
||||
f:write(textutils.serializeJSON(data))
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- Basic account functions:
|
||||
|
||||
local function validateUsername(name)
|
||||
|
@ -52,6 +68,10 @@ local function userAccountsFile(name)
|
|||
return fs.combine(USERS_DIR, name, ACCOUNTS_FILE)
|
||||
end
|
||||
|
||||
local function accountTransactionsFile(username, accountId)
|
||||
return fs.combine(USERS_DIR, username, "tx_" .. accountId .. ".txt")
|
||||
end
|
||||
|
||||
local function userExists(name)
|
||||
return validateUsername(name) and fs.exists(userDir(name))
|
||||
end
|
||||
|
@ -63,7 +83,7 @@ end
|
|||
local function randomAccountId()
|
||||
local id = ""
|
||||
for i = 1, 16 do
|
||||
id = id .. tostring(math.random(1, 9))
|
||||
id = id .. tostring(math.random(0, 9))
|
||||
if i % 4 == 0 and i < 16 then
|
||||
id = id .. "-"
|
||||
end
|
||||
|
@ -71,17 +91,69 @@ local function randomAccountId()
|
|||
return id
|
||||
end
|
||||
|
||||
local function getUserData(name)
|
||||
return readJSON(userDataFile(name))
|
||||
end
|
||||
|
||||
local function getAccounts(name)
|
||||
local f = io.open(userAccountsFile(name), "r")
|
||||
local accounts = textutils.unserializeJSON(f:read("*a"))
|
||||
f:close()
|
||||
return accounts
|
||||
return readJSON(userAccountsFile(name))
|
||||
end
|
||||
|
||||
local function saveAccounts(name, accounts)
|
||||
local f = io.open(userAccountsFile(name), "w")
|
||||
f:write(textutils.serializeJSON(accounts))
|
||||
f:close()
|
||||
writeJSON(userAccountsFile(name), accounts)
|
||||
end
|
||||
|
||||
local function createAccount(username, accountName)
|
||||
local accounts = getAccounts(username)
|
||||
for i, account in pairs(accounts) do
|
||||
if account.name == accountName then
|
||||
return false, "Duplicate account name"
|
||||
end
|
||||
end
|
||||
local newAccount = {
|
||||
id = randomAccountId(),
|
||||
name = accountName,
|
||||
balance = 0,
|
||||
createdAt = os.epoch("utc")
|
||||
}
|
||||
table.insert(accounts, newAccount)
|
||||
saveAccounts(username, accounts)
|
||||
log("Created account " .. newAccount.id .. " for user " .. username)
|
||||
return true, newAccount.id
|
||||
end
|
||||
|
||||
local function deleteAccount(username, accountId)
|
||||
local accounts = getAccounts(username)
|
||||
local targetIndex = nil
|
||||
for i, account in pairs(accounts) do
|
||||
if account.id == accountId then
|
||||
targetIndex = i
|
||||
end
|
||||
end
|
||||
if targetIndex then
|
||||
table.remove(accounts, targetIndex)
|
||||
saveAccounts(username, accounts)
|
||||
log("Deleted user " .. username .. " account " .. accountId)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function renameAccount(username, accountId, newName)
|
||||
local accounts = getAccounts(username)
|
||||
local targetAccount = nil
|
||||
for i, account in pairs(accounts) do
|
||||
if account.id == accountId then
|
||||
targetAccount = account
|
||||
elseif account.name == newName then
|
||||
return false, "Duplicate account name"
|
||||
end
|
||||
end
|
||||
if not targetAccount then return false, "Account not found" end
|
||||
targetAccount.name = newName
|
||||
saveAccounts(accounts)
|
||||
log("Renamed user " .. username .. " account " .. accountId .. " to " .. newName)
|
||||
return true
|
||||
end
|
||||
|
||||
local function createUser(name, password)
|
||||
|
@ -93,40 +165,115 @@ local function createUser(name, password)
|
|||
createdAt = os.epoch("utc")
|
||||
}
|
||||
fs.makeDir(userDir(name))
|
||||
local dataFile = io.open(userDataFile(name), "w")
|
||||
dataFile:write(textutils.serializeJSON(userData))
|
||||
dataFile:close()
|
||||
-- Add an initial account.
|
||||
local initialAccounts = {
|
||||
{
|
||||
id = randomAccountId(),
|
||||
name = "Checking",
|
||||
balance = 0
|
||||
},
|
||||
{
|
||||
id = randomAccountId(),
|
||||
name = "Savings",
|
||||
balance = 0
|
||||
}
|
||||
}
|
||||
saveAccounts(name, initialAccounts)
|
||||
writeJSON(userDataFile(name), userData) -- Flush user data file.
|
||||
saveAccounts(userAccountsFile(name), {}) -- Flush initial accounts file.
|
||||
createAccount(name, "Checking")
|
||||
createAccount(name, "Savings")
|
||||
log("Created new user: " .. name)
|
||||
return true
|
||||
end
|
||||
|
||||
local function deleteUser(name)
|
||||
if not userExists(name) then return false end
|
||||
fs.delete(userDir(name))
|
||||
log("Deleted user \"" .. name .. "\".")
|
||||
return true
|
||||
end
|
||||
|
||||
local function renameUser(oldName, newName)
|
||||
if not validateUsername(newName) then return false, "Invalid new username" end
|
||||
if not userExists(oldName) then return false, "User doesn't exist" end
|
||||
if userExists(newName) then return false, "New username is taken" end
|
||||
fs.move(userDir(oldName), userDir(newName))
|
||||
log("Renamed user \"" .. oldName .. "\" to \"" .. newName .. "\".")
|
||||
return true
|
||||
end
|
||||
|
||||
-- Event handling
|
||||
-- EVENT HANDLING
|
||||
-----------------
|
||||
|
||||
-- Helper function to wrap another function in an authentication check.
|
||||
local function authProtect(func)
|
||||
return function (msg)
|
||||
if (
|
||||
not msg.auth or
|
||||
not msg.auth.username or
|
||||
not msg.auth.password or
|
||||
not userExists(msg.auth.username) or
|
||||
getUserData(msg.auth.username).password ~= msg.auth.password
|
||||
) then
|
||||
return {success = false, error = "Invalid credentials"}
|
||||
end
|
||||
return func(msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function handleCreateUser(msg)
|
||||
if not msg.data or not msg.data.username or not msg.data.password then
|
||||
return {success = false, error = "Invalid request. Requires data.username and data.password."}
|
||||
end
|
||||
local success, errorMsg = createuser(msg.data.username, msg.data.password)
|
||||
if not success then
|
||||
return {success = false, error = errorMsg}
|
||||
end
|
||||
return {success = true}
|
||||
end
|
||||
|
||||
local function handleDeleteUser(msg)
|
||||
deleteUser(msg.auth.username)
|
||||
return {success = true}
|
||||
end
|
||||
|
||||
local function handleRenameUser(msg)
|
||||
if not msg.data or not msg.data.newUsername then
|
||||
return {success = false, error = "Invalid request. Requires data.newUsername."}
|
||||
end
|
||||
local success, errorMsg = renameUser(msg.auth.username, msg.data.newUsername)
|
||||
if not success then
|
||||
return {success = false, error = errorMsg}
|
||||
end
|
||||
return {success = true}
|
||||
end
|
||||
|
||||
local function handleGetUserAccounts(msg)
|
||||
return {success = true, data = getAccounts(msg.auth.username)}
|
||||
end
|
||||
|
||||
local function handleDeleteUserAccount(msg)
|
||||
if not msg.data or not msg.data.accountId then
|
||||
return {success = false, error = "Invalid request. Requires data.accountId."}
|
||||
end
|
||||
deleteAccount(msg.auth.username, msg.data.accountId)
|
||||
return {success = true}
|
||||
end
|
||||
|
||||
local function handleRenameUserAccount(msg)
|
||||
if not msg.data or not msg.data.accountId or not msg.data.newName then
|
||||
return {success = false, error = "Invalid request. Requires data.accountId and data.newName."}
|
||||
end
|
||||
local success, errorMsg = renameAccount(msg.auth.username, msg.data.accountid, msg.data.newName)
|
||||
return {success = success, error = errorMsg}
|
||||
end
|
||||
|
||||
local function BANK_REQUESTS = {
|
||||
["CREATE_USER"] = handleCreateUser,
|
||||
["DELETE_USER"] = authProtect(handleDeleteUser)
|
||||
["RENAME_USER"] = authProtect(handleRenameUser)
|
||||
["GET_ACCOUNTS"] = authProtect(handleGetUserAccounts)
|
||||
["DELETE_ACCOUNT"] = authProtect(handleDeleteUserAccount)
|
||||
["RENAME_ACCOUNT"] = authProtect(handleRenameUserAccount)
|
||||
}
|
||||
|
||||
local function handleBankMessage(remoteId, msg)
|
||||
if msg == nil or msg.command == nil or type(msg.command) ~= "string" then
|
||||
return {success = false, error = "Invalid BANK request. Message is nil or missing \"command\" string property."}
|
||||
end
|
||||
if BANK_REQUESTS[msg.command] then
|
||||
return BANK_REQUESTS[msg.command](msg)
|
||||
end
|
||||
return {success = false, error = "Unknown command: \"" .. msg.command .. "\""}
|
||||
end
|
||||
|
||||
local function handleNetworkEvents()
|
||||
log("Initializing Rednet hosting...")
|
||||
rednet.open("top")
|
||||
|
@ -137,6 +284,11 @@ local function handleNetworkEvents()
|
|||
local remoteId, msg = rednet.receive("BANK", 3)
|
||||
if remoteId ~= nil then
|
||||
log("Received rednet message from computer ID " .. remoteId)
|
||||
local success, response = pcall(handleBankMessage, remoteId, msg)
|
||||
if not success then
|
||||
response = {success = false, error = "An error occurred: " .. response}
|
||||
end
|
||||
rednet.send(remoteId, response, "BANK")
|
||||
end
|
||||
end
|
||||
rednet.unhost("BANK")
|
||||
|
@ -147,9 +299,14 @@ local function handleGuiEvents()
|
|||
while RUNNING do
|
||||
local event, button, x, y = os.pullEvent("mouse_click")
|
||||
if button == 1 and y == 1 and x > W - 4 then
|
||||
log("Quitting...")
|
||||
RUNNING = false
|
||||
end
|
||||
end
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.white)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
end
|
||||
|
||||
local function handleEvents()
|
||||
|
|
Loading…
Reference in New Issue