Removed sqlite3

Removed sqlite3
Release-0.12.0
Shahana Farooqui 2 years ago
parent 0c893baf56
commit 0055301391

@ -47,7 +47,7 @@
"curly": "error",
"no-unused-expressions": "error",
"strict": "error",
"max-len": ["error", { "code": 430 }],
"max-len": ["error", { "code": 450 }],
"no-multiple-empty-lines": "error",
"no-trailing-spaces": "error",
"quote-props": ["error", "as-needed"],

@ -34,28 +34,6 @@ MIT
@angular/flex-layout
MIT
The MIT License
Copyright (c) 2020 Google LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@angular/forms
MIT
@ -661,6 +639,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
clone-deep
MIT
The MIT License (MIT)
Copyright (c) 2014-2018, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
convert-hex
convert-string
@ -1497,6 +1500,80 @@ PERFORMANCE OF THIS SOFTWARE.
is-plain-object
MIT
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
isobject
MIT
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
kind-of
MIT
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
md5.js
MIT
The MIT License (MIT)
@ -2211,6 +2288,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
sha256
shallow-clone
MIT
The MIT License (MIT)
Copyright (c) 2015-present, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
stream-browserify
MIT
MIT License

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -13,6 +13,6 @@
<style>@font-face{font-family:Roboto;src:url(Roboto-Thin.f7a95c9c5999532c.woff2) format("woff2"),url(Roboto-Thin.c13c157cb81e8ebb.woff) format("woff");font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-ThinItalic.b0e084abf689f393.woff2) format("woff2"),url(Roboto-ThinItalic.1111028df6cea564.woff) format("woff");font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Light.0e01b6cd13b3857f.woff2) format("woff2"),url(Roboto-Light.603ca9a537b88428.woff) format("woff");font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-LightItalic.232ef4b20215f720.woff2) format("woff2"),url(Roboto-LightItalic.1b5e142f787151c8.woff) format("woff");font-weight:300;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Regular.475ba9e4e2d63456.woff2) format("woff2"),url(Roboto-Regular.bcefbfee882bc1cb.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-RegularItalic.e3a9ebdaac06bbc4.woff2) format("woff2"),url(Roboto-RegularItalic.0668fae6af0cf8c2.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Medium.457532032ceb0168.woff2) format("woff2"),url(Roboto-Medium.6e1ae5f0b324a0aa.woff) format("woff");font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-MediumItalic.872f7060602d55d2.woff2) format("woff2"),url(Roboto-MediumItalic.e06fb533801cbb08.woff) format("woff");font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Bold.447291a88c067396.woff2) format("woff2"),url(Roboto-Bold.fc482e6133cf5e26.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BoldItalic.1b15168ef6fa4e16.woff2) format("woff2"),url(Roboto-BoldItalic.e26ba339b06f09f7.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Black.2eaa390d458c877d.woff2) format("woff2"),url(Roboto-Black.b25f67ad8583da68.woff) format("woff");font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BlackItalic.7dc03ee444552bc5.woff2) format("woff2"),url(Roboto-BlackItalic.c8dc642467cb3099.woff) format("woff");font-weight:900;font-style:italic}html{width:100%;height:99%;line-height:1.5;overflow-x:hidden;font-family:Roboto,sans-serif!important;font-size:62.5%}body{box-sizing:border-box;height:100%;margin:0;overflow:hidden}*{margin:0;padding:0}</style><link rel="stylesheet" href="styles.2c38f7d09aa7e379.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.2c38f7d09aa7e379.css"></noscript></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.c84b8c161f5af2f0.js" type="module"></script><script src="polyfills.6d989da208bd6fd1.js" type="module"></script><script src="main.0bd11002cc3b3db2.js" type="module"></script>
<script src="runtime.e3525a6108cbcf3c.js" type="module"></script><script src="polyfills.6d989da208bd6fd1.js" type="module"></script><script src="main.d82eeb0bf14b1cdd.js" type="module"></script>
</body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
(()=>{"use strict";var e,v={},g={};function r(e){var i=g[e];if(void 0!==i)return i.exports;var t=g[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(i,t,f,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,f,o]=e[n],c=!0,u=0;u<t.length;u++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[u]))?t.splice(u--,1):(c=!1,o<a&&(a=o));if(c){e.splice(n--,1);var d=f();void 0!==d&&(i=d)}}return i}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,f,o]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>e+"."+{632:"ed9e371233e76094",637:"2fc9559ec90af37c",859:"dccba581e7254ce5",893:"277223bd17cb8056"}[e]+".js",r.miniCssF=e=>{},r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="RTLApp:";r.l=(t,f,o,n)=>{if(e[t])e[t].push(f);else{var a,c;if(void 0!==o)for(var u=document.getElementsByTagName("script"),d=0;d<u.length;d++){var l=u[d];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==i+o){a=l;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",i+o),a.src=r.tu(t)),e[t]=[f];var s=(m,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(_=>_(b)),m)return m(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var n=r.o(e,f)?e[f]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=f){var a=new Promise((l,s)=>n=e[f]=[l,s]);o.push(n[2]=a);var c=r.p+r.u(f),u=new Error;r.l(c,l=>{if(r.o(e,f)&&(0!==(n=e[f])&&(e[f]=void 0),n)){var s=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;u.message="Loading chunk "+f+" failed.\n("+s+": "+p+")",u.name="ChunkLoadError",u.type=s,u.request=p,n[1](u)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var i=(f,o)=>{var u,d,[n,a,c]=o,l=0;if(n.some(p=>0!==e[p])){for(u in a)r.o(a,u)&&(r.m[u]=a[u]);if(c)var s=c(r)}for(f&&f(o);l<n.length;l++)r.o(e,d=n[l])&&e[d]&&e[d][0](),e[n[l]]=0;return r.O(s)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(i.bind(null,0)),t.push=i.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,v={},g={};function r(e){var i=g[e];if(void 0!==i)return i.exports;var t=g[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(i,t,f,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,f,o]=e[n],s=!0,u=0;u<t.length;u++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[u]))?t.splice(u--,1):(s=!1,o<a&&(a=o));if(s){e.splice(n--,1);var d=f();void 0!==d&&(i=d)}}return i}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,f,o]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>e+"."+{632:"4af5fdd4fe008747",637:"ba85cb517f1848fc",859:"a1744360a2102eb6",893:"a44950223f73d9f3"}[e]+".js",r.miniCssF=e=>{},r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="RTLApp:";r.l=(t,f,o,n)=>{if(e[t])e[t].push(f);else{var a,s;if(void 0!==o)for(var u=document.getElementsByTagName("script"),d=0;d<u.length;d++){var l=u[d];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==i+o){a=l;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",i+o),a.src=r.tu(t)),e[t]=[f];var c=(m,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(_=>_(b)),m)return m(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var n=r.o(e,f)?e[f]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=f){var a=new Promise((l,c)=>n=e[f]=[l,c]);o.push(n[2]=a);var s=r.p+r.u(f),u=new Error;r.l(s,l=>{if(r.o(e,f)&&(0!==(n=e[f])&&(e[f]=void 0),n)){var c=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;u.message="Loading chunk "+f+" failed.\n("+c+": "+p+")",u.name="ChunkLoadError",u.type=c,u.request=p,n[1](u)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var i=(f,o)=>{var u,d,[n,a,s]=o,l=0;if(n.some(p=>0!==e[p])){for(u in a)r.o(a,u)&&(r.m[u]=a[u]);if(s)var c=s(r)}for(f&&f(o);l<n.length;l++)r.o(e,d=n[l])&&e[d]&&e[d][0](),e[n[l]]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(i.bind(null,0)),t.push=i.bind(null,t.push.bind(t))})()})();

@ -1,4 +1,5 @@
import request from 'request-promise';
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { CLWSClient } from './webSocketClient.js';
@ -6,6 +7,7 @@ let options = null;
const logger = Logger;
const common = Common;
const clWsClient = CLWSClient;
const databaseService = Database;
export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting CLightning Node Information..' });
common.logEnvVariables(req);
@ -67,6 +69,7 @@ export const getInfo = (req, res, next) => {
req.session.selectedNode.api_version = body.api_version || '';
req.session.selectedNode.ln_version = body.version || '';
clWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'CLightning Node Information Received' });
res.status(200).json(body);
}

