diff --git a/server/addresses.js b/server/addresses.js new file mode 100644 index 0000000..c562216 --- /dev/null +++ b/server/addresses.js @@ -0,0 +1,129 @@ +const crypto = require('crypto') +const fs = require('fs/promises') + +const storePath = '/data/store.json' + +async function readStore() { + try { + const data = await fs.readFile(storePath, 'utf8') + return JSON.parse(data) + } catch (error) { + if (error.code === 'ENOENT') { + return {} + } + + throw error + } +} + +async function writeStore(store) { + const temporaryPath = + `${storePath}.${crypto.randomBytes(8).toString('hex')}.tmp` + + await fs.writeFile( + temporaryPath, + JSON.stringify(store, null, 2) + '\n', + { + mode: 0o600, + }, + ) + + await fs.rename(temporaryPath, storePath) +} + +function normalizeAddress(address) { + if (typeof address !== 'string') { + return null + } + + const normalized = address.trim() + + if (!normalized) { + return null + } + + return normalized +} + +function normalizeLabel(label) { + if (typeof label !== 'string') { + return undefined + } + + const normalized = label.trim() + + return normalized || undefined +} + +async function listAddresses() { + const store = await readStore() + + return Array.isArray(store.addresses) + ? store.addresses + : [] +} + +async function addAddress(address, label) { + const normalizedAddress = normalizeAddress(address) + + if (!normalizedAddress) { + throw new Error('Bitcoin address is required') + } + + const addresses = await listAddresses() + + if (addresses.some((item) => item.address === normalizedAddress)) { + throw new Error('Bitcoin address is already being watched') + } + + const entry = { + address: normalizedAddress, + } + + const normalizedLabel = normalizeLabel(label) + + if (normalizedLabel) { + entry.label = normalizedLabel + } + + const store = await readStore() + + store.addresses = [...addresses, entry] + + await writeStore(store) + + return entry +} + +async function removeAddress(address) { + const normalizedAddress = normalizeAddress(address) + + if (!normalizedAddress) { + throw new Error('Bitcoin address is required') + } + + const store = await readStore() + const addresses = Array.isArray(store.addresses) + ? store.addresses + : [] + + const filtered = addresses.filter( + (item) => item.address !== normalizedAddress, + ) + + if (filtered.length === addresses.length) { + throw new Error('Bitcoin address is not being watched') + } + + store.addresses = filtered + + await writeStore(store) + + return true +} + +module.exports = { + listAddresses, + addAddress, + removeAddress, +} \ No newline at end of file