diff --git a/backend/api/account/accountController.js b/backend/api/account/accountController.js
index 433614b..f36a56a 100644
--- a/backend/api/account/accountController.js
+++ b/backend/api/account/accountController.js
@@ -3,10 +3,9 @@ const accountService = require('./accountService');
const getAccountAllowance = async (req, res) => {
try {
- const response = await accountService.getAccountAllowance();
-
+ const username = req.body.username;
+ const response = await accountService.getAccountAllowance(username);
debug.info(`Account Allowance Response: ${JSON.stringify(response)}`);
-
if (!response.success) res.status(500).json(response);
else res.status(200).json(response);
@@ -18,10 +17,10 @@ const getAccountAllowance = async (req, res) => {
const payInvoice = async (req, res) => {
try {
- const response = await accountService.getAccountAllowance();
-
- debug.info(`Account Allowance Response: ${JSON.stringify(response)}`);
+ const invoice = req.body.invoice;
+ const response = await accountService.payInvoice(invoice);
+ debug.info(`Pay Invoice Response: ${JSON.stringify(response)}`);
if (!response.success) res.status(500).json(response);
else res.status(200).json(response);
@@ -31,12 +30,11 @@ const payInvoice = async (req, res) => {
}
};
-const createInvoice = async (req, res) => {
+const getAccountBalance = async (req, res) => {
try {
- const response = await accountService.getAccountAllowance();
-
- debug.info(`Account Allowance Response: ${JSON.stringify(response)}`);
+ const response = await accountService.getAccountBalance();
+ debug.info(`Account Balance Response: ${JSON.stringify(response)}`);
if (!response.success) res.status(500).json(response);
else res.status(200).json(response);
@@ -46,4 +44,18 @@ const createInvoice = async (req, res) => {
}
};
-module.exports = { getAccountAllowance, payInvoice, createInvoice };
+const createInvoice = async (req, res) => {
+ try {
+ const amountMillisats = req.body.amountMillisats;
+ const description = req.body.description;
+ const response = await accountService.createInvoice(amountMillisats, description);
+ debug.info(`Create Invoice Response: ${JSON.stringify(response)}`);
+ if (!response.success) res.status(500).json(response);
+ else res.status(200).json(response);
+ } catch (error) {
+ debug.error(error.stack);
+ res.status(500).json({ message: error.message, error: error.stack });
+ }
+};
+
+module.exports = { getAccountAllowance, payInvoice, createInvoice, getAccountBalance };
diff --git a/backend/api/account/accountService.js b/backend/api/account/accountService.js
index f94b8ba..16c000a 100644
--- a/backend/api/account/accountService.js
+++ b/backend/api/account/accountService.js
@@ -5,49 +5,41 @@ const accounts = require('../../db/collection');
const getAccountAllowance = async (username) => {
try {
// get allowance from firestore
- return { success: true, message: charge };
+ return { success: true, message: nodes };
} catch (error) {
- debug.error(error.stack, error.status, error.message);
+ debug.error(error.stack);
throw new Error(error);
}
};
-const getAccountBalance = async (username) => {
+const getAccountBalance = async () => {
try {
-
- return { success: true, message: charge };
+ const response = await senseiNodes.getBalance();
+ return { success: true, message: response.balance_satoshis };
} catch (error) {
- debug.error(error.stack, error.status, error.message);
+ debug.error(error.stack);
throw new Error(error);
}
};
-const payInvoice = async (req, res) => {
+const payInvoice = async (invoice) => {
try {
- const invoice = req.body.invoice;
const response = await senseiNodes.payInvoice(invoice);
- debug.info(`Pay Invoice Response: ${JSON.stringify(response)}`);
- if (!response.success) res.status(500).json(response);
- else res.status(200).json(response);
+ return { success: true, message: response };
} catch (error) {
debug.error(error.stack);
- res.status(500).json({ message: error.message, error: error.stack });
+ throw new Error(error);
}
};
-const createInvoice = async (req, res) => {
+const createInvoice = async (amountMillisats, description) => {
try {
- const amountMillisats = req.body.amountMillisats;
- const description = req.body.description;
const response = await senseiNodes.createInvoice(amountMillisats, description);
- debug.info(`Create Invoice Response: ${JSON.stringify(response)}`);
- if (!response.success) res.status(500).json(response);
- else res.status(200).json(response);
-
+ return { success: true, message: response };
} catch (error) {
debug.error(error.stack);
- res.status(500).json({ message: error.message, error: error.stack });
+ throw new Error(error);
}
};
-module.exports = { getAccountAllowance, payInvoice, createInvoice };
+module.exports = { getAccountAllowance, payInvoice, createInvoice, getAccountBalance };
diff --git a/backend/api/account/index.js b/backend/api/account/index.js
index 32a31c9..98ac1ff 100644
--- a/backend/api/account/index.js
+++ b/backend/api/account/index.js
@@ -1,11 +1,9 @@
const express = require('express');
const router = express();
-const { getAccountAllowance, payInvoice, createInvoice } = require('./accountController');
+const { getAccountAllowance, payInvoice, createInvoice, getAccountBalance } = require('./accountController');
-const { getBalance } = require('../../sensei/nodes');
-
-router.get('/account/balance', getBalance);
+router.get('/account/balance', getAccountBalance);
router.get('/allowance', getAccountAllowance);
router.get('/payment/send', payInvoice);
router.get('/payment/receive', createInvoice);
diff --git a/backend/api/admin/adminController.js b/backend/api/admin/adminController.js
index c463efe..d9e9da5 100644
--- a/backend/api/admin/adminController.js
+++ b/backend/api/admin/adminController.js
@@ -51,4 +51,52 @@ const keysend = async (req, res) => {
}
}
-module.exports = { getAllBalances, addNewAccount, keysend };
+// Request body:
+// ```
+// {
+// permissions: {
+// isAdmin: boolean,
+// hasAllowance: boolean,
+// canSpend: boolean,
+// }
+// }
+// ```
+const updatePermissions = async (req, res) => {
+ try {
+ // TODO: Input verification logic?
+ const accountName = req.params.username;
+ const newPermissions = req.body.permissions;
+ const response = await adminService.updatePermissions(accountName, newPermissions);
+ debug.info(`Response for updating a family member's permissions: ${JSON.stringify(response)}`);
+
+ if (!response.success) res.status(500).json(response);
+ else res.status(200).json(response);
+ } catch (error) {
+ debug.error(error.stack);
+ res.status(500).json({ message: error.message, error: error.stack });
+ }
+};
+
+// Request body:
+// ```
+// {
+// allowance: number,
+// }
+// ```
+const setAccountAllowance = async (req, res) => {
+ try {
+ // TODO: Input verification logic?
+ const accountName = req.params.username;
+ const newAllowance = req.body.allowance;
+ const response = await adminService.setAccountAllowance(accountName, newAllowance);
+ debug.info(`Response for setting a family member's allowance: ${JSON.stringify(response)}`);
+
+ if (!response.success) res.status(500).json(response);
+ else res.status(200).json(response);
+ } catch (error) {
+ debug.error(error.stack);
+ res.status(500).json({ message: error.message, error: error.stack });
+ }
+};
+
+module.exports = { getAllBalances, addNewAccount, keysend, updatePermissions, setAccountAllowance };
diff --git a/backend/api/admin/adminService.js b/backend/api/admin/adminService.js
index 61896b1..25251c3 100644
--- a/backend/api/admin/adminService.js
+++ b/backend/api/admin/adminService.js
@@ -1,14 +1,17 @@
const debug = require('../../utils/debug');
-const accounts = require('../../db/collection');
+const collection = require('../../db/collection');
const senseiAdmin = require('../../sensei/admin');
const senseiNodes = require('../../sensei/nodes');
+const DOC_NAME = 'nicks-family';
+const MEMBERS_SUBCOLLECTION_NAME = 'members';
+
const getAllBalances = async (req, res) => {
try {
const response = await senseiAdmin.listNodes();
let nodes = [];
for (let node of response.nodes) {
- node.balance = (await senseiNodes.getBalance(node.username)).balance_satoshis;
+ node.balance = (await senseiNodes.getBalance()).balance_satoshis;
nodes.push(node);
}
return { success: true, message: nodes };
@@ -18,4 +21,32 @@ const getAllBalances = async (req, res) => {
}
};
-module.exports = { getAllBalances };
+const updatePermissions = async (accountName, newPermissions) => {
+ const docRef = await collection.doc(DOC_NAME).collection(MEMBERS_SUBCOLLECTION_NAME).doc(accountName);
+ const doc = await docRef.get();
+ if (!doc.exists) {
+ const errMsg = `Firestore document "${docName}/${subcollectionName}/${accountName}" does not exist in the families collection`;
+ debug.error(errMsg);
+ throw new Error(errMsg);
+ }
+
+ // TODO: Error handling?
+ await docRef.update({permissions: newPermissions});
+ return { success: true };
+};
+
+const setAccountAllowance = async (accountName, newAllowance) => {
+ const docRef = await collection.doc(DOC_NAME).collection(MEMBERS_SUBCOLLECTION_NAME).doc(accountName);
+ const doc = await docRef.get();
+ if (!doc.exists) {
+ const errMsg = `Firestore document "${docName}/${subcollectionName}/${accountName}" does not exist in the families collection`;
+ debug.error(errMsg);
+ throw new Error(errMsg);
+ }
+
+ // TODO: Error handling?
+ await docRef.update({ allowance: newAllowance, "permissions.hasAllowance": true });
+ return { success: true };
+};
+
+module.exports = { getAllBalances, updatePermissions, setAccountAllowance };
diff --git a/backend/api/admin/index.js b/backend/api/admin/index.js
index 1c6b6a1..a73d5ed 100644
--- a/backend/api/admin/index.js
+++ b/backend/api/admin/index.js
@@ -1,10 +1,12 @@
const express = require('express');
const router = express();
-const { getAllBalances, addNewAccount, keysend } = require('./adminController');
+const { getAllBalances, addNewAccount, keysend, updatePermissions, setAccountAllowance } = require('./adminController');
router.get('/balances', getAllBalances);
router.post('/add', addNewAccount);
router.post('/transfer', keysend);
+router.post('/account/:username/permissions', updatePermissions)
+router.post('/account/:username/allowance', setAccountAllowance)
module.exports = router;
diff --git a/backend/sensei/admin.js b/backend/sensei/admin.js
index f8510e9..f321241 100644
--- a/backend/sensei/admin.js
+++ b/backend/sensei/admin.js
@@ -1,205 +1,73 @@
-const fetch = require('node-fetch');
-const BASE_URL = process.env.BASE_URL;
-const MACAROON = process.env.MACAROON;
-const TOKEN = process.env.TOKEN;
+const { apiCall } = require('../utils/apiCall');
const initSensei = async (username, passphrase, alias, electrum_url, start) => {
- const res = await fetch(`${BASE_URL}/v1/init`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- body: {
- username,
- passphrase,
- alias,
- electrum_url,
- start,
- },
+ const res = await apiCall('/v1/init', 'POST', {
+ username,
+ passphrase,
+ alias,
+ electrum_url,
+ start,
});
-
- console.log(res);
return await res.json();
};
const listNodes = async (page, take, query) => {
- const res = await fetch(`${BASE_URL}/v1/nodes?page=${0}&take=${10}`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- }
- });
-
- console.log(res);
+ const res = await apiCall('/v1/nodes?page=${0}&take=${10}', 'GET');
return await res.json();
};
const createNode = async (username, passphrase, alias, start) => {
- const res = await fetch(`${BASE_URL}/v1/nodes`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- body: {
- username,
- passphrase,
- alias,
- start,
- },
+ const res = await apiCall('/v1/nodes', 'POST', {
+ username,
+ passphrase,
+ alias,
+ start,
});
-
- console.log(res);
return await res.json();
};
const startNode = async (pubkey, passphrase) => {
- const res = await fetch(`${BASE_URL}/v1/nodes/start`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- body: {
- pubkey,
- passphrase,
- },
- });
-
- console.log(res);
+ const res = await apiCall('/v1/nodes/start', 'POST', { pubkey, passphrase });
return await res.json();
};
const stopNode = async (pubkey) => {
- const res = await fetch(`${BASE_URL}/v1/nodes/stop`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- body: {
- pubkey,
- },
- });
-
- console.log(res);
+ const res = await apiCall('/v1/nodes/stop', 'POST', { pubkey });
return await res.json();
};
const deleteNode = async (pubkey) => {
- const res = await fetch(`${BASE_URL}/v1/nodes/delete`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- body: {
- pubkey,
- },
- });
-
- console.log(res);
+ const res = await apiCall('/v1/nodes/delete', 'POST', { pubkey });
return await res.json();
};
const nodeStatus = async () => {
- const res = await fetch(`${BASE_URL}/v1/status`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- });
-
- console.log(res);
+ const res = await apiCall('/v1/status', 'GET');
return await res.json();
};
const startSensi = async (passphrase) => {
- const res = await fetch(`${BASE_URL}/v1/start`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- body: {
- passphrase,
- },
- });
-
- console.log(res);
+ const res = await apiCall('/v1/start', 'POST', { passphrase });
return await res.json();
};
const login = async (username, passphrase) => {
- const res = await fetch(`${BASE_URL}/v1/login`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- body: {
- username,
- passphrase,
- },
- });
-
- console.log(res);
+ const res = await apiCall('/v1/login', 'POST', { username, passphrase });
return await res.json();
};
const logout = async () => {
- const res = await fetch(`${BASE_URL}/v1/logout`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- });
-
- console.log(res);
+ const res = await apiCall('/v1/logout', 'POST');
return await res.json();
};
const getConfig = async () => {
- const res = await fetch(`${BASE_URL}/v1/config`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- });
-
- console.log(res);
+ const res = await apiCall('/v1/config', 'GET');
return await res.json();
};
const updateConfig = async (electrum_url) => {
- const res = await fetch(`${BASE_URL}/v1/config`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Cookie': `macaroon=${MACAROON}; token=${TOKEN}`
- },
- credentials: 'include',
- body: {
- electrum_url,
- },
- });
-
- console.log(res);
+ const res = await apiCall('/v1/config', 'POST', { electrum_url });
return await res.json();
};
diff --git a/frontend/public/brontosaurus.svg b/frontend/public/brontosaurus.svg
new file mode 100644
index 0000000..0ebf409
--- /dev/null
+++ b/frontend/public/brontosaurus.svg
@@ -0,0 +1,111 @@
+
+
+
diff --git a/frontend/public/flowers.jpg b/frontend/public/flowers.jpg
new file mode 100644
index 0000000..97fc70f
Binary files /dev/null and b/frontend/public/flowers.jpg differ
diff --git a/frontend/public/spinosaurus.svg b/frontend/public/spinosaurus.svg
new file mode 100644
index 0000000..aaa585d
--- /dev/null
+++ b/frontend/public/spinosaurus.svg
@@ -0,0 +1,118 @@
+
+
+
diff --git a/frontend/src/App.js b/frontend/src/App.js
index ae375b5..3403edf 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -18,6 +18,8 @@ import { ContactsIcon, CrossIcon, GearIcon, HomeIcon, MenuIcon } from '@bitcoin-
import { ChartSquareBarIcon } from '@heroicons/react/solid';
import NewFamily from './routes/NewFamily';
import Invite from './routes/Invite';
+import Kid from './routes/Kid';
+import Grandma from './routes/Grandma';
const Child = {};
@@ -78,6 +80,8 @@ function App() {
70,000,000 sats
+{tx.date}
+{tx.memo}
+{tx.amount}
+1000 sats
+{tx.date}
+{tx.memo}
+{tx.amount}
+