@ -2,13 +2,14 @@ import request from 'request-promise';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { Database } from '../../utils/database.js';
import { CollectionFieldsEnum, CollectionsEnum } from '../../models/database.model.js';
let options = null;
const logger = Logger;
const common = Common;
const DB = Database;
const databaseService = Database;
export const listOfferBookmarks = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offer Bookmarks..' });
DB.rtlDB.offer.findAll().then((offers) => {
databaseService.find(req.session.selectedNode, CollectionsEnum.OFFERS).then((offers) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Bookmarks Received', data: offers });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmarks Received' });
res.status(200).json(offers);
@ -19,10 +20,10 @@ export const listOfferBookmarks = (req, res, next) => {
};
export const deleteOfferBookmark = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Deleting Offer Bookmark..' });
DB.rtlDB.offer.destroy({ where: { id: req.params.offerUUID } }).then((deleteRes) => {
databaseService.destroy(req.session.selectedNode, CollectionsEnum.OFFERS, CollectionFieldsEnum.BOLT12, req.params.offerStr).then((deleteRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Bookmark Deleted', data: deleteRes });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmark Deleted' });
res.status(204).json({ id: req.params.offerUUID });
res.status(204).json(req.params.offerStr);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Offers', 'Offer Bookmark Delete Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });

@ -1,13 +1,12 @@
import request from 'request-promise';
import * as uuidModule from 'uuid';
const { v4 } = uuidModule;
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { Database } from '../../utils/database.js';
import { CollectionFieldsEnum, CollectionsEnum } from '../../models/database.model.js';
let options = null;
const logger = Logger;
const common = Common;
const DB = Database;
const databaseService = Database;
function paymentReducer(accumulator, currentPayment) {
const currPayHash = currentPayment.payment_hash;
if (!currentPayment.partid) {
@ -113,32 +112,26 @@ export const postPayment = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent' });
if (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB) {
let offer = {};
if (req.body.offerUUID) {
let updatedOffer = { title: req.body.title, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount) };
return DB.rtlDB.offer.update(updatedOffer, { where: { id: req.body.offerUUID } }).then((updatedOffer) => {
offer = { id: req.body.offerUUID, title: req.body.title, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), updatedAt: new Date(Date.now()) };
if (req.body.bolt12) {
const offerToUpdate = { bolt12: req.body.bolt12, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
if (req.body.vendor) {
offerToUpdate['vendor'] = req.body.vendor;
}
if (req.body.description) {
offerToUpdate['description'] = req.body.description;
}
return databaseService.update(req.session.selectedNode, CollectionsEnum.OFFERS, offerToUpdate, CollectionFieldsEnum.BOLT12, req.body.bolt12).then((updatedOffer) => {
logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Updated', data: updatedOffer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: offer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: updatedOffer });
}).catch((errDB) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB update error', error: errDB });
return res.status(201).json({ paymentResponse: body, saveToDBError: errDB });
});
}
else {
offer = { id: v4(), offerBolt12: req.body.offerBolt12, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, vendor: req.body.vendor, description: req.body.description };
return DB.rtlDB.offer.create(offer).then((savedOffer) => {
logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Saved', data: savedOffer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: savedOffer.dataValues });
}).catch((errDB) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB save error', error: errDB });
return res.status(201).json({ paymentResponse: body, saveToDBError: errDB });
});
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
}
}
else {
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
}
}
if (req.body.paymentType === 'INVOICE') {
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });

@ -1,4 +1,5 @@
import request from 'request-promise';
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { ECLWSClient } from './webSocketClient.js';
@ -6,6 +7,7 @@ let options = null;
const logger = Logger;
const common = Common;
const eclWsClient = ECLWSClient;
const databaseService = Database;
export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting Eclair Node Information..' });
common.logEnvVariables(req);
@ -38,9 +40,9 @@ export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Eclair Node Information Received' });
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
eclWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
res.status(200).json(body);
}).
catch((errRes) => {
}).catch((errRes) => {
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});

@ -1,4 +1,5 @@
import request from 'request-promise';
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { LNDWSClient } from './webSocketClient.js';
@ -6,6 +7,7 @@ let options = null;
const logger = Logger;
const common = Common;
const lndWsClient = LNDWSClient;
const databaseService = Database;
export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting LND Node Information..' });
common.logEnvVariables(req);
@ -44,6 +46,7 @@ export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'LND Node Information Received' });
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
lndWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
res.status(200).json(body);
}
}).catch((errRes) => {

@ -3,6 +3,7 @@ import { sep } from 'path';
import ini from 'ini';
import parseHocon from 'hocon-parser';
import request from 'request-promise';
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js';
@ -10,12 +11,16 @@ const options = { url: '' };
const logger = Logger;
const common = Common;
const wsServer = WSServer;
const databaseService = Database;
export const updateSelectedNode = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating Selected Node..' });
const selNodeIndex = req.body.currNodeIndex ? req.body.currNodeIndex : common.initSelectedNode ? common.initSelectedNode.index : 1;
req.session.selectedNode = common.findNode(selNodeIndex);
if (req.headers && req.headers.authorization && req.headers.authorization !== '') {
wsServer.updateLNWSClientDetails(req.session.id, +req.session.selectedNode.index, +req.body.prevNodeIndex);
if (req.body.prevNodeIndex !== -1) {
databaseService.unloadDatabase(req.body.prevNodeIndex);
}
}
const responseVal = !req.session.selectedNode.ln_node ? '' : req.session.selectedNode.ln_node;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Selected Node Updated To', data: responseVal });
@ -336,6 +341,7 @@ export const updateServiceSettings = (req, res, next) => {
}
common.replaceNode(req, selectedNode);
}
return node;
});
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');

@ -1,6 +1,7 @@
import jwt from 'jsonwebtoken';
import * as otplib from 'otplib';
import * as crypto from 'crypto';
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
const logger = Logger;
@ -9,6 +10,7 @@ const ONE_MINUTE = 60000;
const LOCKING_PERIOD = 30 * ONE_MINUTE; // HALF AN HOUR
const ALLOWED_LOGIN_ATTEMPTS = 5;
const failedLoginAttempts = {};
const databaseService = Database;
const loginInterval = setInterval(() => {
for (const ip in failedLoginAttempts) {
if (new Date().getTime() > (failedLoginAttempts[ip].lastTried + LOCKING_PERIOD)) {
@ -122,6 +124,9 @@ export const resetPassword = (req, res, next) => {
};
export const logoutUser = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'Logged out' });
if (req.session.selectedNode && req.session.selectedNode.index) {
databaseService.unloadDatabase(+req.session.selectedNode.index);
}
req.session.destroy();
res.status(200).json({ loggedout: true });
};

@ -0,0 +1,38 @@
export var CollectionsEnum;
(function (CollectionsEnum) {
CollectionsEnum["OFFERS"] = "Offers";
})(CollectionsEnum || (CollectionsEnum = {}));
export var OfferFieldsEnum;
(function (OfferFieldsEnum) {
OfferFieldsEnum["BOLT12"] = "bolt12";
OfferFieldsEnum["AMOUNTMSAT"] = "amountmSat";
OfferFieldsEnum["TITLE"] = "title";
OfferFieldsEnum["VENDOR"] = "vendor";
OfferFieldsEnum["DESCRIPTION"] = "description";
})(OfferFieldsEnum || (OfferFieldsEnum = {}));
export const CollectionFieldsEnum = Object.assign({}, OfferFieldsEnum);
export class Offer {
constructor(bolt12, amountmSat, title, vendor, description, lastUpdatedAt) {
this.bolt12 = bolt12;
this.amountmSat = amountmSat;
this.title = title;
this.vendor = vendor;
this.description = description;
this.lastUpdatedAt = lastUpdatedAt;
}
}
export const validateOffer = (documentToValidate) => {
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.BOLT12)) {
return ({ isValid: false, error: CollectionFieldsEnum.BOLT12 + 'is mandatory.' });
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.AMOUNTMSAT)) {
return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'is mandatory.' });
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TITLE)) {
return ({ isValid: false, error: CollectionFieldsEnum.TITLE + 'is mandatory.' });
}
if ((typeof documentToValidate[CollectionFieldsEnum.AMOUNTMSAT] !== 'number')) {
return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'should be a number.' });
}
return ({ isValid: true });
};

@ -1,30 +0,0 @@
export const Offer = (sequelize, Sequelize) => {
const offerInstance = sequelize.define('Offers', {
id: {
type: Sequelize.UUIDV4,
primaryKey: true,
allowNull: false
},
offerBolt12: {
type: Sequelize.STRING,
allowNull: false
},
amountmSat: {
type: Sequelize.NUMBER,
allowNull: false
},
title: {
type: Sequelize.STRING,
allowNull: false
},
vendor: {
type: Sequelize.STRING,
allowNull: true
},
description: {
type: Sequelize.STRING,
allowNull: true
}
});
return offerInstance;
};

@ -4,7 +4,7 @@ import { isAuthenticated } from '../../utils/authCheck.js';
import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, createOffer, fetchOfferInvoice } from '../../controllers/c-lightning/offers.js';
const router = Router();
router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks);
router.delete('/offerbookmark/:offerUUID', isAuthenticated, deleteOfferBookmark);
router.delete('/offerbookmark/:offerStr', isAuthenticated, deleteOfferBookmark);
router.get('/', isAuthenticated, listOffers);
router.post('/', isAuthenticated, createOffer);
router.post('/fetchOfferInvoice', isAuthenticated, fetchOfferInvoice);

