Added transactions

This commit is contained in:
Andrew Lalis 2023-08-29 15:36:53 -04:00
parent d5b2016dc3
commit ffd620f172
2 changed files with 89 additions and 16 deletions

View File

@ -9,6 +9,7 @@ local client = {}
client.state = { client.state = {
auth = nil, auth = nil,
hostId = nil, hostId = nil,
securityKey = nil,
timeout = 3 timeout = 3
} }
@ -28,18 +29,28 @@ local function request(command, data)
return requestRaw({command = command, data = data}) return requestRaw({command = command, data = data})
end end
local function requestAuth(command, data) local function requestAuth(command, data, secure)
secure = secure or false
if not client.loggedIn() then if not client.loggedIn() then
return {success = false, error = "Client not logged in"} return {success = false, error = "Client not logged in"}
end end
return requestRaw({command = command, auth = client.state.auth, data = data}) local authInfo = {
username = client.state.auth.username,
password = client.state.auth.password
}
if secure and not client.state.securityKey then
return {success = false, error = "Missing security key for secure request."}
end
autInfo.key = client.state.securityKey
return requestRaw({command = command, auth = authInfo, data = data})
end end
-- Base functions -- Base functions
function client.init(modemName, host) function client.init(modemName, host, securityKey)
rednet.open(modemName) rednet.open(modemName)
client.state.hostId = rednet.lookup("BANK", host) client.state.hostId = rednet.lookup("BANK", host)
client.state.securityKey = securityKey or nil
return client.state.hostId ~= nil return client.state.hostId ~= nil
end end
@ -103,4 +114,12 @@ function client.renameAccount(accountId, newName)
return response.success, response.error return response.success, response.error
end end
function client.recordTransaction(accountId, amount, description)
local response = requestAuth("RECORD_TRANSACTION", {accountId = accountId, amount = amount, description = description}, true)
if not response.success then
return nil, response.error
end
return response.data
end
return client return client

View File

@ -55,6 +55,10 @@ local function validateUsername(name)
) )
end end
local function validateTransactionDescription(desc)
return string.find(desc, "^%w+[ !%.%w]*$") ~= nil and #desc <= 64
end
local function userDir(name) local function userDir(name)
return fs.combine(USERS_DIR, name) return fs.combine(USERS_DIR, name)
end end
@ -102,12 +106,28 @@ local function saveAccounts(name, accounts)
writeJSON(userAccountsFile(name), accounts) writeJSON(userAccountsFile(name), accounts)
end end
local function findAccountById(accounts, id)
for i, account in pairs(accounts) do
if account.id == id then
return account
end
end
return nil
end
local function findAccountByName(accounts, name)
for i, account in pairs(accounts) do
if account.name == name then
return account
end
end
return nil
end
local function createAccount(username, accountName) local function createAccount(username, accountName)
local accounts = getAccounts(username) local accounts = getAccounts(username)
for i, account in pairs(accounts) do if findAccountByName(accounts, accountName) then
if account.name == accountName then return false, "Duplicate account name"
return false, "Duplicate account name"
end
end end
local newAccount = { local newAccount = {
id = randomAccountId(), id = randomAccountId(),
@ -140,15 +160,11 @@ end
local function renameAccount(username, accountId, newName) local function renameAccount(username, accountId, newName)
local accounts = getAccounts(username) local accounts = getAccounts(username)
local targetAccount = nil local targetAccount = findAccountById(accounts, accountId)
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 if not targetAccount then return false, "Account not found" end
if findAccountByName(accounts, newName) ~= nil then
return false, "Duplicate account name"
end
targetAccount.name = newName targetAccount.name = newName
saveAccounts(accounts) saveAccounts(accounts)
log("Renamed user " .. username .. " account " .. accountId .. " to " .. newName) log("Renamed user " .. username .. " account " .. accountId .. " to " .. newName)
@ -188,6 +204,32 @@ local function renameUser(oldName, newName)
return true return true
end end
local function recordTransaction(username, accountId, amount, description)
if not validateTransactionDescription(description) then return false, "Invalid transaction description" end
if not userExists(username) then return false, "User doesn't exist" end
local accounts = getAccounts(username)
local account = findAccountById(accounts, accountId)
if account == nil then return false, "Account doesn't exist" end
if account.balance + amount < 0 then return false, "Insufficient funds" end
-- Everything is OK, record the transaction.
local tx = {
amount = amount,
description = description,
timestamp = os.epoch("utc")
}
local f = io.open(accountTransactionsFile(username, accountId), "a")
local txStr = tostring(tx.amount)..";"..tostring(tx.timestamp)..";"..tx.description
f:write(txStr .. string.rep(" ", 99 - #txStr) .. "\n")
f:close()
if fs.getSize(accountTransactionsFile(username, accountId)) % 100 ~= 0 then
log("WARNING! Transaction file for account " .. accountId .. " is not consistent!")
end
account.balance = account.balance + amount
saveAccounts(username, accounts)
os.queueEvent("bank_account_balance", username, accountId, account.balance)
return true, tx
end
local function initSecurityKey() local function initSecurityKey()
-- Initialize security key -- Initialize security key
local SECURITY_KEY_FILE = "key.txt" local SECURITY_KEY_FILE = "key.txt"
@ -290,6 +332,17 @@ local function handleRenameUserAccount(msg)
return {success = success, error = errorMsg} return {success = success, error = errorMsg}
end end
local function handleRecordTransactionToAccount(msg)
if not msg.data or not msg.data.amount or not msg.data.description or not msg.data.accountId then
return {success = false, error = "Invalid request. Requires data.amount and data.description and data.accountId."}
end
local success, errorMsgOrTx = recordTransaction(msg.auth.username, msg.data.accountId, msg.data.amount, msg.data.description)
if not success then
return {success = false, error = errorMsgOrTx}
end
return {success = true, data = errorMsgOrTx}
end
-- A registry of all possible BANK requests, and their handler functions. -- A registry of all possible BANK requests, and their handler functions.
local BANK_REQUESTS = { local BANK_REQUESTS = {
["STATUS"] = handleGetStatus, ["STATUS"] = handleGetStatus,
@ -299,7 +352,8 @@ local BANK_REQUESTS = {
["GET_ACCOUNTS"] = authProtect(handleGetUserAccounts), ["GET_ACCOUNTS"] = authProtect(handleGetUserAccounts),
["CREATE_ACCOUNT"] = authProtect(handleCreateUserAccount), ["CREATE_ACCOUNT"] = authProtect(handleCreateUserAccount),
["DELETE_ACCOUNT"] = authProtect(handleDeleteUserAccount), ["DELETE_ACCOUNT"] = authProtect(handleDeleteUserAccount),
["RENAME_ACCOUNT"] = authProtect(handleRenameUserAccount) ["RENAME_ACCOUNT"] = authProtect(handleRenameUserAccount),
["RECORD_TRANSACTION"] = authProtect(handleRecordTransactionToAccount, true)
} }
local function handleBankMessage(remoteId, msg) local function handleBankMessage(remoteId, msg)