@ -13,7 +13,6 @@ import eclRoutes from '../routes/eclair/index.js';
import { Common } from './common.js';
import { Logger } from './logger.js';
import { Config } from './config.js';
import { Database } from './database.js';
import { CLWSClient } from '../controllers/c-lightning/webSocketClient.js';
import { ECLWSClient } from '../controllers/eclair/webSocketClient.js';
import { LNDWSClient } from '../controllers/lnd/webSocketClient.js';
@ -24,7 +23,6 @@ export class ExpressApplication {
this.logger = Logger;
this.common = Common;
this.config = Config;
this.DB = Database;
this.eclWsClient = ECLWSClient;
this.clWsClient = CLWSClient;
this.lndWsClient = LNDWSClient;
@ -33,13 +31,6 @@ export class ExpressApplication {
this.loadConfiguration = () => {
this.config.setServerConfiguration();
};
this.loadDb = () => {
if (this.DB.rtlSequelize) {
this.DB.rtlSequelize.sync().then(() => {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'DEBUG', fileName: 'App', msg: 'Database Connected' });
});
}
};
this.setCORS = () => { CORS.mount(this.app); };
this.setCSRF = () => { CSRF.mount(this.app); };
this.setApplicationRoutes = () => {
@ -89,7 +80,6 @@ export class ExpressApplication {
this.setCORS();
this.setCSRF();
this.setApplicationRoutes();
this.loadDb();
}
}
export default ExpressApplication;

@ -328,7 +328,7 @@ export class CommonService {
catch (err) {
if (err.code !== 'EEXIST') {
if (err.code === 'ENOENT') {
throw new Error(`ENOENT: No such file or directory, mkdir '${directoryName}'. Ensure that channel backup path separator is '${sep}'`);
throw new Error(`ENOENT: No such file or directory, mkdir '${directoryName}'. Ensure that the path separator is '${sep}'`);
}
else {
throw err;

@ -1,28 +1,205 @@
import { join, dirname } from 'path';
import * as fs from 'fs';
import { join, dirname, sep } from 'path';
import { fileURLToPath } from 'url';
import sqlz from 'sequelize';
const { Sequelize } = sqlz;
import { Common } from '../utils/common.js';
import { Logger } from '../utils/logger.js';
import { Offer } from '../models/offers.model.js';
import { CollectionsEnum, validateOffer } from '../models/database.model.js';
export class DatabaseService {
constructor() {
this.common = Common;
this.logger = Logger;
this.rtlConfigPassword = '';
this.directoryName = dirname(fileURLToPath(import.meta.url));
this.rtlSequelize = null;
this.rtlDB = null;
this.rtlSequelize = new Sequelize({
database: 'RTLStore',
username: '',
password: '',
storage: join(this.directoryName, '..', '..', 'database', 'rtl-db.sqlite'),
dialect: 'sqlite',
logging: messageToLog => this.logger.log({ level: 'DEBUG', fileName: 'DBLog', msg: messageToLog })
this.dbDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'database');
this.nodeDatabase = {};
}
loadDatabase(selectedNode) {
try {
if (!this.nodeDatabase[selectedNode.index]) {
this.nodeDatabase[selectedNode.index] = { adapter: null, data: null };
}
this.nodeDatabase[selectedNode.index].adapter = new DatabaseAdapter(this.dbDirectory, 'rtldb', selectedNode);
this.nodeDatabase[selectedNode.index].data = this.nodeDatabase[selectedNode.index].adapter.fetchData();
}
catch (err) {
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: 'Database', msg: 'Database Load Error', error: err });
}
}
create(selectedNode, collectionName, newDocument) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, newDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
}
else {
this.nodeDatabase[selectedNode.index].data[collectionName].push(newDocument);
this.saveDatabase(+selectedNode.index);
resolve(newDocument);
}
}
catch (errRes) {
reject(errRes);
}
});
}
update(selectedNode, collectionName, updatedDocument, documentFieldName, documentFieldValue) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
let foundDocIdx = -1;
let foundDoc = null;
if (this.nodeDatabase[selectedNode.index].data[collectionName]) {
foundDocIdx = this.nodeDatabase[selectedNode.index].data[collectionName].findIndex((document) => document[documentFieldName] === documentFieldValue);
foundDoc = foundDocIdx > -1 ? JSON.parse(JSON.stringify(this.nodeDatabase[selectedNode.index].data[collectionName][foundDocIdx])) : null;
}
if (foundDocIdx > -1 && foundDoc) {
for (const docKey in updatedDocument) {
if (Object.prototype.hasOwnProperty.call(updatedDocument, docKey)) {
foundDoc[docKey] = updatedDocument[docKey];
}
}
updatedDocument = foundDoc;
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, updatedDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
}
else {
if (foundDocIdx > -1) {
this.nodeDatabase[selectedNode.index].data[collectionName].splice(foundDocIdx, 1, updatedDocument);
}
else {
if (!this.nodeDatabase[selectedNode.index].data[collectionName]) {
this.nodeDatabase[selectedNode.index].data[collectionName] = [];
}
this.nodeDatabase[selectedNode.index].data[collectionName].push(updatedDocument);
}
this.saveDatabase(+selectedNode.index);
resolve(updatedDocument);
}
}
catch (errRes) {
reject(errRes);
}
});
}
find(selectedNode, collectionName, documentFieldName, documentFieldValue) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
if (documentFieldName && documentFieldValue) {
resolve(this.nodeDatabase[selectedNode.index].data[collectionName].find((document) => document[documentFieldName] === documentFieldValue));
}
else {
resolve(this.nodeDatabase[selectedNode.index].data[collectionName]);
}
}
catch (errRes) {
reject(errRes);
}
});
}
destroy(selectedNode, collectionName, documentFieldName, documentFieldValue) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
const removeDocIdx = this.nodeDatabase[selectedNode.index].data[collectionName].findIndex((document) => document[documentFieldName] === documentFieldValue);
if (removeDocIdx > -1) {
this.nodeDatabase[selectedNode.index].data[collectionName].splice(removeDocIdx, 1);
}
else {
reject(new Error('Unable to delete, document not found.'));
}
this.saveDatabase(+selectedNode.index);
resolve(documentFieldValue);
}
catch (errRes) {
reject(errRes);
}
});
this.rtlDB = {
Sequelize: this.rtlSequelize,
offer: Offer(this.rtlSequelize, Sequelize)
};
}
validateDocument(collectionName, documentToValidate) {
switch (collectionName) {
case CollectionsEnum.OFFERS:
return validateOffer(documentToValidate);
default:
return ({ isValid: false, error: 'Collection does not exist' });
}
}
saveDatabase(nodeIndex) {
try {
if (!this.nodeDatabase[nodeIndex]) {
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'ERROR', fileName: 'Database', msg: 'Database Save Error: Selected Node Config Not Found.' });
}
this.nodeDatabase[nodeIndex].adapter.saveData(this.nodeDatabase[nodeIndex].data);
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'INFO', fileName: 'Database', msg: 'Database Saved' });
}
catch (err) {
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'ERROR', fileName: 'Database', msg: 'Database Save Error', error: err });
throw new Error(err);
}
}
unloadDatabase(nodeIndex) {
this.saveDatabase(nodeIndex);
this.nodeDatabase[nodeIndex] = null;
}
}
export class DatabaseAdapter {
constructor(dbDirectoryPath, fileName, selNode = null) {
this.dbDirectoryPath = dbDirectoryPath;
this.fileName = fileName;
this.selNode = selNode;
this.dbFile = '';
this.dbFile = dbDirectoryPath + sep + fileName + '-node-' + selNode.index + '.json';
}
fetchData() {
try {
if (!fs.existsSync(this.dbDirectoryPath)) {
fs.mkdirSync(this.dbDirectoryPath);
}
}
catch (err) {
return new Error('Unable to Create Directory Error ' + JSON.stringify(err));
}
try {
if (!fs.existsSync(this.dbFile)) {
fs.writeFileSync(this.dbFile, '{}');
}
}
catch (err) {
return new Error('Unable to Create Database File Error ' + JSON.stringify(err));
}
try {
const dataFromFile = fs.readFileSync(this.dbFile, 'utf-8');
return !dataFromFile ? null : JSON.parse(dataFromFile);
}
catch (err) {
return new Error('Database Read Error ' + JSON.stringify(err));
}
}
getSelNode() {
return this.selNode;
}
saveData(data) {
try {
if (data) {
const tempFile = this.dbFile + '.tmp';
fs.writeFileSync(tempFile, JSON.stringify(data, null, 2));
fs.renameSync(tempFile, this.dbFile);
}
return true;
}
catch (err) {
return new Error('Database Write Error ' + JSON.stringify(err));
}
}
}
export const Database = new DatabaseService();

@ -1,25 +1,13 @@
FROM node:14-alpine
RUN apk add --no-cache tini
WORKDIR /RTL
COPY package.json /RTL/package.json
COPY package-lock.json /RTL/package-lock.json
# Install dependencies
RUN apk add --no-cache g++ git make python3 sqlite sqlite-dev \
&& npm un sqlite3 -S \
&& npm i --production \
&& NODE_SQLITE_VERSION=4.2.0 \
&& wget https://github.com/mapbox/node-sqlite3/archive/refs/tags/v${NODE_SQLITE_VERSION}.zip -O /opt/sqlite3.zip \
&& mkdir -p /opt/sqlite3 \
&& unzip /opt/sqlite3.zip -d /opt/sqlite3 \
&& cd /opt/sqlite3/node-sqlite3-${NODE_SQLITE_VERSION} \
&& npm install \
&& ./node_modules/.bin/node-pre-gyp install --fallback-to-build --build-from-source --sqlite=/usr/bin --python=$(which python) \
&& mkdir -p /RTL/node_modules/sqlite3 \
&& mv /opt/sqlite3/node-sqlite3-${NODE_SQLITE_VERSION}/* /RTL/node_modules/sqlite3 \
&& apk del g++ git make python3 \
&& rm -Rf /opt/sqlite3 /opt/sqlite3.zip
RUN npm install --production
COPY angular/ /RTL/angular/
COPY backend/ /RTL/backend/
@ -27,4 +15,6 @@ COPY rtl.js /RTL/rtl.js
EXPOSE 3000
ENTRYPOINT ["/sbin/tini", "-g", "--"]
CMD ["node", "rtl"]

@ -1,4 +1,4 @@
FROM node:12-stretch-slim AS builder
FROM node:14-stretch-slim AS builder
ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-armel /tini
ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-armel.asc /tini.asc
@ -7,28 +7,15 @@ RUN chmod +x /tini
WORKDIR /RTL
COPY package.json /RTL/package.json
COPY package-lock.json /RTL/package-lock.json
# Install dependencies
RUN apt-get update && apt-get install -y g++ wget git unzip make python3 sqlite libsqlite3-dev \
&& npm un sqlite3 -S \
&& npm i --production \
&& NODE_SQLITE_VERSION=4.2.0 \
&& wget https://github.com/mapbox/node-sqlite3/archive/refs/tags/v${NODE_SQLITE_VERSION}.zip -O /opt/sqlite3.zip \
&& mkdir -p /opt/sqlite3 \
&& unzip /opt/sqlite3.zip -d /opt/sqlite3 \
&& cd /opt/sqlite3/node-sqlite3-${NODE_SQLITE_VERSION} \
&& npm install \
&& ./node_modules/.bin/node-pre-gyp install --fallback-to-build --build-from-source --sqlite=/usr/bin --python=$(which python) \
&& mkdir -p /RTL/node_modules/sqlite3 \
&& mv /opt/sqlite3/node-sqlite3-${NODE_SQLITE_VERSION}/* /RTL/node_modules/sqlite3 \
&& apt-get autoremove --yes \
&& rm -Rf /opt/sqlite3 /opt/sqlite3.zip
RUN npm install --production
COPY angular/ /RTL/angular/
COPY backend/ /RTL/backend/
COPY rtl.js /RTL/rtl.js
FROM arm32v7/node:12-stretch-slim
FROM arm32v7/node:14-stretch-slim
WORKDIR /RTL

@ -1,4 +1,4 @@
FROM node:14-stretch-slim AS builder
FROM node:14-stretch-slim as builder
ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-arm64 /tini
RUN chmod +x /tini
@ -6,22 +6,9 @@ RUN chmod +x /tini
WORKDIR /RTL
COPY package.json /RTL/package.json
COPY package-lock.json /RTL/package-lock.json
# Install dependencies
RUN apt-get update && apt-get install -y g++ wget git unzip make python3 sqlite libsqlite3-dev \
&& npm un sqlite3 -S \
&& npm i --production \
&& NODE_SQLITE_VERSION=4.2.0 \
&& wget https://github.com/mapbox/node-sqlite3/archive/refs/tags/v${NODE_SQLITE_VERSION}.zip -O /opt/sqlite3.zip \
&& mkdir -p /opt/sqlite3 \
&& unzip /opt/sqlite3.zip -d /opt/sqlite3 \
&& cd /opt/sqlite3/node-sqlite3-${NODE_SQLITE_VERSION} \
&& npm install \
&& ./node_modules/.bin/node-pre-gyp install --fallback-to-build --build-from-source --sqlite=/usr/bin --python=$(which python) \
&& mkdir -p /RTL/node_modules/sqlite3 \
&& mv /opt/sqlite3/node-sqlite3-${NODE_SQLITE_VERSION}/* /RTL/node_modules/sqlite3 \
&& apt-get autoremove --yes \
&& rm -Rf /opt/sqlite3 /opt/sqlite3.zip
RUN npm install --production
COPY angular/ /RTL/angular/
COPY backend/ /RTL/backend/

4739
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -27,20 +27,20 @@
"@angular/compiler": "~13.0.2",
"@angular/compiler-cli": "~13.0.2",
"@angular/core": "~13.0.2",
"@angular/flex-layout": "^12.0.0-beta.34",
"@angular/flex-layout": "^13.0.0-beta.36",
"@angular/forms": "~13.0.2",
"@angular/material": "^13.0.2",
"@angular/platform-browser": "~13.0.2",
"@angular/platform-browser-dynamic": "~13.0.2",
"@angular/router": "~13.0.2",
"@fortawesome/angular-fontawesome": "^0.8.1",
"@fortawesome/angular-fontawesome": "^0.10.1",
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@ngrx/effects": "^12.0.0",
"@ngrx/store": "^12.0.0",
"@swimlane/ngx-charts": "^16.0.0",
"angular-user-idle": "^2.2.5",
"@ngrx/effects": "^13.0.2",
"@ngrx/store": "^13.0.2",
"@swimlane/ngx-charts": "^19.2.0",
"angular-user-idle": "^2.2.7",
"atob": "^2.1.2",
"cookie-parser": "^1.4.5",
"csurf": "^1.11.0",
@ -58,28 +58,25 @@
"request-promise": "^4.2.6",
"roboto-fontface": "^0.10.0",
"rxjs": "^7.1.0",
"sequelize": "^6.7.0",
"sha256": "^0.2.0",
"sqlite3": "^4.2.0",
"tslib": "^2.0.0",
"tslib": "^2.3.1",
"typescript": "~4.4.4",
"uuidv4": "^6.2.12",
"ws": "^7.4.6",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^13.0.3",
"@angular-eslint/builder": "12.0.0",
"@angular-eslint/eslint-plugin": "12.0.0",
"@angular-eslint/eslint-plugin-template": "12.0.0",
"@angular-eslint/schematics": "12.0.0",
"@angular-eslint/template-parser": "12.0.0",
"@angular-eslint/builder": "13.0.1",
"@angular-eslint/eslint-plugin": "13.0.1",
"@angular-eslint/eslint-plugin-template": "13.0.1",
"@angular-eslint/schematics": "13.0.1",
"@angular-eslint/template-parser": "13.0.1",
"@angular/cli": "^13.0.3",
"@ngrx/store-devtools": "^12.0.0",
"@ngrx/store-devtools": "^13.0.2",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.19.9",
"@typescript-eslint/eslint-plugin": "4.23.0",
"@typescript-eslint/parser": "4.23.0",
"@types/node": "^16.11.12",
"@typescript-eslint/eslint-plugin": "~4.23.0",
"@typescript-eslint/parser": "~4.23.0",
"crypto-browserify": "^3.12.0",
"dotenv": "^8.2.0",
"eslint": "^7.32.0",
@ -91,7 +88,7 @@
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"nodemon": "^2.0.6",
"nodemon": "~2.0.6",
"protractor": "~7.0.0",
"stream-browserify": "^3.0.0",
"ts-node": "~8.3.0"

@ -1,4 +1,5 @@
import request from 'request-promise';
import { Database, DatabaseService } from '../../utils/database.js';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
import { CLWSClient, CLWebSocketClient } from './webSocketClient.js';
@ -7,6 +8,7 @@ let options = null;
const logger: LoggerService = Logger;
const common: CommonService = Common;
const clWsClient: CLWebSocketClient = CLWSClient;
const databaseService: DatabaseService = Database;
export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting CLightning Node Information..' });
@ -59,6 +61,7 @@ export const getInfo = (req, res, next) => {
req.session.selectedNode.api_version = body.api_version || '';
req.session.selectedNode.ln_version = body.version || '';
clWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'CLightning Node Information Received' });
res.status(200).json(body);
}

@ -2,15 +2,16 @@ import request from 'request-promise';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
import { Database, DatabaseService } from '../../utils/database.js';
import { CollectionFieldsEnum, CollectionsEnum } from '../../models/database.model.js';
let options = null;
const logger: LoggerService = Logger;
const common: CommonService = Common;
const DB: DatabaseService = Database;
const databaseService: DatabaseService = Database;
export const listOfferBookmarks = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offer Bookmarks..' });
DB.rtlDB.offer.findAll().then((offers) => {
databaseService.find(req.session.selectedNode, CollectionsEnum.OFFERS).then((offers) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Bookmarks Received', data: offers });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmarks Received' });
res.status(200).json(offers);
@ -22,10 +23,10 @@ export const listOfferBookmarks = (req, res, next) => {
export const deleteOfferBookmark = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Deleting Offer Bookmark..' });
DB.rtlDB.offer.destroy({ where: { id: req.params.offerUUID } }).then((deleteRes) => {
databaseService.destroy(req.session.selectedNode, CollectionsEnum.OFFERS, CollectionFieldsEnum.BOLT12, req.params.offerStr).then((deleteRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Bookmark Deleted', data: deleteRes });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmark Deleted' });
res.status(204).json({ id: req.params.offerUUID });
res.status(204).json(req.params.offerStr);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Offers', 'Offer Bookmark Delete Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });

@ -1,14 +1,13 @@
import request from 'request-promise';
import * as uuidModule from 'uuid';
const { v4 } = uuidModule;
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
import { Database, DatabaseService } from '../../utils/database.js';
import { CollectionFieldsEnum, CollectionsEnum, Offer } from '../../models/database.model.js';
let options = null;
const logger: LoggerService = Logger;
const common: CommonService = Common;
const DB: DatabaseService = Database;
const databaseService: DatabaseService = Database;
function paymentReducer(accumulator, currentPayment) {
const currPayHash = currentPayment.payment_hash;
@ -108,29 +107,20 @@ export const postPayment = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent' });
if (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB) {
let offer = {};
if (req.body.offerUUID) {
let updatedOffer = { title: req.body.title, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount) };
return DB.rtlDB.offer.update(updatedOffer, { where: { id: req.body.offerUUID } }).then((updatedOffer) => {
offer = { id: req.body.offerUUID, title: req.body.title, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), updatedAt: new Date(Date.now()) };
if (req.body.bolt12) {
const offerToUpdate: Offer = { bolt12: req.body.bolt12, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
if (req.body.vendor) { offerToUpdate['vendor'] = req.body.vendor; }
if (req.body.description) { offerToUpdate['description'] = req.body.description; }
return databaseService.update(req.session.selectedNode, CollectionsEnum.OFFERS, offerToUpdate, CollectionFieldsEnum.BOLT12, req.body.bolt12).then((updatedOffer) => {
logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Updated', data: updatedOffer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: offer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: updatedOffer });
}).catch((errDB) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB update error', error: errDB });
return res.status(201).json({ paymentResponse: body, saveToDBError: errDB });
});
} else {
offer = { id: v4(), offerBolt12: req.body.offerBolt12, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, vendor: req.body.vendor, description: req.body.description };
return DB.rtlDB.offer.create(offer).then((savedOffer) => {
logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Saved', data: savedOffer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: savedOffer.dataValues });
}).catch((errDB) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB save error', error: errDB });
return res.status(201).json({ paymentResponse: body, saveToDBError: errDB });
});
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
}
} else {
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
}
}
if (req.body.paymentType === 'INVOICE') {

@ -1,4 +1,5 @@
import request from 'request-promise';
import { Database, DatabaseService } from '../../utils/database.js';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
import { ECLWSClient, ECLWebSocketClient } from './webSocketClient.js';
@ -7,6 +8,7 @@ let options = null;
const logger: LoggerService = Logger;
const common: CommonService = Common;
const eclWsClient: ECLWebSocketClient = ECLWSClient;
const databaseService: DatabaseService = Database;
export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting Eclair Node Information..' });
@ -36,12 +38,12 @@ export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Eclair Node Information Received' });
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
eclWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
res.status(200).json(body);
}).
catch((errRes) => {
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
}).catch((errRes) => {
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
}
}
};

@ -1,4 +1,5 @@
import request from 'request-promise';
import { Database, DatabaseService } from '../../utils/database.js';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
import { LNDWSClient, LNDWebSocketClient } from './webSocketClient.js';
@ -7,6 +8,7 @@ let options = null;
const logger: LoggerService = Logger;
const common: CommonService = Common;
const lndWsClient: LNDWebSocketClient = LNDWSClient;
const databaseService: DatabaseService = Database;
export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting LND Node Information..' });
@ -40,6 +42,7 @@ export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'LND Node Information Received' });
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
lndWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
res.status(200).json(body);
}
}).catch((errRes) => {

@ -3,6 +3,7 @@ import { sep } from 'path';
import ini from 'ini';
import parseHocon from 'hocon-parser';
import request from 'request-promise';
import { Database, DatabaseService } from '../../utils/database.js';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js';
@ -12,6 +13,7 @@ const options = { url: '' };
const logger: LoggerService = Logger;
const common: CommonService = Common;
const wsServer = WSServer;
const databaseService: DatabaseService = Database;
export const updateSelectedNode = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating Selected Node..' });
@ -19,6 +21,9 @@ export const updateSelectedNode = (req, res, next) => {
req.session.selectedNode = common.findNode(selNodeIndex);
if (req.headers && req.headers.authorization && req.headers.authorization !== '') {
wsServer.updateLNWSClientDetails(req.session.id, +req.session.selectedNode.index, +req.body.prevNodeIndex);
if (req.body.prevNodeIndex !== -1) {
databaseService.unloadDatabase(req.body.prevNodeIndex);
}
}
const responseVal = !req.session.selectedNode.ln_node ? '' : req.session.selectedNode.ln_node;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Selected Node Updated To', data: responseVal });
@ -335,6 +340,7 @@ export const updateServiceSettings = (req, res, next) => {
}
common.replaceNode(req, selectedNode);
}
return node;
});
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');

@ -1,14 +1,17 @@
import jwt from 'jsonwebtoken';
import * as otplib from 'otplib';
import * as crypto from 'crypto';
import { Database, DatabaseService } from '../../utils/database.js';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
const logger: LoggerService = Logger;
const common: CommonService = Common;
const ONE_MINUTE = 60000;
const LOCKING_PERIOD = 30 * ONE_MINUTE; // HALF AN HOUR
const ALLOWED_LOGIN_ATTEMPTS = 5;
const failedLoginAttempts = {};
const databaseService: DatabaseService = Database;
const loginInterval = setInterval(() => {
for (const ip in failedLoginAttempts) {
@ -117,6 +120,9 @@ export const resetPassword = (req, res, next) => {
export const logoutUser = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'Logged out' });
if (req.session.selectedNode && req.session.selectedNode.index) {
databaseService.unloadDatabase(+req.session.selectedNode.index);
}
req.session.destroy();
res.status(200).json({ loggedout: true });
};

@ -0,0 +1,46 @@
export enum CollectionsEnum {
OFFERS = 'Offers'
}
export type Collections = {
Offers: Offer[];
}
export enum OfferFieldsEnum {
BOLT12 = 'bolt12',
AMOUNTMSAT = 'amountmSat',
TITLE = 'title',
VENDOR = 'vendor',
DESCRIPTION = 'description'
}
export const CollectionFieldsEnum = { ...OfferFieldsEnum };
export class Offer {
constructor(
public bolt12: string,
public amountmSat: number,
public title: string,
public vendor?: string,
public description?: string,
public lastUpdatedAt?: number
) { }
}
export const validateOffer = (documentToValidate): any => {
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.BOLT12)) {
return ({ isValid: false, error: CollectionFieldsEnum.BOLT12 + 'is mandatory.' });
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.AMOUNTMSAT)) {
return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'is mandatory.' });
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TITLE)) {
return ({ isValid: false, error: CollectionFieldsEnum.TITLE + 'is mandatory.' });
}
if ((typeof documentToValidate[CollectionFieldsEnum.AMOUNTMSAT] !== 'number')) {
return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'should be a number.' });
}
return ({ isValid: true });
};

@ -1,30 +0,0 @@
export const Offer = (sequelize, Sequelize) => {
const offerInstance = sequelize.define('Offers', {
id: {
type: Sequelize.UUIDV4,
primaryKey: true,
allowNull: false
},
offerBolt12: {
type: Sequelize.STRING,
allowNull: false
},
amountmSat: {
type: Sequelize.NUMBER,
allowNull: false
},
title: {
type: Sequelize.STRING,
allowNull: false
},
vendor: {
type: Sequelize.STRING,
allowNull: true
},
description: {
type: Sequelize.STRING,
allowNull: true
}
});
return offerInstance;
};

@ -6,7 +6,7 @@ import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, crea
const router = Router();
router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks);
router.delete('/offerbookmark/:offerUUID', isAuthenticated, deleteOfferBookmark);
router.delete('/offerbookmark/:offerStr', isAuthenticated, deleteOfferBookmark);
router.get('/', isAuthenticated, listOffers);
router.post('/', isAuthenticated, createOffer);

@ -14,7 +14,6 @@ import eclRoutes from '../routes/eclair/index.js';
import { Common, CommonService } from './common.js';
import { Logger, LoggerService } from './logger.js';
import { Config, ConfigService } from './config.js';
import { Database, DatabaseService } from './database.js';
import { CLWSClient, CLWebSocketClient } from '../controllers/c-lightning/webSocketClient.js';
import { ECLWSClient, ECLWebSocketClient } from '../controllers/eclair/webSocketClient.js';
import { LNDWSClient, LNDWebSocketClient } from '../controllers/lnd/webSocketClient.js';
@ -27,7 +26,6 @@ export class ExpressApplication {
public logger: LoggerService = Logger;
public common: CommonService = Common;
public config: ConfigService = Config;
public DB: DatabaseService = Database;
public eclWsClient: ECLWebSocketClient = ECLWSClient;
public clWsClient: CLWebSocketClient = CLWSClient;
public lndWsClient: LNDWebSocketClient = LNDWSClient;
@ -45,7 +43,6 @@ export class ExpressApplication {
this.setCORS();
this.setCSRF();
this.setApplicationRoutes();
this.loadDb();
}
public getApp = () => this.app;
@ -54,14 +51,6 @@ export class ExpressApplication {
this.config.setServerConfiguration();
}
private loadDb = () => {
if (this.DB.rtlSequelize) {
this.DB.rtlSequelize.sync().then(() => {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'DEBUG', fileName: 'App', msg: 'Database Connected' });
});
}
}
public setCORS = () => { CORS.mount(this.app); }
public setCSRF = () => { CSRF.mount(this.app); }

@ -341,7 +341,7 @@ export class CommonService {
} catch (err) {
if (err.code !== 'EEXIST') {
if (err.code === 'ENOENT') {
throw new Error(`ENOENT: No such file or directory, mkdir '${directoryName}'. Ensure that channel backup path separator is '${sep}'`);
throw new Error(`ENOENT: No such file or directory, mkdir '${directoryName}'. Ensure that the path separator is '${sep}'`);
} else {
throw err;
}

@ -1,32 +1,208 @@
import { join, dirname } from 'path';
import * as fs from 'fs';
import { join, dirname, sep } from 'path';
import { fileURLToPath } from 'url';
import sqlz from 'sequelize';
const { Sequelize } = sqlz;
import { Common, CommonService } from '../utils/common.js';
import { Logger, LoggerService } from '../utils/logger.js';
import { Offer } from '../models/offers.model.js';
import { Collections, CollectionsEnum, validateOffer } from '../models/database.model.js';
import { CommonSelectedNode } from '../models/config.model.js';
export class DatabaseService {
public common: CommonService = Common;
public logger: LoggerService = Logger;
public rtlConfigPassword = '';
public directoryName = dirname(fileURLToPath(import.meta.url));
public rtlSequelize = null;
public rtlDB = null;
constructor() {
this.rtlSequelize = new Sequelize({
database: 'RTLStore',
username: '',
password: '',
storage: join(this.directoryName, '..', '..', 'database', 'rtl-db.sqlite'),
dialect: 'sqlite',
logging: messageToLog => this.logger.log({ level: 'DEBUG', fileName: 'DBLog', msg: messageToLog })
public dbDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'database');
public nodeDatabase: { id?: { adapter: DatabaseAdapter, data: Collections } } = {};
constructor() { }
loadDatabase(selectedNode: CommonSelectedNode) {
try {
if (!this.nodeDatabase[selectedNode.index]) {
this.nodeDatabase[selectedNode.index] = { adapter: null, data: null };
}
this.nodeDatabase[selectedNode.index].adapter = new DatabaseAdapter(this.dbDirectory, 'rtldb', selectedNode);
this.nodeDatabase[selectedNode.index].data = this.nodeDatabase[selectedNode.index].adapter.fetchData();
} catch (err) {
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: 'Database', msg: 'Database Load Error', error: err });
}
}
create(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum, newDocument: any) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, newDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
} else {
this.nodeDatabase[selectedNode.index].data[collectionName].push(newDocument);
this.saveDatabase(+selectedNode.index);
resolve(newDocument);
}
} catch (errRes) {
reject(errRes);
}
});
this.rtlDB = {
Sequelize: this.rtlSequelize,
offer: Offer(this.rtlSequelize, Sequelize)
};
}
update(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum, updatedDocument: any, documentFieldName: string, documentFieldValue: string) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
let foundDocIdx = -1;
let foundDoc = null;
if (this.nodeDatabase[selectedNode.index].data[collectionName]) {
foundDocIdx = this.nodeDatabase[selectedNode.index].data[collectionName].findIndex((document: any) => document[documentFieldName] === documentFieldValue);
foundDoc = foundDocIdx > -1 ? JSON.parse(JSON.stringify(this.nodeDatabase[selectedNode.index].data[collectionName][foundDocIdx])) : null;
}
if (foundDocIdx > -1 && foundDoc) {
for (const docKey in updatedDocument) {
if (Object.prototype.hasOwnProperty.call(updatedDocument, docKey)) {
foundDoc[docKey] = updatedDocument[docKey];
}
}
updatedDocument = foundDoc;
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, updatedDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
} else {
if (foundDocIdx > -1) {
this.nodeDatabase[selectedNode.index].data[collectionName].splice(foundDocIdx, 1, updatedDocument);
} else {
if (!this.nodeDatabase[selectedNode.index].data[collectionName]) {
this.nodeDatabase[selectedNode.index].data[collectionName] = [];
}
this.nodeDatabase[selectedNode.index].data[collectionName].push(updatedDocument);
}
this.saveDatabase(+selectedNode.index);
resolve(updatedDocument);
}
} catch (errRes) {
reject(errRes);
}
});
}
find(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum, documentFieldName?: string, documentFieldValue?: string) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
if (documentFieldName && documentFieldValue) {
resolve(this.nodeDatabase[selectedNode.index].data[collectionName].find((document: any) => document[documentFieldName] === documentFieldValue));
} else {
resolve(this.nodeDatabase[selectedNode.index].data[collectionName]);
}
} catch (errRes) {
reject(errRes);
}
});
}
destroy(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum, documentFieldName: string, documentFieldValue: string) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
const removeDocIdx = this.nodeDatabase[selectedNode.index].data[collectionName].findIndex((document) => document[documentFieldName] === documentFieldValue);
if (removeDocIdx > -1) {
this.nodeDatabase[selectedNode.index].data[collectionName].splice(removeDocIdx, 1);
} else {
reject(new Error('Unable to delete, document not found.'));
}
this.saveDatabase(+selectedNode.index);
resolve(documentFieldValue);
} catch (errRes) {
reject(errRes);
}
});
}
validateDocument(collectionName: CollectionsEnum, documentToValidate: any) {
switch (collectionName) {
case CollectionsEnum.OFFERS:
return validateOffer(documentToValidate);
default:
return ({ isValid: false, error: 'Collection does not exist' });
}
}
saveDatabase(nodeIndex: number) {
try {
if (!this.nodeDatabase[nodeIndex]) {
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'ERROR', fileName: 'Database', msg: 'Database Save Error: Selected Node Config Not Found.' });
}
this.nodeDatabase[nodeIndex].adapter.saveData(this.nodeDatabase[nodeIndex].data);
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'INFO', fileName: 'Database', msg: 'Database Saved' });
} catch (err) {
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'ERROR', fileName: 'Database', msg: 'Database Save Error', error: err });
throw new Error(err);
}
}
unloadDatabase(nodeIndex: number) {
this.saveDatabase(nodeIndex);
this.nodeDatabase[nodeIndex] = null;
}
}
export class DatabaseAdapter {
private dbFile = '';
constructor(public dbDirectoryPath: string, public fileName: string, private selNode: CommonSelectedNode = null) {
this.dbFile = dbDirectoryPath + sep + fileName + '-node-' + selNode.index + '.json';
}
fetchData() {
try {
if (!fs.existsSync(this.dbDirectoryPath)) {
fs.mkdirSync(this.dbDirectoryPath);
}
} catch (err) {
return new Error('Unable to Create Directory Error ' + JSON.stringify(err));
}
try {
if (!fs.existsSync(this.dbFile)) {
fs.writeFileSync(this.dbFile, '{}');
}
} catch (err) {
return new Error('Unable to Create Database File Error ' + JSON.stringify(err));
}
try {
const dataFromFile = fs.readFileSync(this.dbFile, 'utf-8');
return !dataFromFile ? null : (<Collections>JSON.parse(dataFromFile));
} catch (err) {
return new Error('Database Read Error ' + JSON.stringify(err));
}
}
getSelNode() {
return this.selNode;
}
saveData(data: any) {
try {
if (data) {
const tempFile = this.dbFile + '.tmp';
fs.writeFileSync(tempFile, JSON.stringify(data, null, 2));
fs.renameSync(tempFile, this.dbFile);
}
return true;
} catch (err) {
return new Error('Database Write Error ' + JSON.stringify(err));
}
}
}
export const Database = new DatabaseService();

@ -131,6 +131,6 @@ export const setOfferBookmarks = createAction(CLActions.SET_OFFER_BOOKMARKS_CL,
export const addUpdateOfferBookmark = createAction(CLActions.ADD_UPDATE_OFFER_BOOKMARK_CL, props<{ payload: OfferBookmark }>());
export const deleteOfferBookmark = createAction(CLActions.DELETE_OFFER_BOOKMARK_CL, props<{ payload: { offer_uuid: string } }>());
export const deleteOfferBookmark = createAction(CLActions.DELETE_OFFER_BOOKMARK_CL, props<{ payload: { bolt12: string } }>());
export const removeOfferBookmark = createAction(CLActions.REMOVE_OFFER_BOOKMARK_CL, props<{ payload: { offer_uuid: string } }>());
export const removeOfferBookmark = createAction(CLActions.REMOVE_OFFER_BOOKMARK_CL, props<{ payload: { bolt12: string } }>());

@ -398,11 +398,10 @@ export class CLEffects implements OnDestroy {
return {
type: CLActions.FETCH_CHANNELS_CL
};
}),
catchError((err: any) => {
this.handleErrorWithAlert('UpdateChannel', UI_MESSAGES.UPDATE_CHAN_POLICY, 'Update Channel Failed', this.CHILD_API_URL + environment.CHANNELS_API, err);
return of({ type: RTLActions.VOID });
})
}), catchError((err: any) => {
this.handleErrorWithAlert('UpdateChannel', UI_MESSAGES.UPDATE_CHAN_POLICY, 'Update Channel Failed', this.CHILD_API_URL + environment.CHANNELS_API, err);
return of({ type: RTLActions.VOID });
})
);
})
));
@ -811,11 +810,10 @@ export class CLEffects implements OnDestroy {
type: CLActions.ADD_OFFER_CL,
payload: postRes
};
}),
catchError((err: any) => {
this.handleErrorWithoutAlert('SaveNewOffer', UI_MESSAGES.CREATE_OFFER, 'Create Offer Failed.', err);
return of({ type: RTLActions.VOID });
})
}), catchError((err: any) => {
this.handleErrorWithoutAlert('SaveNewOffer', UI_MESSAGES.CREATE_OFFER, 'Create Offer Failed.', err);
return of({ type: RTLActions.VOID });
})
);
})
));
@ -857,12 +855,10 @@ export class CLEffects implements OnDestroy {
type: CLActions.SET_OFFERS_CL,
payload: res.offers ? res.offers : []
};
}),
catchError((err: any) => {
this.handleErrorWithoutAlert('FetchOffers', UI_MESSAGES.NO_SPINNER, 'Fetching Offers Failed.', err);
return of({ type: RTLActions.VOID });
})
);
}), catchError((err: any) => {
this.handleErrorWithoutAlert('FetchOffers', UI_MESSAGES.NO_SPINNER, 'Fetching Offers Failed.', err);
return of({ type: RTLActions.VOID });
}));
})
));
@ -881,12 +877,10 @@ export class CLEffects implements OnDestroy {
type: CLActions.UPDATE_OFFER_CL,
payload: { offer: postRes }
};
}),
catchError((err: any) => {
this.handleErrorWithoutAlert('DisableOffer', UI_MESSAGES.DISABLE_OFFER, 'Disabling Offer Failed.', err);
return of({ type: RTLActions.VOID });
})
);
}), catchError((err: any) => {
this.handleErrorWithoutAlert('DisableOffer', UI_MESSAGES.DISABLE_OFFER, 'Disabling Offer Failed.', err);
return of({ type: RTLActions.VOID });
}));
})
));
@ -902,21 +896,19 @@ export class CLEffects implements OnDestroy {
type: CLActions.SET_OFFER_BOOKMARKS_CL,
payload: res || []
};
}),
catchError((err: any) => {
this.handleErrorWithoutAlert('FetchOfferBookmarks', UI_MESSAGES.NO_SPINNER, 'Fetching Offer Bookmarks Failed.', err);
return of({ type: RTLActions.VOID });
})
);
}), catchError((err: any) => {
this.handleErrorWithoutAlert('FetchOfferBookmarks', UI_MESSAGES.NO_SPINNER, 'Fetching Offer Bookmarks Failed.', err);
return of({ type: RTLActions.VOID });
}));
})
));
peidOffersDeleteCL = createEffect(() => this.actions.pipe(
ofType(CLActions.DELETE_OFFER_BOOKMARK_CL),
mergeMap((action: { type: string, payload: { offer_uuid: string } }) => {
mergeMap((action: { type: string, payload: { bolt12: string } }) => {
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.DELETE_OFFER_BOOKMARK }));
this.store.dispatch(updateCLAPICallStatus({ payload: { action: 'DeleteOfferBookmark', status: APICallStatusEnum.INITIATED } }));
return this.httpClient.delete(this.CHILD_API_URL + environment.OFFERS_API + '/offerbookmark/' + action.payload.offer_uuid).
return this.httpClient.delete(this.CHILD_API_URL + environment.OFFERS_API + '/offerbookmark/' + action.payload.bolt12).
pipe(map((postRes: any) => {
this.logger.info(postRes);
this.store.dispatch(updateCLAPICallStatus({ payload: { action: 'DeleteOfferBookmark', status: APICallStatusEnum.COMPLETED } }));
@ -924,14 +916,12 @@ export class CLEffects implements OnDestroy {
this.store.dispatch(openSnackBar({ payload: 'Offer Bookmark Deleted Successfully!' }));
return {
type: CLActions.REMOVE_OFFER_BOOKMARK_CL,
payload: { offer_uuid: action.payload.offer_uuid }
payload: { bolt12: action.payload.bolt12 }
};
}),
catchError((err: any) => {
this.handleErrorWithoutAlert('DeleteOfferBookmark', UI_MESSAGES.DELETE_OFFER_BOOKMARK, 'Deleting Offer Bookmark Failed.', err);
return of({ type: RTLActions.VOID });
})
);
}), catchError((err: any) => {
this.handleErrorWithAlert('DeleteOfferBookmark', UI_MESSAGES.DELETE_OFFER_BOOKMARK, 'Deleting Offer Bookmark Failed.', this.CHILD_API_URL + environment.OFFERS_API + '/offerbookmark/' + action.payload.bolt12, err);
return of({ type: RTLActions.VOID });
}));
})
));

@ -6,7 +6,7 @@ import {
setInfo, setInvoices, setLocalRemoteBalance, setOffers, addOffer, setPayments, setPeers, setUTXOs,
updateCLAPICallStatus, updateInvoice, updateOffer, setOfferBookmarks, addUpdateOfferBookmark, removeOfferBookmark
} from './cl.actions';
import { Channel } from '../../shared/models/clModels';
import { Channel, OfferBookmark } from '../../shared/models/clModels';
export const CLReducer = createReducer(initCLState,
on(updateCLAPICallStatus, (state, { payload }) => {
@ -177,15 +177,15 @@ export const CLReducer = createReducer(initCLState,
offersBookmarks: payload
})),
on(addUpdateOfferBookmark, (state, { payload }) => {
const newOfferBMs = [...state.offersBookmarks];
const offerBMExistsIdx = newOfferBMs.findIndex((offer) => offer.id === payload.id);
const newOfferBMs: OfferBookmark[] = [...state.offersBookmarks];
const offerBMExistsIdx = newOfferBMs.findIndex((offer: OfferBookmark) => offer.bolt12 === payload.bolt12);
if (offerBMExistsIdx < 0) {
newOfferBMs.unshift(payload);
} else {
let updatedOffer = { ...newOfferBMs[offerBMExistsIdx] };
const updatedOffer = { ...newOfferBMs[offerBMExistsIdx] };
updatedOffer.title = payload.title;
updatedOffer.amountmSat = payload.amountmSat;
updatedOffer.updatedAt = payload.updatedAt;
updatedOffer.lastUpdatedAt = payload.lastUpdatedAt;
newOfferBMs.splice(offerBMExistsIdx, 1, updatedOffer);
}
return {
@ -195,7 +195,7 @@ export const CLReducer = createReducer(initCLState,
}),
on(removeOfferBookmark, (state, { payload }) => {
const modifiedOfferBookmarks = [...state.offersBookmarks];
const removeOfferBookmarkIdx = state.offersBookmarks.findIndex((ob) => ob.id === payload.offer_uuid);
const removeOfferBookmarkIdx = state.offersBookmarks.findIndex((ob) => ob.bolt12 === payload.bolt12);
if (removeOfferBookmarkIdx > -1) {
modifiedOfferBookmarks.splice(removeOfferBookmarkIdx, 1);
}

@ -27,4 +27,4 @@ export const nodeInfoAndBalance = createSelector(clState, (state: CLState) => ({
export const nodeInfoAndNodeSettingsAndAPIsStatus = createSelector(clState, (state: CLState) => ({ information: state.information, nodeSettings: state.nodeSettings, apisCallStatus: [state.apisCallStatus.FetchInfo, state.apisCallStatus.FetchForwardingHistory] }));
export const offers = createSelector(clState, (state: CLState) => ({ offers: state.offers, apiCallStatus: state.apisCallStatus.FetchOffers }));
export const offerBookmarks = createSelector(clState, (state: CLState) => ({ offersBookmarks: state.offersBookmarks, apiCallStatus: state.apisCallStatus.FetchOfferBookmarks }));
export const getoffer = (offerBolt12Str) => createSelector(clState, (state: CLState) => (state.offers.find((offer: Offer) => offer.bolt12 === offerBolt12Str)));
export const getoffer = (bolt12Str) => createSelector(clState, (state: CLState) => (state.offers.find((offer: Offer) => offer.bolt12 === bolt12Str)));

@ -1,6 +1,6 @@
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" class="padding-gap">
<div fxLayout="column" fxLayoutAlign="start stretch">
<div fxLayout="column" fxLayoutAlign="start stretch" fxLayout.gt-sm="row wrap" class="page-sub-title-container mt-1">
<div fxLayout="column" fxLayoutAlign="start stretch" fxLayout.gt-sm="row wrap" class="page-sub-title-container">
<div fxFlex="70" fxLayoutAlign="start start" fxLayoutAlign.gt-sm="start center">
<fa-icon [icon]="faHistory" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Offer Bookmarks</span>
@ -12,9 +12,9 @@
<div [perfectScrollbar] fxLayout="column" fxLayoutAlign="start center" fxFlex="100" class="table-container">
<mat-progress-bar *ngIf="apiCallStatus?.status === apiCallStatusEnum.INITIATED" mode="indeterminate"></mat-progress-bar>
<table mat-table #table fxFlex="100" [dataSource]="offersBookmarks" matSort [ngClass]="{'overflow-auto error-border': errorMessage !== '','overflow-auto': true}">
<ng-container matColumnDef="updatedAt">
<ng-container matColumnDef="lastUpdatedAt">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Updated At </th>
<td mat-cell *matCellDef="let offersbookmark">{{offersbookmark.updatedAt | date:'dd/MMM/YYYY HH:mm'}}</td>
<td mat-cell *matCellDef="let offersbookmark">{{offersbookmark.lastUpdatedAt | date:'dd/MMM/YYYY HH:mm'}}</td>
</ng-container>
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Title </th>

@ -52,16 +52,16 @@ export class CLOfferBookmarksTableComponent implements OnInit, AfterViewInit, On
this.screenSize = this.commonService.getScreenSize();
if (this.screenSize === ScreenSizeEnum.XS) {
this.flgSticky = false;
this.displayedColumns = ['updatedAt', 'title', 'amountmSat', 'actions'];
this.displayedColumns = ['lastUpdatedAt', 'title', 'amountmSat', 'actions'];
} else if (this.screenSize === ScreenSizeEnum.SM) {
this.flgSticky = false;
this.displayedColumns = ['updatedAt', 'title', 'amountmSat', 'actions'];
this.displayedColumns = ['lastUpdatedAt', 'title', 'amountmSat', 'actions'];
} else if (this.screenSize === ScreenSizeEnum.MD) {
this.flgSticky = false;
this.displayedColumns = ['updatedAt', 'title', 'amountmSat', 'description', 'actions'];
this.displayedColumns = ['lastUpdatedAt', 'title', 'amountmSat', 'description', 'actions'];
} else {
this.flgSticky = true;
this.displayedColumns = ['updatedAt', 'title', 'amountmSat', 'description', 'actions'];
this.displayedColumns = ['lastUpdatedAt', 'title', 'amountmSat', 'description', 'actions'];
}
}
@ -91,7 +91,7 @@ export class CLOfferBookmarksTableComponent implements OnInit, AfterViewInit, On
this.store.dispatch(openAlert({
payload: {
data: {
offer: { bolt12: selOffer.offerBolt12 },
offer: { bolt12: selOffer.bolt12 },
newlyAdded: false,
component: CLOfferInformationComponent
}
@ -113,7 +113,7 @@ export class CLOfferBookmarksTableComponent implements OnInit, AfterViewInit, On
}));
this.rtlEffects.closeConfirm.pipe(takeUntil(this.unSubs[1])).subscribe((confirmRes) => {
if (confirmRes) {
this.store.dispatch(deleteOfferBookmark({ payload: { offer_uuid: selOffer.id } }));
this.store.dispatch(deleteOfferBookmark({ payload: { bolt12: selOffer.bolt12 } }));
}
});
}
@ -123,9 +123,8 @@ export class CLOfferBookmarksTableComponent implements OnInit, AfterViewInit, On
payload: {
data: {
paymentType: PaymentTypes.OFFER,
offerBolt12: selOffer.offerBolt12,
bolt12: selOffer.bolt12,
offerTitle: selOffer.title,
offerUUId: selOffer.id,
component: CLLightningSendPaymentsComponent
}
}

@ -49,12 +49,12 @@
</div>
</div>
<div *ngIf="showAdvanced">
<mat-divider class="w-100 my-1"></mat-divider>
<div fxLayout="row">
<mat-divider *ngIf="offer?.used || offer?.single_use" class="w-100 my-1"></mat-divider>
<div fxLayout="row" *ngIf="offer?.used || offer?.single_use">
<div fxFlex="50">
<h4 fxLayoutAlign="start" class="font-bold-500">Used</h4>
<span class="foreground-secondary-text">
{{!offer?.used ? 'N/K' : offer?.used ? 'Yes' : 'No' }}
{{ !offer?.used ? 'N/K' : offer?.used ? 'Yes' : 'No' }}
</span>
</div>
<div fxFlex="50">

@ -33,7 +33,7 @@ export class CLOfferInformationComponent implements OnInit, OnDestroy {
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
public flgOfferPaid = false;
public flgVersionCompatible: boolean = true;
public flgVersionCompatible = true;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<CLOfferInformationComponent>, @Inject(MAT_DIALOG_DATA) public data: CLOfferInformation, private logger: LoggerService, private commonService: CommonService, private snackBar: MatSnackBar, private store: Store<RTLState>, private dataService: DataService) { }

@ -58,7 +58,6 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
public zeroAmtOffer = false;
public offerInvoice: OfferInvoice = null;
public offerAmount = null;
public offerUUID = null;
public flgSaveToDB = false;
public paymentDecoded: PayRequest = {};
@ -91,9 +90,8 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
this.pubkey = this.data.pubkeyKeysend;
break;
case PaymentTypes.OFFER:
this.onPaymentRequestEntry(this.data.offerBolt12);
this.onPaymentRequestEntry(this.data.bolt12);
this.offerTitle = this.data.offerTitle;
this.offerUUID = this.data.offerUUId;
this.flgSaveToDB = false;
break;
default:
@ -209,6 +207,7 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
}
sendPayment() {
this.paymentError = '';
if (this.paymentType === PaymentTypes.INVOICE) {
if (this.zeroAmtInvoice) {
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, paymentType: PaymentTypes.INVOICE, invoice: this.paymentRequest, amount: this.paymentAmount * 1000, fromDialog: true } }));
@ -223,7 +222,7 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
this.store.dispatch(fetchOfferInvoice({ payload: { offer: this.offerRequest } }));
}
} else {
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, paymentType: PaymentTypes.OFFER, invoice: this.offerInvoice.invoice, saveToDB: this.flgSaveToDB, offerUUID: this.offerUUID, offerBolt12: this.offerRequest, amount: this.offerAmount * 1000, zeroAmtOffer: this.zeroAmtOffer, title: this.offerTitle, vendor: this.offerVendor, description: this.offerDescription, fromDialog: true } }));
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, paymentType: PaymentTypes.OFFER, invoice: this.offerInvoice.invoice, saveToDB: this.flgSaveToDB, bolt12: this.offerRequest, amount: this.offerAmount * 1000, zeroAmtOffer: this.zeroAmtOffer, title: this.offerTitle, vendor: this.offerVendor, description: this.offerDescription, fromDialog: true } }));
}
}
}
@ -264,7 +263,6 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
this.offerAmount = null;
this.offerDecodedHint = '';
this.zeroAmtOffer = false;
this.flgSaveToDB = false;
this.paymentError = '';
if (this.offerReq) { this.offerReq.control.setErrors(null); }
}
@ -367,6 +365,7 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
case PaymentTypes.OFFER:
this.offerRequest = '';
this.offerDecoded = {};
this.flgSaveToDB = false;
this.resetOfferDetails();
break;
default:

@ -95,9 +95,8 @@ export interface CLPaymentInformation {
paymentType: PaymentTypes;
invoiceBolt11?: string;
pubkeyKeysend?: string;
offerBolt12?: string;
bolt12?: string;
offerTitle?: string;
offerUUId?: string;
newlyAdded?: boolean;
component?: any;
}

@ -98,10 +98,8 @@ export interface Offer {
}
export interface OfferBookmark {
id?: string;
updatedAt?: string;
createdAt?: string;
offerBolt12?: string;
lastUpdatedAt?: string;
bolt12?: string;
amountmSat?: number;
title?: string;
vendor?: string;
@ -417,8 +415,7 @@ export interface SendPayment {
invoice?: string;
description?: string;
saveToDB?: boolean;
offerBolt12?: string;
offerUUID?: string;
bolt12?: string;
amount?: number;
zeroAmtOffer?: boolean;
pubkey?: string;

@ -290,6 +290,7 @@ export const UI_MESSAGES = {
DISABLE_OFFER: 'Disabling Offer...',
CREATE_OFFER: 'Creating Offer...',
DELETE_OFFER_BOOKMARK: 'Deleting Bookmark...',
LOG_OUT: 'Logging Out...'
};
export enum PaymentTypes {

@ -427,7 +427,7 @@ export class RTLEffects implements OnDestroy {
pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.VERIFY_TOKEN }));
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.VERIFY_TOKEN }));
this.store.dispatch(updateRootAPICallStatus({ payload: { action: 'VerifyToken', status: APICallStatusEnum.COMPLETED } }));
this.logger.info('Token Successfully Verified!');
this.setLoggedInDetails(false, action.payload.authResponse);
@ -446,16 +446,18 @@ export class RTLEffects implements OnDestroy {
ofType(RTLActions.LOGOUT),
withLatestFrom(this.store.select(rootAppConfig)),
mergeMap(([action, appConfig]) => {
if (+appConfig.sso.rtlSSO) {
window.location.href = appConfig.sso.logoutRedirectLink;
} else {
this.router.navigate(['./login']);
}
this.sessionService.clearAll();
this.store.dispatch(setNodeData({ payload: {} }));
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.LOG_OUT }));
return this.httpClient.get(environment.AUTHENTICATE_API + '/logout').
pipe(map((postRes: any) => {
this.logger.info(postRes);
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.LOG_OUT }));
if (+appConfig.sso.rtlSSO) {
window.location.href = appConfig.sso.logoutRedirectLink;
} else {
this.router.navigate(['./login']);
}
this.sessionService.clearAll();
this.store.dispatch(setNodeData({ payload: {} }));
this.logger.warn('LOGGED OUT');
}));
})),

Loading…
Cancel
Save