├── .babelrc
├── .editorconfig
├── .eslintrc.json
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .prettierignore
├── .prettierrc.yml
├── .travis.yml
├── LICENSE
├── README.md
├── client
├── app.js
├── components
│ ├── AboutUs.js
│ ├── AllTransactions.js
│ ├── BitcoinsInfo.js
│ ├── Chart.js
│ ├── ChartTwo.js
│ ├── FAQs.js
│ ├── Footer.js
│ ├── HomePage.js
│ ├── MessageForm.js
│ ├── UserInfo.js
│ ├── auth-form.js
│ ├── index.js
│ ├── navbar.js
│ ├── searchBar.js
│ ├── signUp.js
│ ├── signup.js
│ ├── user-home.spec.js
│ └── userHome.js
├── history.js
├── index.js
├── routes.js
├── socket.js
└── store
│ ├── allUsers.js
│ ├── index.js
│ ├── transactions.js
│ ├── user.js
│ └── user.spec.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── images
│ ├── LightText.htm
│ ├── LightText_files
│ │ ├── bundle.js
│ │ ├── css
│ │ ├── css(1)
│ │ ├── lightbulb.png
│ │ ├── lightbulbblack.png
│ │ ├── semantic.min.css
│ │ └── style.css
│ ├── Sihem_1.jpg
│ ├── download-1.png
│ ├── download.png
│ ├── iphone.png
│ ├── lightText.gif
│ ├── lightbulb.png
│ ├── lightbulbblack.png
│ ├── maurice.jpeg
│ ├── milan.jpeg
│ ├── screen.png
│ ├── screen2.png
│ ├── screen3.png
│ ├── screen4.png
│ └── tali.jpeg
├── index.html
└── style.css
├── script
├── deploy
├── encrypt-heroku-auth-token
├── seed.js
└── seed.spec.js
├── server
├── Util
│ └── chart.js
├── api
│ ├── admin.macaroon
│ ├── crypto.js
│ ├── index.js
│ ├── lightning.js
│ ├── promise.js
│ ├── receive_sms.js
│ ├── rpc.proto
│ ├── send_sms.js
│ ├── testnet
│ │ ├── admin.macaroon
│ │ ├── base64Admin.macaroon
│ │ ├── base64tls.cert
│ │ ├── tls (1).cert
│ │ ├── tls (1).key
│ │ ├── tls (2).cert
│ │ ├── tls (2).key
│ │ ├── tls (3).cert
│ │ ├── tls (4).cert
│ │ ├── tls (5).cert
│ │ ├── tls (6).cert
│ │ ├── tls.cert
│ │ └── tls.key
│ ├── transactions.js
│ ├── users.js
│ └── users.spec.js
├── auth
│ ├── google.js
│ └── index.js
├── db
│ ├── db.js
│ ├── index.js
│ └── models
│ │ ├── index.js
│ │ ├── transactions.js
│ │ ├── user.js
│ │ └── user.spec.js
├── index.js
└── socket
│ └── index.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/react",
4 | "@babel/env"
5 | /*
6 | Babel uses these "presets" to know how to transpile your Javascript code. Here's what we're saying with these:
7 |
8 | 'react': teaches Babel to recognize JSX - a must have for React!
9 |
10 | 'env': teaches Babel to transpile Javascript . This preset is highly configurable, and you can reduce the size of your bundle by limiting the number of features you transpile. Learn more here: https://github.com/babel/babel-preset-env
11 | */
12 | ],
13 | "plugins": [
14 | /*
15 | These plugins teach Babel to recognize EcmaScript language features that have reached "stage 2" in the process of approval for inclusion in the official EcmaScript specification (called the "TC39 process"). There are 5 stages in the process, starting at 0 (basically a brand new proposal) going up to 4 (finished and ready for inclusion). Read more about it here: http://2ality.com/2015/11/tc39-process.html. Using new language features before they're officially part of EcmaScript is fun, but it also carries a risk: sometimes proposed features can change substantially (or be rejected entirely) before finally being included in the language, so if you jump on the bandwagon too early, you risk having your code be dependent on defunct/nonstandard syntax! "Stage 2" is a fairly safe place to start - after stage 2, the feature is well on its way to official inclusion and only minor changes are expected.
16 | */
17 | "@babel/plugin-syntax-dynamic-import",
18 | "@babel/plugin-syntax-import-meta",
19 | "@babel/plugin-proposal-class-properties",
20 | "@babel/plugin-proposal-json-strings",
21 | [
22 | "@babel/plugin-proposal-decorators",
23 | {
24 | "legacy": true
25 | }
26 | ],
27 | "@babel/plugin-proposal-function-sent",
28 | "@babel/plugin-proposal-export-namespace-from",
29 | "@babel/plugin-proposal-numeric-separator",
30 | "@babel/plugin-proposal-throw-expressions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [*.{yml,yaml}]
16 | indent_style = space
17 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": ["fullstack", "prettier", "prettier/react"],
4 | "rules": {
5 | "semi": 0
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to this Project
2 |
3 | For pull requests to be merged, authors should:
4 |
5 | * Write any applicable unit tests
6 | * Add any relevant documentation
7 | * Reference any relevant issues
8 | * Obtain a review from a team member
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | For bugs, please include the following:
2 |
3 | * What is the expected behavior?
4 | * What is the actual behavior?
5 | * What steps reproduce the behavior?
6 |
7 | For features, please specify at least minimal requirements, e.g.:
8 |
9 | * "As a user, I want a notification badge showing unread count, so I can easily manage my messages"
10 | * "As a developer, I want linting to work properly with JSX, so I can see when there is a mistake"
11 | * "As an admin, I want a management panel for users, so I can delete spurious accounts"
12 |
13 | ---
14 |
15 | _Issue description here…_
16 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Assignee Tasks
2 |
3 | * [ ] added unit tests (or none needed)
4 | * [ ] written relevant docs (or none needed)
5 | * [ ] referenced any relevant issues (or none exist)
6 |
7 | ### Guidelines
8 |
9 | Please add a description of this Pull Request's motivation, scope, outstanding issues or potential alternatives, reasoning behind the current solution, and any other relevant information for posterity.
10 |
11 | ---
12 |
13 | _Your PR Notes Here_
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | public/bundle.js
3 | public/bundle.js.map
4 | secrets.js
5 | .DS_Store
6 | npm-debug.log
7 | yarn-error.log
8 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | public/bundle.js
2 | public/bundle.js.map
3 | package-lock.json
4 | package.json
5 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | # printWidth: 80 # 80
2 | # tabWidth: 2 # 2
3 | # useTabs: false # false
4 | semi: false # true
5 | singleQuote: true # false
6 | # trailingComma: none # none | es5 | all
7 | bracketSpacing: false # true
8 | # jsxBracketSameLine: false # false
9 | # arrowParens: avoid # avoid | always
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - node # uses most recent stable node version
4 | services:
5 | - postgresql # starts up postgres
6 | addons:
7 | postgresql: '9.6' # highest available postgres version on Travis
8 | dist: trusty # uses trusty environment
9 | sudo: false # when on trusty, uses Docker containers for speed
10 | notifications:
11 | email:
12 | on_success: change # default: change (only when going from broken to fixed)
13 | on_failure: always # default: always (which is annoying, as it should be)
14 | install:
15 | - npm ci # faster, goes only from package-lock
16 | before_script:
17 | - psql -c 'create database "lighttext-test";' -U postgres # remember to change this name if you change it elsewhere (e.g. package.json)
18 | script:
19 | - npm test # test the code
20 | - npm run build-client # make the bundle
21 |
22 | # before_deploy:
23 | # - rm -rf node_modules # omit from the tarball, since we skip cleanup
24 | # deploy:
25 | # skip_cleanup: true # prevents travis from deleting the build
26 | # provider: heroku
27 | # app: YOUR-HEROKU-APP-NAME-HERE # see README
28 | # api_key:
29 | # secure: YOUR-***ENCRYPTED***-API-KEY-HERE # see README
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Fullstack Academy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LightText
2 |
3 | LightText (LT) is a lightning wallet that allows you to send and receive bitcoin payments over the lightning network. LT is an SMS client aimed to provide access to crypto infrastructure for individuals without smartphones.
4 |
5 | Grab your favorite Nokia phone, sign up, fund your account with bitcoin and begin paying friends and lightning invoices.
6 |
7 | Website : [light-text.herokuapp.com](https://light-text.herokuapp.com/)
8 |
9 | Youtube : https://youtu.be/SxjsJ4FDSzI
10 |
11 |
12 |
13 | 
14 |
15 | 
16 |
17 | 
18 |
19 | 
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/client/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import {Navbar, Footer} from './components'
4 | import Routes from './routes'
5 |
6 | const App = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default App
17 |
--------------------------------------------------------------------------------
/client/components/AboutUs.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function AboutUs() {
4 | return (
5 |
6 |
7 |
About us
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Taulant Vokshi
15 |
United States
16 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Milan Patel
34 |
United States
35 |
39 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
Sihem Meriki
53 |
France
54 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
Maurice Shalam
72 |
United States
73 |
77 |
83 |
84 |
85 |
86 |
87 |
88 | Created at Fullstack Academy of Code in New York
89 |
90 |
91 |
92 | )
93 | }
94 |
--------------------------------------------------------------------------------
/client/components/AllTransactions.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {connect} from 'react-redux'
3 | import {getTransactionThunk} from '../store/transactions'
4 | import {Table} from 'semantic-ui-react'
5 | import Moment from 'react-moment'
6 | import 'moment-timezone'
7 | /**
8 | * COMPONENT
9 | */
10 | export class UserInfo extends React.Component {
11 | constructor(props) {
12 | super(props)
13 | this.state = {
14 | receiverTransaction: [],
15 | senderTransaction: []
16 | }
17 | }
18 | async componentDidMount() {
19 | await this.props.getTransaction()
20 | this.setState({
21 | receiverTransaction: this.props.history.transaction.receiver,
22 | senderTransaction: this.props.history.transaction.sender
23 | })
24 | }
25 | render() {
26 | const receiverTransaction = this.state.receiverTransaction
27 | const senderTransaction = this.state.senderTransaction
28 | let receiverTransactionHtml = (
29 |
30 |
31 | No transaction recorded
32 |
33 |
34 | )
35 | let senderTransactionHtml = (
36 |
37 |
38 | No transaction recorded
39 |
40 |
41 | )
42 | if (receiverTransaction.length > 0) {
43 | receiverTransactionHtml = receiverTransaction.map((transaction, i) => {
44 | return (
45 |
46 | {transaction.sender.username}
47 |
48 | {transaction.amount} satoshis
49 |
50 |
51 | {transaction.createdAt}
52 |
53 |
54 | )
55 | })
56 | }
57 | if (senderTransaction.length > 0) {
58 | senderTransactionHtml = senderTransaction.map((transaction, i) => {
59 | return (
60 |
61 | {transaction.receiver.username}
62 |
63 | {transaction.amount} satoshis
64 |
65 |
66 | {transaction.createdAt}
67 |
68 |
69 | )
70 | })
71 | }
72 | return (
73 |
74 |
Your transactions
75 |
76 |
77 |
78 |
Received
79 |
80 |
81 |
82 | From
83 |
84 | Amount
85 |
86 | Date
87 |
88 |
89 |
90 | {receiverTransactionHtml}
91 |
92 |
93 |
94 |
Sent
95 |
96 |
97 |
98 | To
99 |
100 | Amount
101 |
102 | Date
103 |
104 |
105 |
106 | {senderTransactionHtml}
107 |
108 |
109 |
110 |
111 |
112 | )
113 | }
114 | }
115 |
116 | /**
117 | * CONTAINER
118 | */
119 | const mapState = state => {
120 | return {
121 | user: state.user,
122 | history: state.transactions
123 | }
124 | }
125 | const dispatchMapState = dispatch => {
126 | return {
127 | getTransaction: () => dispatch(getTransactionThunk())
128 | }
129 | }
130 |
131 | export default connect(mapState, dispatchMapState)(UserInfo)
132 |
--------------------------------------------------------------------------------
/client/components/BitcoinsInfo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {input} from 'semantic-ui-react'
3 | export default class BitcoinInfo extends React.Component {
4 | constructor() {
5 | super()
6 | this.state = {
7 | date: '',
8 | rate: '',
9 | value: 1
10 | }
11 | this.onChange = this.onChange.bind(this)
12 | }
13 | componentDidMount() {
14 | var that = this
15 | var date = new Date().getDate()
16 | var month = new Date().getMonth() + 1
17 | var year = new Date().getFullYear()
18 | var hours = new Date().getHours()
19 | var min = new Date().getMinutes()
20 | var sec = new Date().getSeconds()
21 | that.setState({
22 | date:
23 | date + '/' + month + '/' + year + ' ' + hours + ':' + min + ':' + sec
24 | })
25 | fetch('https://api.coindesk.com/v1/bpi/currentprice.json')
26 | .then(response => response.json())
27 | .then(responseJson => {
28 | this.setState({
29 | rate: responseJson.bpi.USD.rate_float
30 | })
31 | // console.log('eur to bit : ' + responseJson.bpi.EUR.rate)
32 | })
33 | .catch(error => {
34 | console.error(error)
35 | })
36 | }
37 | onChange(e) {
38 | const re = /^[0-9\b]+$/
39 | if (e.target.value === '' || re.test(e.target.value)) {
40 | this.setState({value: e.target.value})
41 | }
42 | }
43 | render() {
44 | const result = this.state.rate * this.state.value
45 | return (
46 |
47 |
Bitcoins Information
48 |
Last Update : {this.state.date}
49 |
50 | Today's rate : 1 Bitcoin = {Number(this.state.rate).toFixed(2)} USD
51 |
52 |
Convert your bitcoins to dollars :
53 |
54 |
55 |
63 |
64 |
= {Number(result).toFixed(2)} USD
65 |
66 | )
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/client/components/Chart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {connect} from 'react-redux'
3 | import {Bar, Pie, Polar} from 'react-chartjs-2'
4 | import {getTransactionThunk} from '../store/transactions'
5 | class ChartTwo extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.createChart = this.createChart.bind(this)
9 | }
10 | async componentDidMount() {
11 | await this.props.getTransactions()
12 | }
13 |
14 | createChart(data, status) {
15 | return {
16 | labels: [...Object.keys(data)],
17 | datasets: [
18 | {
19 | label: status,
20 | backgroundColor: [
21 | '#d34848',
22 | '#ff8162',
23 | '#fffa67',
24 | 'rgba(153, 102, 255, 0.2)',
25 | '#62374e'
26 | ],
27 | data: [...Object.values(data)]
28 | }
29 | ]
30 | }
31 | }
32 |
33 | render() {
34 | const receiveData = {}
35 |
36 | !!this.props.history.transaction.sender &&
37 | this.props.history.transaction.sender.forEach(user => {
38 | const username = user.receiver.username
39 | !receiveData[username]
40 | ? (receiveData[username] = user.amount)
41 | : (receiveData[username] += user.amount)
42 | })
43 |
44 | return (
45 |
48 | )
49 | }
50 | }
51 | const mapStateToProps = state => {
52 | return {history: state.transactions}
53 | }
54 | const mapDispatchToProps = dispatch => {
55 | return {getTransactions: () => dispatch(getTransactionThunk())}
56 | }
57 |
58 | export default connect(mapStateToProps, mapDispatchToProps)(ChartTwo)
59 |
--------------------------------------------------------------------------------
/client/components/ChartTwo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {connect} from 'react-redux'
3 | import {Bar, Pie, Polar} from 'react-chartjs-2'
4 | import {getTransactionThunk} from '../store/transactions'
5 | class Chart extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.createChart = this.createChart.bind(this)
9 | }
10 | async componentDidMount() {
11 | await this.props.getTransactions()
12 | }
13 |
14 | createChart(data, status) {
15 | return {
16 | labels: [...Object.keys(data)],
17 | datasets: [
18 | {
19 | label: status,
20 | backgroundColor: [
21 | '#616f39',
22 | '#a7d129',
23 | '#f8eeb4',
24 | 'rgba(153, 102, 255, 0.2)',
25 | '#62374e'
26 | ],
27 | data: [...Object.values(data)]
28 | }
29 | ]
30 | }
31 | }
32 |
33 | render() {
34 | const sendData = {}
35 |
36 | !!this.props.history.transaction.receiver &&
37 | this.props.history.transaction.receiver.forEach(user => {
38 | const username = user.sender.username
39 | !sendData[username]
40 | ? (sendData[username] = user.amount)
41 | : (sendData[username] += user.amount)
42 | })
43 |
44 | return (
45 |
48 | )
49 | }
50 | }
51 | const mapStateToProps = state => {
52 | return {history: state.transactions}
53 | }
54 | const mapDispatchToProps = dispatch => {
55 | return {getTransactions: () => dispatch(getTransactionThunk())}
56 | }
57 |
58 | export default connect(mapStateToProps, mapDispatchToProps)(Chart)
59 |
--------------------------------------------------------------------------------
/client/components/FAQs.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Link} from 'react-router-dom'
3 |
4 | export default function FAQs() {
5 | return (
6 |
7 |
FAQs
8 |
9 |
10 | What are the advantages of the Lightning Network ?
11 |
12 |
13 |
14 | Instant Payments.
15 |
16 | Lightning-fast blockchain payments without worrying about block
17 | confirmation times. Security is enforced by blockchain
18 | smart-contracts without creating a on-blockchain transaction for
19 | individual payments. Payment speed measured in milliseconds to
20 | seconds.{' '}
21 |
22 |
23 |
24 | Scalability.
25 |
26 | Capable of millions to billions of transactions per second across
27 | the network. Capacity blows away legacy payment rails by many
28 | orders of magnitude. Attaching payment per action/click is now
29 | possible without custodians.
30 |
31 |
32 |
33 | Low Cost.
34 |
35 | By transacting and settling off-blockchain, the Lightning Network
36 | allows for exceptionally low fees, which allows for emerging use
37 | cases such as instant micropayments.
38 |
39 |
40 |
41 | Cross Blockchains.
42 |
43 | Cross-chain atomic swaps can occur off-chain instantly with
44 | heterogeneous blockchain consensus rules. So long as the chains
45 | can support the same cryptographic hash function, it is possible
46 | to make transactions across blockchains without trust in 3rd party
47 | custodians.
48 |
49 |
50 |
51 |
52 |
53 |
How do I send Bitcoin with LightText ?
54 |
55 | You can send Bitcoin by texting 'SEND' + the amount you want to send +
56 | the username or number of the person you want to send to. For more
57 | information go back to the Home page.
58 |
59 |
60 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/client/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Link} from 'react-router-dom'
3 |
4 | export default function Footer() {
5 | return (
6 |
7 |
8 |
9 | Home
10 |
11 |
12 | FAQs
13 |
14 |
15 |
16 |
© LightText All Rights Reserved. 2019
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/client/components/HomePage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | // import {Link} from 'react-router-dom'
3 | import {Signup} from './signUp'
4 | import {AboutUs} from './'
5 | import BitcoinInfo from './BitcoinsInfo'
6 |
7 | export default function HomePage() {
8 | return (
9 |
10 |
11 |
12 |
13 |
How It Works
14 |
15 |
16 | With LightText you can send Bitcoins by SMS. You have to register
17 | with us and send money to someone registered. You don't need
18 | Internet, just a phone number.
19 |
20 | Lightning Wallets ⚡
21 | with zero configuration
22 |
23 | Unfairly cheap and fast transactions. LightText brings zero
24 | configuration, ready to use, user friendly Lightning Network
25 | Wallets for iOS and Android. Read more about our open source.
26 |
27 |
28 |
29 |
30 |
37 |
48 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | It is easy if you can text, you can crypto !
62 |
63 |
64 |
69 |
79 |
82 |
83 | )
84 | }
85 |
--------------------------------------------------------------------------------
/client/components/MessageForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Button, Form} from 'semantic-ui-react'
3 | import {connect} from 'react-redux'
4 | import {postMessageThunk, getTransactionThunk} from '../store/transactions'
5 | import {withToastManager} from 'react-toast-notifications'
6 |
7 | class MessageForm extends React.Component {
8 | constructor(props) {
9 | super(props)
10 | this.getMessageStatus = this.getMessageStatus.bind(this)
11 | }
12 |
13 | getMessageStatus(message) {
14 | if (
15 | message.startsWith('You have insufficient') ||
16 | message.startsWith('The user you are trying') ||
17 | message.startsWith('We are in beta') ||
18 | message.startsWith('You need to enter') ||
19 | message.startsWith('You are not registered') ||
20 | message.startsWith('You can only send')
21 | ) {
22 | return 'warning'
23 | } else {
24 | return 'success'
25 | }
26 | }
27 | //Hello
28 | render() {
29 | const {handleSubmit} = this.props
30 | console.log(this.getMessageStatus(this.props.message), 'getMessageStatus')
31 | return (
32 |
33 |
34 | You prefere to send a message from your account instead of a SMS ? It
35 | is possible , use the box here !
36 |
37 |
61 |
62 | )
63 | }
64 | }
65 | const mapProps = state => {
66 | return {
67 | message: state.transactions.message
68 | }
69 | }
70 |
71 | const mapDispatch = dispatch => {
72 | return {
73 | handleSubmit(evt) {
74 | evt.preventDefault()
75 | const messages = evt.target.messages.value
76 | dispatch(postMessageThunk(messages))
77 |
78 | evt.target.messages.value = ''
79 | }
80 | }
81 | }
82 | export default withToastManager(connect(mapProps, mapDispatch)(MessageForm))
83 |
--------------------------------------------------------------------------------
/client/components/UserInfo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {connect} from 'react-redux'
3 | import {getTransactionThunk} from '../store/transactions'
4 | import {Table, Button} from 'semantic-ui-react'
5 | import Moment from 'react-moment'
6 | import {Link} from 'react-router-dom'
7 | import 'moment-timezone'
8 | /**
9 | * COMPONENT
10 | */
11 | export class UserInfo extends React.Component {
12 | constructor(props) {
13 | super(props)
14 | this.state = {
15 | receiverTransaction: [],
16 | senderTransaction: []
17 | }
18 | }
19 |
20 | componentDidUpdate(prevProps) {
21 | const prevreceiver = prevProps.history.transaction.receiver
22 | const prevsender = prevProps.history.transaction.sender
23 |
24 | if (prevreceiver || prevsender) {
25 | const receiver = this.props.history.transaction.receiver
26 | const sender = this.props.history.transaction.sender
27 | if (
28 | sender.length !== prevsender.length ||
29 | receiver.length !== prevreceiver.length
30 | ) {
31 | this.setState({
32 | receiverTransaction: this.props.history.transaction.receiver.slice(
33 | -6
34 | ),
35 | senderTransaction: this.props.history.transaction.sender.slice(-6)
36 | })
37 | }
38 | }
39 | }
40 | async componentDidMount() {
41 | await this.props.getTransaction()
42 | this.setState({
43 | receiverTransaction: this.props.history.transaction.receiver.slice(-6),
44 | senderTransaction: this.props.history.transaction.sender.slice(-6)
45 | })
46 | }
47 | render() {
48 | console.log(this.props.history.transaction, 'This are my props')
49 | const receiverTransaction = this.state.receiverTransaction
50 | const senderTransaction = this.state.senderTransaction
51 | let receiverTransactionHtml = (
52 |
53 |
54 | No transaction recorded
55 |
56 |
57 | )
58 | let senderTransactionHtml = (
59 |
60 |
61 | No transaction recorded
62 |
63 |
64 | )
65 | if (receiverTransaction.length > 0) {
66 | receiverTransactionHtml = receiverTransaction.map((transaction, i) => {
67 | if (i < 10) {
68 | return (
69 |
70 | {transaction.sender.username}
71 |
72 | {transaction.amount} satoshis
73 |
74 |
75 | {transaction.createdAt}
76 |
77 |
78 | )
79 | }
80 | })
81 | }
82 | if (senderTransaction.length > 0) {
83 | senderTransactionHtml = senderTransaction.map((transaction, i) => {
84 | return (
85 |
86 | {transaction.receiver.username}
87 |
88 | {transaction.amount} satoshis
89 |
90 |
91 | {transaction.createdAt}
92 |
93 |
94 | )
95 | })
96 | }
97 | return (
98 |
99 |
Your transactions
100 |
105 | See more transactions
106 |
107 |
108 |
109 |
110 |
Received
111 |
112 |
113 |
114 | From
115 |
116 | Amount
117 |
118 | Date
119 |
120 |
121 |
122 | {receiverTransactionHtml}
123 |
124 |
125 |
126 |
Sent
127 |
128 |
129 |
130 | To
131 |
132 | Amount
133 |
134 | Date
135 |
136 |
137 | {senderTransactionHtml}
138 |
139 |
140 |
141 |
142 |
143 | )
144 | }
145 | }
146 |
147 | /**
148 | * CONTAINER
149 | */
150 | const mapState = state => {
151 | return {
152 | user: state.user,
153 | history: state.transactions
154 | }
155 | }
156 | const dispatchMapState = dispatch => {
157 | return {
158 | getTransaction: () => dispatch(getTransactionThunk())
159 | }
160 | }
161 |
162 | export default connect(mapState, dispatchMapState)(UserInfo)
163 |
164 | // PROP TYPES
165 |
166 | // UserInfo.propTypes = {
167 | // email: PropTypes.string
168 | // }
169 |
--------------------------------------------------------------------------------
/client/components/auth-form.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {connect} from 'react-redux'
3 | import PropTypes from 'prop-types'
4 | import {login} from '../store'
5 |
6 | import {Link} from 'react-router-dom'
7 | import {
8 | Button,
9 | Form,
10 | Grid,
11 | Header,
12 | Image,
13 | Message,
14 | Segment
15 | } from 'semantic-ui-react'
16 |
17 | /**
18 | * COMPONENT
19 | */
20 |
21 | const AuthForm = props => {
22 | const {name, displayName, handleSubmit, error} = props
23 | return (
24 |
25 | {/*
26 | Heads up! The styles below are necessary for the correct render of this example.
27 | You can do same with CSS, the main idea is that all the elements up to the `Grid`
28 | below must have a height of 100%.
29 | */}
30 |
37 |
38 |
39 |
40 | {displayName} to your account
41 |
42 |
67 | {/*
68 | {displayName} with Google
69 | */}
70 |
71 |
72 |
73 | )
74 | }
75 |
76 | /**
77 | * CONTAINER
78 | * Note that we have two different sets of 'mapStateToProps' functions -
79 | * one for Login, and one for Signup. However, they share the same 'mapDispatchToProps'
80 | * function, and share the same Component. This is a good example of how we
81 | * can stay DRY with interfaces that are very similar to each other!
82 | */
83 | const mapLogin = state => {
84 | return {
85 | name: 'login',
86 | displayName: 'Login',
87 | error: state.user.error
88 | }
89 | }
90 |
91 | const mapDispatch = dispatch => {
92 | return {
93 | handleSubmit(evt) {
94 | evt.preventDefault()
95 | const formName = evt.target.name
96 | const email = evt.target.email.value
97 | const password = evt.target.password.value
98 | dispatch(login(email, password, formName))
99 | }
100 | }
101 | }
102 |
103 | export const Login = connect(mapLogin, mapDispatch)(AuthForm)
104 | /**
105 | * PROP TYPES
106 | */
107 | AuthForm.propTypes = {
108 | name: PropTypes.string.isRequired,
109 | displayName: PropTypes.string.isRequired,
110 | handleSubmit: PropTypes.func.isRequired,
111 | error: PropTypes.object
112 | }
113 |
--------------------------------------------------------------------------------
/client/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * `components/index.js` exists simply as a 'central export' for our components.
3 | * This way, we can import all of our components from the same place, rather than
4 | * having to figure out which file they belong to!
5 | */
6 | export {default as Navbar} from './navbar'
7 | export {default as userHome} from './userHome'
8 | export {Login} from './auth-form'
9 | export {signup} from './signUp'
10 | export {default as HomePage} from './HomePage'
11 | export {default as SearchBar} from './searchBar'
12 | export {default as BitcoinsInfo} from './BitcoinsInfo'
13 | export {default as FAQs} from './FAQs'
14 | export {default as AboutUs} from './AboutUs'
15 | export {default as AllTransactions} from './AllTransactions'
16 | export {default as Footer} from './Footer'
17 |
--------------------------------------------------------------------------------
/client/components/navbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import {connect} from 'react-redux'
4 | import {Link} from 'react-router-dom'
5 | import {logout} from '../store'
6 |
7 | const Navbar = ({handleClick, isLoggedIn}) => (
8 |
9 |
10 |
11 |
12 |
13 |
LightText
14 |
15 |
16 |
17 |
18 | {isLoggedIn ? (
19 |
20 | {/* The navbar will show these links after you log in */}
21 |
22 |
23 | FAQs
24 |
25 |
26 | My profile
27 |
28 |
29 |
30 | All Transactions
31 |
32 |
33 | Logout
34 |
35 |
36 |
37 | ) : (
38 |
39 |
40 |
41 | Login
42 |
43 |
44 | FAQs
45 |
46 |
47 |
48 | )}
49 |
50 |
51 | )
52 |
53 | /**
54 | * CONTAINER
55 | */
56 | const mapState = state => {
57 | return {
58 | isLoggedIn: !!state.user.id
59 | }
60 | }
61 |
62 | const mapDispatch = dispatch => {
63 | return {
64 | handleClick() {
65 | dispatch(logout())
66 | }
67 | }
68 | }
69 |
70 | export default connect(mapState, mapDispatch)(Navbar)
71 |
72 | /**
73 | * PROP TYPES
74 | */
75 | Navbar.propTypes = {
76 | handleClick: PropTypes.func.isRequired,
77 | isLoggedIn: PropTypes.bool.isRequired
78 | }
79 |
--------------------------------------------------------------------------------
/client/components/searchBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {input} from 'semantic-ui-react'
3 | import {connect} from 'react-redux'
4 | import {getAllUsersThunks} from '../store/allUsers'
5 |
6 | class SearchBar extends React.Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | searchWord: '',
11 | hideResults: true
12 | }
13 | this.handleChange = this.handleChange.bind(this)
14 | }
15 | async componentDidMount() {
16 | await this.props.getAllUsers()
17 | }
18 | handleBlur = () => {
19 | this.setState({
20 | hideResults: true
21 | })
22 | }
23 | handleChange = event => {
24 | this.setState({
25 | searchWord: event.target.value,
26 | hideResults: false
27 | })
28 | }
29 | render() {
30 | return (
31 |
32 |
33 | You are looking for a user and you do not remember his username ? Find
34 | it here !
35 |
36 |
56 |
57 | )
58 | }
59 | }
60 | const mapState = state => {
61 | return {
62 | allUsers: state.allUsers
63 | }
64 | }
65 |
66 | const mapDispatch = dispatch => {
67 | return {getAllUsers: () => dispatch(getAllUsersThunks())}
68 | }
69 |
70 | export default connect(mapState, mapDispatch)(SearchBar)
71 |
--------------------------------------------------------------------------------
/client/components/signUp.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {connect} from 'react-redux'
3 | import {auth} from '../store'
4 | import {Link} from 'react-router-dom'
5 | import {
6 | Button,
7 | Form,
8 | Grid,
9 | Header,
10 | Image,
11 | Message,
12 | Segment
13 | } from 'semantic-ui-react'
14 |
15 | /**
16 | * COMPONENT
17 | */
18 |
19 | const SignUp = props => {
20 | const {name, displayName, handleSubmit, error} = props
21 | return (
22 |
23 | {/*
24 | Heads up! The styles below are necessary for the correct render of this example.
25 | You can do same with CSS, the main idea is that all the elements up to the `Grid`
26 | below must have a height of 100%.
27 | */}
28 |
35 |
36 |
37 |
38 |
39 | {displayName} to your account
40 |
41 |
95 | {/*
96 | {displayName} with Google
97 | */}
98 |
99 |
100 |
101 | )
102 | }
103 |
104 | const mapSignup = state => {
105 | return {
106 | name: 'signup',
107 | displayName: 'Sign Up',
108 | error: state.user.error
109 | }
110 | }
111 | const mapDispatch = dispatch => {
112 | return {
113 | handleSubmit(evt) {
114 | evt.preventDefault()
115 | const email = evt.target.email.value
116 | const password = evt.target.password.value
117 | const phone = evt.target.phoneNumber.value
118 | const wallet = evt.target.wallet.value
119 | const userName = evt.target.userName.value
120 | dispatch(auth(userName, email, password, phone, wallet))
121 | }
122 | }
123 | }
124 |
125 | export const Signup = connect(mapSignup, mapDispatch)(SignUp)
126 |
--------------------------------------------------------------------------------
/client/components/signup.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {connect} from 'react-redux'
3 | import {auth} from '../store'
4 |
5 | import {Button, Form, Grid, Header, Image, Segment} from 'semantic-ui-react'
6 |
7 | /**
8 | * COMPONENT
9 | */
10 |
11 | const SignUp = props => {
12 | const {name, displayName, handleSubmit, error} = props
13 | return (
14 |
15 | {/*
16 | Heads up! The styles below are necessary for the correct render of this example.
17 | You can do same with CSS, the main idea is that all the elements up to the `Grid`
18 | below must have a height of 100%.
19 | */}
20 |
27 |
28 |
29 |
30 | {/* */}
31 | {displayName} to your account
32 |
33 |
77 | {/*
78 | {displayName} with Google
79 | */}
80 |
81 |
82 |
83 | )
84 | }
85 |
86 | const mapSignup = state => {
87 | return {
88 | name: 'signup',
89 | displayName: 'Sign Up',
90 | error: state.user.error
91 | }
92 | }
93 | const mapDispatch = dispatch => {
94 | return {
95 | handleSubmit(evt) {
96 | evt.preventDefault()
97 | const newPhone = evt.target.phoneNumber.value
98 | const userName = evt.target.userName.value
99 | const email = evt.target.email.value
100 | const password = evt.target.password.value
101 | const phone = '+1' + newPhone
102 |
103 | dispatch(auth(userName, email, password, phone))
104 | }
105 | }
106 | }
107 |
108 | export const Signup = connect(mapSignup, mapDispatch)(SignUp)
109 |
--------------------------------------------------------------------------------
/client/components/user-home.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe beforeEach it */
2 |
3 | import {expect} from 'chai'
4 | import React from 'react'
5 | import enzyme, {shallow} from 'enzyme'
6 | import Adapter from 'enzyme-adapter-react-16'
7 | import {userHome} from './userHome'
8 |
9 | const adapter = new Adapter()
10 | enzyme.configure({adapter})
11 |
12 | describe('userHome', () => {
13 | let userHome
14 |
15 | beforeEach(() => {
16 | userHome = shallow( )
17 | })
18 |
19 | it('renders the email in an h3', () => {
20 | expect(userHome.find('h3').text()).to.be.equal('Welcome, cody@email.com')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/client/components/userHome.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import UserInfo from '../components/UserInfo'
3 | import MessageForm from '../components/MessageForm'
4 | import BitcoinsInfo from '../components/BitcoinsInfo'
5 | import {SearchBar} from './'
6 | import Chart from '../components/Chart'
7 | import ChartTwo from '../components/ChartTwo'
8 | class userHome extends React.Component {
9 | render() {
10 | return (
11 |
12 |
13 |
18 |
19 |
27 |
28 |
Send a text
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Received chart
39 |
40 |
41 |
42 |
Sent chart
43 |
44 |
45 |
46 |
47 | )
48 | }
49 | }
50 |
51 | export default userHome
52 |
--------------------------------------------------------------------------------
/client/history.js:
--------------------------------------------------------------------------------
1 | import createHistory from 'history/createBrowserHistory'
2 | import createMemoryHistory from 'history/createMemoryHistory'
3 |
4 | const history =
5 | process.env.NODE_ENV === 'test' ? createMemoryHistory() : createHistory()
6 |
7 | export default history
8 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import {Provider} from 'react-redux'
4 | import {Router} from 'react-router-dom'
5 | import history from './history'
6 | import store from './store'
7 | import App from './app'
8 | import {ToastProvider} from 'react-toast-notifications'
9 |
10 | // establishes socket connection
11 | import './socket'
12 |
13 | ReactDOM.render(
14 |
15 |
16 |
17 |
18 |
19 |
20 | ,
21 | document.getElementById('app')
22 | )
23 |
--------------------------------------------------------------------------------
/client/routes.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {withRouter, Route, Switch} from 'react-router-dom'
4 | import PropTypes from 'prop-types'
5 | import {Login, userHome, HomePage, FAQs} from './components'
6 | import {Signup} from './components/signUp'
7 | import {me} from './store'
8 | import AllTransactions from './components/AllTransactions'
9 | /**
10 | * COMPONENT
11 | */
12 | class Routes extends Component {
13 | componentDidMount() {
14 | this.props.loadInitialData()
15 | }
16 |
17 | render() {
18 | const {isLoggedIn} = this.props
19 |
20 | return (
21 |
22 | {/* Routes placed here are available to all visitors */}
23 |
24 |
25 |
26 |
27 |
28 |
29 | {isLoggedIn && (
30 |
31 | {/* Routes placed here are only available after logging in */}
32 |
33 |
34 |
35 |
36 | )}
37 | {/* Displays our Login component as a fallback */}
38 |
39 |
40 |
41 | )
42 | }
43 | }
44 |
45 | /**
46 | * CONTAINER
47 | */
48 | const mapState = state => {
49 | return {
50 | // Being 'logged in' for our purposes will be defined has having a state.user that has a truthy id.
51 | // Otherwise, state.user will be an empty object, and state.user.id will be falsey
52 | isLoggedIn: !!state.user.id
53 | }
54 | }
55 |
56 | const mapDispatch = dispatch => {
57 | return {
58 | loadInitialData() {
59 | dispatch(me())
60 | }
61 | }
62 | }
63 |
64 | // The `withRouter` wrapper makes sure that updates are not blocked
65 | // when the url changes
66 | export default withRouter(connect(mapState, mapDispatch)(Routes))
67 |
68 | /**
69 | * PROP TYPES
70 | */
71 | Routes.propTypes = {
72 | loadInitialData: PropTypes.func.isRequired,
73 | isLoggedIn: PropTypes.bool.isRequired
74 | }
75 |
--------------------------------------------------------------------------------
/client/socket.js:
--------------------------------------------------------------------------------
1 | import io from 'socket.io-client'
2 |
3 | const socket = io(window.location.origin)
4 |
5 | socket.on('connect', () => {
6 | console.log('Connected!')
7 | })
8 |
9 | export default socket
10 |
--------------------------------------------------------------------------------
/client/store/allUsers.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const GET_ALLUSERS = 'GET_ALLUSERS'
4 |
5 | const users = []
6 |
7 | const getAllUsers = users => ({type: GET_ALLUSERS, users})
8 |
9 | export const getAllUsersThunks = () => {
10 | return async dispatch => {
11 | try {
12 | const res = await axios.get('/api/users')
13 | dispatch(getAllUsers(res.data))
14 | } catch (error) {
15 | throw new Error('An error has occur: err')
16 | }
17 | }
18 | }
19 |
20 | export default function(state = users, action) {
21 | switch (action.type) {
22 | case GET_ALLUSERS:
23 | return action.users
24 | default:
25 | return state
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/client/store/index.js:
--------------------------------------------------------------------------------
1 | import {createStore, combineReducers, applyMiddleware} from 'redux'
2 | import createLogger from 'redux-logger'
3 | import thunkMiddleware from 'redux-thunk'
4 | import {composeWithDevTools} from 'redux-devtools-extension'
5 | import user from './user'
6 | import transactions from './transactions'
7 | import allUsers from './allUsers'
8 |
9 | const reducer = combineReducers({user, transactions, allUsers})
10 | const middleware = composeWithDevTools(
11 | applyMiddleware(thunkMiddleware, createLogger({collapsed: true}))
12 | )
13 | const store = createStore(reducer, middleware)
14 |
15 | export default store
16 | export * from './user'
17 |
--------------------------------------------------------------------------------
/client/store/transactions.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | /**
4 | * ACTION TYPES
5 | */
6 | const GET_TRANSACTIONS = 'GET_TRANSACTIONS'
7 | const POST_MESSAGE = 'POST_MESSAGE'
8 | /**
9 | * INITIAL STATE
10 | */
11 | let defaultTransaction = {
12 | transaction: [],
13 | message: ''
14 | }
15 | /**
16 | * ACTION CREATORS
17 | */
18 | const getTransaction = transaction => ({type: GET_TRANSACTIONS, transaction})
19 | const postMessage = message => ({type: POST_MESSAGE, message})
20 | /**
21 | * THUNK CREATORS
22 | */
23 |
24 | export const getTransactionThunk = () => {
25 | return async dispatch => {
26 | try {
27 | const results = await axios.get('/api/transactions')
28 | dispatch(getTransaction(results.data))
29 | } catch (error) {
30 | throw new Error('An error has occur: err')
31 | }
32 | }
33 | }
34 |
35 | export const postMessageThunk = messages => {
36 | return async dispatch => {
37 | try {
38 | const results = await axios.post('/api/sms', {
39 | messages
40 | })
41 | dispatch(postMessage(results.data))
42 | dispatch(getTransactionThunk())
43 | } catch (error) {
44 | throw new Error('An error has occur: err')
45 | }
46 | }
47 | }
48 |
49 | export default function(state = defaultTransaction, action) {
50 | switch (action.type) {
51 | case GET_TRANSACTIONS:
52 | return {...state, transaction: action.transaction}
53 | case POST_MESSAGE:
54 | return {...state, message: action.message}
55 | default:
56 | return state
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/client/store/user.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import history from '../history'
3 |
4 | /**
5 | * ACTION TYPES
6 | */
7 | const GET_USER = 'GET_USER'
8 | const REMOVE_USER = 'REMOVE_USER'
9 |
10 | /**
11 | * INITIAL STATE
12 | */
13 | const defaultUser = {}
14 |
15 | /**
16 | * ACTION CREATORS
17 | */
18 | const getUser = user => ({type: GET_USER, user})
19 | const removeUser = () => ({type: REMOVE_USER})
20 |
21 | /**
22 | * THUNK CREATORS
23 | */
24 | export const me = () => async dispatch => {
25 | try {
26 | const res = await axios.get('/auth/me')
27 | dispatch(getUser(res.data || defaultUser))
28 | } catch (err) {
29 | console.error(err)
30 | }
31 | }
32 |
33 | export const auth = (
34 | username,
35 | email,
36 | password,
37 | phone,
38 | wallet
39 | ) => async dispatch => {
40 | let res
41 | try {
42 | res = await axios.post(`/auth/signup`, {
43 | username,
44 | email,
45 | password,
46 | phone,
47 | wallet
48 | })
49 | } catch (authError) {
50 | return dispatch(getUser({error: authError}))
51 | }
52 |
53 | try {
54 | dispatch(getUser(res.data))
55 | history.push('/userHome')
56 | } catch (dispatchOrHistoryErr) {
57 | console.error(dispatchOrHistoryErr)
58 | }
59 | }
60 |
61 | export const login = (email, password) => async dispatch => {
62 | let res
63 | console.log(email, 'this is the email')
64 | try {
65 | res = await axios.post(`/auth/login`, {
66 | password,
67 | email
68 | })
69 | } catch (authError) {
70 | return dispatch(getUser({error: authError}))
71 | }
72 |
73 | try {
74 | dispatch(getUser(res.data))
75 | history.push('/userHome')
76 | } catch (dispatchOrHistoryErr) {
77 | console.error(dispatchOrHistoryErr)
78 | }
79 | }
80 | export const logout = () => async dispatch => {
81 | try {
82 | await axios.post('/auth/logout')
83 | dispatch(removeUser())
84 | history.push('/login')
85 | } catch (err) {
86 | console.error(err)
87 | }
88 | }
89 |
90 | /**
91 | * REDUCER
92 | */
93 | export default function(state = defaultUser, action) {
94 | switch (action.type) {
95 | case GET_USER:
96 | return action.user
97 | case REMOVE_USER:
98 | return defaultUser
99 | default:
100 | return state
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/client/store/user.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe beforeEach afterEach it */
2 |
3 | import {expect} from 'chai'
4 | import {me, logout} from './user'
5 | import axios from 'axios'
6 | import MockAdapter from 'axios-mock-adapter'
7 | import configureMockStore from 'redux-mock-store'
8 | import thunkMiddleware from 'redux-thunk'
9 | import history from '../history'
10 |
11 | const middlewares = [thunkMiddleware]
12 | const mockStore = configureMockStore(middlewares)
13 |
14 | describe('thunk creators', () => {
15 | let store
16 | let mockAxios
17 |
18 | const initialState = {user: {}}
19 |
20 | beforeEach(() => {
21 | mockAxios = new MockAdapter(axios)
22 | store = mockStore(initialState)
23 | })
24 |
25 | afterEach(() => {
26 | mockAxios.restore()
27 | store.clearActions()
28 | })
29 |
30 | describe('me', () => {
31 | it('eventually dispatches the GET USER action', async () => {
32 | const fakeUser = {email: 'Cody'}
33 | mockAxios.onGet('/auth/me').replyOnce(200, fakeUser)
34 | await store.dispatch(me())
35 | const actions = store.getActions()
36 | expect(actions[0].type).to.be.equal('GET_USER')
37 | expect(actions[0].user).to.be.deep.equal(fakeUser)
38 | })
39 | })
40 |
41 | describe('logout', () => {
42 | it('logout: eventually dispatches the REMOVE_USER action', async () => {
43 | mockAxios.onPost('/auth/logout').replyOnce(204)
44 | await store.dispatch(logout())
45 | const actions = store.getActions()
46 | expect(actions[0].type).to.be.equal('REMOVE_USER')
47 | expect(history.location.pathname).to.be.equal('/login')
48 | })
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lighttext",
3 | "version": "1.0.0",
4 | "description": "Some boilerplate code to get you started - get shakin'!",
5 | "engines": {
6 | "node": ">= 7.0.0"
7 | },
8 | "main": "index.js",
9 | "scripts": {
10 | "build-client": "webpack",
11 | "build-client-watch": "webpack -w",
12 | "deploy": "script/deploy",
13 | "heroku-token": "script/encrypt-heroku-auth-token",
14 | "lint": "eslint ./ --ignore-path .gitignore",
15 | "lint-fix": "npm run lint -- --fix",
16 | "precommit": "lint-staged",
17 | "prepare": "if [ -d .git ]; then npm-merge-driver install; fi",
18 | "prettify": "prettier --write \"**/*.{js,jsx,json,css,scss,md}\"",
19 | "postinstall": "touch secrets.js",
20 | "seed": "node script/seed.js",
21 | "start": "node server",
22 | "start-dev": "NODE_ENV='development' npm run build-client-watch & NODE_ENV='development' npm run start-server",
23 | "start-server": "nodemon server -e html,js,scss --ignore public --ignore client",
24 | "test": "NODE_ENV='test' mocha \"./server/**/*.spec.js\" \"./client/**/*.spec.js\" \"./script/**/*.spec.js\" --require @babel/polyfill --require @babel/register"
25 | },
26 | "lint-staged": {
27 | "*.{js,jsx}": [
28 | "prettier --write",
29 | "git add"
30 | ],
31 | "*.{css,scss,json,md}": [
32 | "prettier --write",
33 | "git add"
34 | ]
35 | },
36 | "author": "Fullstack Academy of Code",
37 | "license": "MIT",
38 | "dependencies": {
39 | "@grpc/proto-loader": "^0.4.0",
40 | "axios": "^0.15.3",
41 | "chart.js": "^2.8.0",
42 | "compression": "^1.7.3",
43 | "connect-session-sequelize": "^4.1.0",
44 | "cron": "^1.7.0",
45 | "express": "^4.16.3",
46 | "express-session": "^1.15.1",
47 | "fs": "0.0.1-security",
48 | "grpc": "^1.19.0",
49 | "history": "^4.6.3",
50 | "moment": "^2.24.0",
51 | "morgan": "^1.9.1",
52 | "passport": "^0.3.2",
53 | "passport-google-oauth": "^1.0.0",
54 | "pg": "^6.1.2",
55 | "pg-hstore": "^2.3.2",
56 | "prop-types": "^15.6.2",
57 | "react": "^16.4.2",
58 | "react-chartjs-2": "^2.7.4",
59 | "react-dom": "^16.4.2",
60 | "react-moment": "^0.8.4",
61 | "react-redux": "^5.0.2",
62 | "react-router-dom": "^4.3.1",
63 | "react-search-field": "^1.0.0",
64 | "react-toast-notifications": "^1.3.1",
65 | "redux": "^3.6.0",
66 | "redux-logger": "^2.8.1",
67 | "redux-thunk": "^2.3.0",
68 | "semantic-ui-react": "^0.86.0",
69 | "sequelize": "^4.38.0",
70 | "socket.io": "^2.1.1",
71 | "twilio": "^3.29.1"
72 | },
73 | "devDependencies": {
74 | "@babel/core": "^7.3.4",
75 | "@babel/plugin-proposal-class-properties": "^7.3.4",
76 | "@babel/plugin-proposal-decorators": "7.0.0-beta.54",
77 | "@babel/plugin-proposal-export-namespace-from": "7.0.0-beta.54",
78 | "@babel/plugin-proposal-function-sent": "7.0.0-beta.54",
79 | "@babel/plugin-proposal-json-strings": "7.0.0-beta.54",
80 | "@babel/plugin-proposal-numeric-separator": "7.0.0-beta.54",
81 | "@babel/plugin-proposal-throw-expressions": "7.0.0-beta.54",
82 | "@babel/plugin-syntax-dynamic-import": "7.0.0-beta.54",
83 | "@babel/plugin-syntax-import-meta": "7.0.0-beta.54",
84 | "@babel/polyfill": "^7.0.0-beta.55",
85 | "@babel/preset-env": "^7.3.4",
86 | "@babel/preset-react": "^7.0.0-beta.55",
87 | "@babel/register": "^7.0.0-beta.55",
88 | "axios-mock-adapter": "^1.15.0",
89 | "babel-eslint": "^8.2.6",
90 | "babel-loader": "^8.0.0-beta.4",
91 | "chai": "^3.5.0",
92 | "enzyme": "^3.9.0",
93 | "enzyme-adapter-react-16": "^1.11.0",
94 | "eslint": "^4.19.1",
95 | "eslint-config-fullstack": "^5.1.0",
96 | "eslint-config-prettier": "^2.9.0",
97 | "eslint-plugin-react": "^7.10.0",
98 | "husky": "^0.14.3",
99 | "lint-staged": "^7.2.0",
100 | "mocha": "^5.2.0",
101 | "nodemon": "^1.18.3",
102 | "npm-merge-driver": "^2.3.5",
103 | "prettier": "1.11.1",
104 | "react-test-renderer": "^16.4.2",
105 | "redux-devtools-extension": "^2.13.5",
106 | "redux-mock-store": "^1.5.3",
107 | "supertest": "^3.1.0",
108 | "webpack": "^4.16.4",
109 | "webpack-cli": "^3.1.0"
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/LightText.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LightText
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/public/images/LightText_files/css:
--------------------------------------------------------------------------------
1 | /* latin */
2 | @font-face {
3 | font-family: 'Fredoka One';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: local('Fredoka One'), local('FredokaOne-Regular'), url(https://fonts.gstatic.com/s/fredokaone/v6/k3kUo8kEI-tA1RRcTZGmTlHGCaen8wf-.woff2) format('woff2');
7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
8 | }
9 |
--------------------------------------------------------------------------------
/public/images/LightText_files/css(1):
--------------------------------------------------------------------------------
1 | /* cyrillic-ext */
2 | @font-face {
3 | font-family: 'Montserrat';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: local('Montserrat Regular'), local('Montserrat-Regular'), url(https://fonts.gstatic.com/s/montserrat/v12/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2');
7 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
8 | }
9 | /* cyrillic */
10 | @font-face {
11 | font-family: 'Montserrat';
12 | font-style: normal;
13 | font-weight: 400;
14 | src: local('Montserrat Regular'), local('Montserrat-Regular'), url(https://fonts.gstatic.com/s/montserrat/v12/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2');
15 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
16 | }
17 | /* vietnamese */
18 | @font-face {
19 | font-family: 'Montserrat';
20 | font-style: normal;
21 | font-weight: 400;
22 | src: local('Montserrat Regular'), local('Montserrat-Regular'), url(https://fonts.gstatic.com/s/montserrat/v12/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2');
23 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
24 | }
25 | /* latin-ext */
26 | @font-face {
27 | font-family: 'Montserrat';
28 | font-style: normal;
29 | font-weight: 400;
30 | src: local('Montserrat Regular'), local('Montserrat-Regular'), url(https://fonts.gstatic.com/s/montserrat/v12/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2');
31 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
32 | }
33 | /* latin */
34 | @font-face {
35 | font-family: 'Montserrat';
36 | font-style: normal;
37 | font-weight: 400;
38 | src: local('Montserrat Regular'), local('Montserrat-Regular'), url(https://fonts.gstatic.com/s/montserrat/v12/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2');
39 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
40 | }
41 |
--------------------------------------------------------------------------------
/public/images/LightText_files/lightbulb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/LightText_files/lightbulb.png
--------------------------------------------------------------------------------
/public/images/LightText_files/lightbulbblack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/LightText_files/lightbulbblack.png
--------------------------------------------------------------------------------
/public/images/LightText_files/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | background-color: #f3f9fb;
4 | font-family: 'Montserrat', sans-serif;
5 | /* margin-left: 3%;
6 | margin-right: 3%; */
7 | }
8 | .navbar {
9 | /* background-color: #68bbe1; */
10 | background-color: white;
11 | /* padding-top: 20px; */
12 | /* overflow: hidden;
13 | position: fixed;
14 | top: 0;
15 | width: 100%; */
16 | }
17 |
18 | .imageLogo {
19 | width: 50px;
20 | height: 50px;
21 | }
22 |
23 | .container {
24 | display: flex;
25 | flex-direction: row;
26 | margin-left: 40px;
27 | }
28 |
29 | .title {
30 | flex: 0.5;
31 | margin-top: 20px;
32 | color: #68bbe1;
33 | font-family: 'Fredoka One', cursive;
34 | font-size: 2.5rem;
35 | }
36 |
37 | .link {
38 | font-size: 1.3rem;
39 | text-align: right;
40 | padding-bottom: 10px;
41 | }
42 | .links {
43 | color: #68bbe1;
44 | margin-right: 20px;
45 | }
46 |
47 | .balance {
48 | position: absolute;
49 | z-index: 100;
50 | text-align: center;
51 | left: 45%;
52 | top: 10%;
53 | }
54 |
55 | .transactions {
56 | position: absolute;
57 | z-index: 100;
58 | text-align: center;
59 | left: 45%;
60 | top: 20%;
61 | }
62 |
63 | #iphone {
64 | margin-top: 150px;
65 | position: relative;
66 | text-align: center;
67 | overflow: hidden;
68 | position: fixed; /* Set the navbar to fixed position */
69 | top: 0; /* Position the navbar at the top of the page */
70 | width: 100%; /* Full width */
71 | /* background-color: black; */
72 | }
73 | #iphoneImage {
74 | /* position: absolute; */
75 | text-align: center;
76 | left: 0;
77 | top: 0;
78 | }
79 |
80 | #welcome {
81 | position: absolute;
82 | z-index: 100;
83 | text-align: center;
84 | left: 45%;
85 | top: 6%;
86 | }
87 |
88 | #balance {
89 | position: absolute;
90 | z-index: 100;
91 | text-align: center;
92 | left: 42%;
93 | top: 20%;
94 | }
95 |
96 | #transactions {
97 | position: absolute;
98 | z-index: 100;
99 | text-align: center;
100 | left: 42%;
101 | top: 30%;
102 | }
103 |
104 | #wholePage {
105 | display: flex;
106 | justify-content: space-between;
107 | margin-top: 100px;
108 | margin-left: 50px;
109 | margin-right: 50px;
110 | }
111 | .instructions {
112 | margin-top: 40px;
113 | margin-right: 250px;
114 | }
115 | .instructions h3 {
116 | font-size: 40px;
117 | }
118 | .text {
119 | font-size: 17px;
120 | }
121 | .instructionSignUp {
122 | display: flex;
123 | flex-direction: column;
124 | justify-content: space-around;
125 | margin-left: 40px;
126 | }
127 | .signUp {
128 | margin-right: 350px;
129 | margin-top: 90px;
130 | }
131 | .iphoneHome {
132 | margin-top: 50px;
133 | align-items: left;
134 | }
135 |
136 | .userList {
137 | font-size: 10px;
138 | }
139 |
140 | .ui right labeled icon button {
141 | font-size: 40px;
142 | }
143 |
--------------------------------------------------------------------------------
/public/images/Sihem_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/Sihem_1.jpg
--------------------------------------------------------------------------------
/public/images/download-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/download-1.png
--------------------------------------------------------------------------------
/public/images/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/download.png
--------------------------------------------------------------------------------
/public/images/iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/iphone.png
--------------------------------------------------------------------------------
/public/images/lightText.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/lightText.gif
--------------------------------------------------------------------------------
/public/images/lightbulb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/lightbulb.png
--------------------------------------------------------------------------------
/public/images/lightbulbblack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/lightbulbblack.png
--------------------------------------------------------------------------------
/public/images/maurice.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/maurice.jpeg
--------------------------------------------------------------------------------
/public/images/milan.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/milan.jpeg
--------------------------------------------------------------------------------
/public/images/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/screen.png
--------------------------------------------------------------------------------
/public/images/screen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/screen2.png
--------------------------------------------------------------------------------
/public/images/screen3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/screen3.png
--------------------------------------------------------------------------------
/public/images/screen4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/screen4.png
--------------------------------------------------------------------------------
/public/images/tali.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/public/images/tali.jpeg
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | LightText
7 |
8 |
12 |
16 |
20 |
24 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/public/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | background-color: #f3f9fb;
4 | font-family: 'Montserrat', sans-serif;
5 | /* margin-left: 3%;
6 | margin-right: 3%; */
7 | }
8 | .navbar {
9 | /* background-color: #68bbe1; */
10 | background-color: white;
11 | height: 100px;
12 | /* padding-top: 0px; */
13 | /* overflow: hidden;
14 | position: fixed;
15 | top: 0;
16 | width: 100%; */
17 | }
18 | .hidden {
19 | display: none;
20 | }
21 | .imageLogo {
22 | width: 50px;
23 | height: 50px;
24 | }
25 |
26 | .container {
27 | display: flex;
28 | flex-direction: row;
29 | }
30 |
31 | .title {
32 | flex: 0.5;
33 | margin-top: 20px;
34 | margin-left: 5px;
35 | color: #68bbe1;
36 | font-family: 'Fredoka One', cursive;
37 | font-size: 2.5rem;
38 | }
39 |
40 | .link {
41 | font-size: 1.3rem;
42 | text-align: right;
43 | padding-bottom: 10px;
44 | }
45 | .links {
46 | color: #68bbe1;
47 | margin-right: 20px;
48 | }
49 |
50 | .balance {
51 | position: absolute;
52 | z-index: 100;
53 | text-align: center;
54 | left: 45%;
55 | top: 10%;
56 | }
57 |
58 | #wholePage {
59 | display: flex;
60 | /* flex-wrap: wrap; */
61 | justify-content: space-between;
62 | /* margin-top: 40px; */
63 | margin-left: 50px;
64 | margin-right: 50px;
65 | }
66 |
67 | .bottomDiv {
68 | /* display: flex;
69 | justify-content: space-between;
70 | /* flex-direction: column; */
71 | /* margin-top: 40px;
72 | margin-left: 90px;
73 | margin-right: 30px; */
74 | text-align: center;
75 | }
76 |
77 | .instructions {
78 | /* margin-top: 40px; */
79 | margin-right: 250px;
80 | width: 60%;
81 | text-align: justify;
82 | }
83 |
84 | .text {
85 | font-size: 1.1em;
86 | line-height: 1.5em;
87 | color: #0c2550;
88 | text-align: justify;
89 | }
90 | .instructionSignUp {
91 | display: flex;
92 | flex-direction: column;
93 | justify-content: space-around;
94 | margin-left: 40px;
95 | }
96 | .bitcoinHome {
97 | /* width: 50%; */
98 | margin-top: 90px;
99 | align-items: center;
100 | }
101 | .signUp {
102 | width: 50%;
103 | /* margin-left: 50px; */
104 | margin-top: 30px;
105 | }
106 | .iphoneHome {
107 | margin-top: 50px;
108 | /* align-items: left; */
109 | width: 40%;
110 | }
111 |
112 | .userList {
113 | font-size: 10px;
114 | }
115 |
116 | .transactionDiv {
117 | margin-top: 50px;
118 | }
119 | .col-lg-6 {
120 | background-color: #f3f9fb;
121 | padding: 20px;
122 | margin-top: 30px;
123 | align-content: center;
124 | }
125 |
126 | #btcInfo,
127 | #title,
128 | #transactionTable,
129 | #work,
130 | #lightning,
131 | #aboutTitle {
132 | font-size: 2.5em;
133 | font-family: 'Open Sans', sans-serif;
134 | font-family: 'Montserrat', sans-serif;
135 | font-family: 'Oswald', sans-serif;
136 | font-family: 'Lato', sans-serif;
137 | font-family: 'Roboto', sans-serif;
138 | font-family: 'Merriweather', serif;
139 | font-family: 'Bitter', serif;
140 | font-weight: bold;
141 | color: #0c2550;
142 | }
143 | #aboutTitle {
144 | text-align: center;
145 | }
146 | .aboutDiv {
147 | padding-top: 50px;
148 | margin-top: 200px;
149 | height: 550px;
150 | background-color: white;
151 | }
152 | #lightning {
153 | margin-top: 40px;
154 | }
155 | #transactionTable {
156 | margin-left: 20px;
157 | }
158 | h3 {
159 | margin-left: 30px;
160 | margin-bottom: 15px;
161 | font-size: 1.7em;
162 | color: #0c2550;
163 | }
164 |
165 | h4 {
166 | font-style: italic;
167 | font-size: 1.2em;
168 | color: #0c2550;
169 | }
170 | #update {
171 | margin-left: 40px;
172 | margin-top: 35px;
173 | font-size: 1.2em;
174 | font-style: italic;
175 | color: #0c2550;
176 | /* font-weight: bold; */
177 | }
178 |
179 | #today,
180 | #convert {
181 | margin-left: 25px;
182 | margin-top: 10px;
183 | font-size: 1.2em;
184 | color: #0c2550;
185 | }
186 | #findUsername,
187 | #beforeBox {
188 | margin-left: 25px;
189 | margin-top: 35px;
190 | font-size: 1.2em;
191 | color: #0c2550;
192 | }
193 | #convert {
194 | margin-left: 25px;
195 | margin-top: 25px;
196 | font-size: 1.2em;
197 | color: #0c2550;
198 | }
199 | .result {
200 | font-size: 1.7em;
201 | font-weight: bold;
202 | margin-top: 20px;
203 | margin-left: 80px;
204 | }
205 | .myform {
206 | width: 60%;
207 | height: 60%;
208 | margin: auto;
209 | }
210 | transaction {
211 | display: inline;
212 | }
213 |
214 | .container-fluid transaction {
215 | display: flex;
216 | flex-direction: row;
217 | justify-content: space-between;
218 | }
219 |
220 | form.message,
221 | form.username {
222 | text-align: right;
223 | }
224 | form.message .ui.input.focus {
225 | display: block;
226 | }
227 | form.message .ui.input.focus label {
228 | margin-right: 5px;
229 | }
230 | form.message .ui.button {
231 | margin: 5px 0 0;
232 | }
233 | .transactions table.ui.collapsing.table {
234 | width: 90%;
235 | margin: 0 auto;
236 | }
237 | .transactions table th.name {
238 | width: 40%;
239 | }
240 | .transactions table th.amount {
241 | width: 40%;
242 | }
243 | .transactions table th.date {
244 | width: 20%;
245 | }
246 | .transactions table .empty {
247 | text-align: center;
248 | color: #666666;
249 | font-style: italic;
250 | }
251 |
252 | .photo {
253 | margin: 60px auto 0;
254 | display: flex;
255 | justify-content: space-around;
256 | }
257 | .photo .student {
258 | width: 200px;
259 | text-align: center;
260 | }
261 | .pictures {
262 | margin: 0 auto;
263 | position: relative;
264 | width: 85px;
265 | height: 85px;
266 | border-radius: 100%;
267 | overflow: hidden;
268 | }
269 |
270 | .photo .pictures img {
271 | width: 100px;
272 | margin-left: -6px;
273 | margin-top: -3px;
274 | }
275 |
276 | .presentation {
277 | margin-top: 20px;
278 | }
279 | .presentation span {
280 | font-size: 1.4em;
281 | font-weight: bold;
282 | color: #0c2550;
283 | }
284 |
285 | .presentation p {
286 | font-size: 1.2em;
287 | text-align: center;
288 | /* font-weight: bold; */
289 | color: #0c2550;
290 | }
291 | .presentation .github img,
292 | .presentation .linkedin img {
293 | width: 16px;
294 | height: 16px;
295 | display: inline-block;
296 | margin-right: 5px;
297 | }
298 |
299 | .presentation .github a,
300 | .presentation .linkedin a {
301 | font-size: 1em;
302 | }
303 | /* .student {
304 | display: flex;
305 | justify-content: space-between;
306 | flex-direction: column;
307 | } */
308 |
309 | .fullstack {
310 | margin-top: 70px;
311 | font-size: 1.5em;
312 | text-align: center;
313 | color: #0c2550;
314 | }
315 | .faqs {
316 | margin: 50px;
317 | background-color: white;
318 | }
319 | .faqsTitle {
320 | margin-top: 80px;
321 | font-size: 3em;
322 | font-family: 'Open Sans', sans-serif;
323 | font-family: 'Montserrat', sans-serif;
324 | font-family: 'Oswald', sans-serif;
325 | font-family: 'Lato', sans-serif;
326 | font-family: 'Roboto', sans-serif;
327 | font-family: 'Merriweather', serif;
328 | font-family: 'Bitter', serif;
329 | font-weight: bold;
330 | color: #0c2550;
331 | text-align: center;
332 | }
333 |
334 | .bold {
335 | font-weight: bold;
336 | color: #0c2550;
337 | }
338 |
339 | .description {
340 | color: #0c2550;
341 | margin-left: 4px;
342 | text-align: justify;
343 | }
344 | .question {
345 | padding-top: 20px;
346 | color: #0c2550;
347 | font-size: 2em;
348 | }
349 |
350 | .chart {
351 | margin-top: 50px;
352 | margin-bottom: 50px;
353 | display: flex;
354 | justify-content: space-around;
355 | /* height: 200px;
356 | width: 100%;
357 | background-color: #e9e4e6;
358 | margin-left: auto; */
359 | }
360 |
361 | .lastDiv {
362 | margin-top: 30px;
363 | }
364 |
365 | #text {
366 | font-size: 2.5em;
367 | color: #0c2550;
368 | font-style: italic;
369 | font-weight: bold;
370 | }
371 | .cryptoEasy {
372 | margin-right: 150px;
373 | align-items: center;
374 | text-align: center;
375 | margin-top: 130px;
376 | width: 20%;
377 | }
378 | .centralDiv {
379 | display: flex;
380 | justify-content: space-between;
381 | }
382 |
383 | .footer {
384 | display: flex;
385 | justify-content: space-between;
386 | padding: 30px 50px;
387 | background: #f5f5f5;
388 | color: #ccc;
389 | }
390 |
391 | .tablegraphe {
392 | display: flex;
393 | justify-content: space-between;
394 | flex-direction: row;
395 | }
396 |
397 | .userInfo {
398 | width: 50%;
399 | }
400 |
401 | .chartOne {
402 | padding-top: 20px;
403 | padding-bottom: 40px;
404 | width: 60%;
405 | border: 3 solid black;
406 | background-color: #e7eaf6;
407 | }
408 |
--------------------------------------------------------------------------------
/script/deploy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Hello, welcome to a bash script.
4 |
5 | # This bash script deploys your boilermaker application.
6 |
7 | # On the terminal you run individual
8 | # bash commands, and this file strings a bunch of commands together.
9 |
10 | # The first line of this file, or the `hashbang`, tells the system to
11 | # execute the text of this file as a bash program.
12 |
13 | # We want this entire script to exit if any single line fails.
14 | # So we set the `-e` flag.
15 | set -e
16 |
17 | # If our deploy fails partway through we want to clean up after ourselves.
18 | # This next block is like a try/catch for our entire script.
19 |
20 | # We trap any program EXIT and run this function.
21 | # Whether the deploy succeeds or fails, we'll clean up the deploy branch.
22 |
23 | function cleanup_at_exit {
24 | # return to your master branch
25 | git checkout master
26 |
27 | # remove the deploy branch
28 | git branch -D deploy
29 | }
30 | trap cleanup_at_exit EXIT
31 |
32 | # checks out a new branch called "deploy". Note that the name "deploy" here isn't magical,
33 | # but it needs to match the name of the branch we specify when we push to our heroku remote.
34 | git checkout -b deploy
35 |
36 | # webpack will run in "production mode"
37 | webpack -p
38 |
39 | # "force" add the otherwise gitignored build files
40 | git add -f public/bundle.js public/bundle.js.map
41 |
42 | # create a commit, even if nothing changed
43 | git commit --allow-empty -m 'Deploying'
44 |
45 | # push your local "deploy" branch to the "master" branch on heroku
46 | git push --force heroku deploy:master
47 |
--------------------------------------------------------------------------------
/script/encrypt-heroku-auth-token:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | function cleanup_at_exit {
6 | # remove temporary key file
7 | rm tmp.pem
8 | }
9 |
10 | trap cleanup_at_exit EXIT
11 |
12 | # Get the repo name from the `origin` git remote.
13 | export REPO=$(git remote get-url origin | \
14 | sed 's/^.*[\/:]\([^\/:]*\)\/\([^\/]*\)$/\1\/\2/' | \
15 | sed 's/^\(.*\)\.git$/\1/'
16 | )
17 |
18 | # Get the Travis public key for this repo.
19 | export TRAVIS_KEY=$(curl --silent https://api.travis-ci.org/repos/$REPO/key)
20 |
21 | # Save the key to a file.
22 | node -e "console.log($TRAVIS_KEY['key'])" > tmp.pem
23 |
24 | # Generate a Heroku token, encrypt and encode it and print it in YAML.
25 | echo -n $(heroku auth:token) | \
26 | openssl rsautl -encrypt -pubin -inkey tmp.pem | \
27 | base64 | awk '{print " secure: "$1}'
28 |
--------------------------------------------------------------------------------
/script/seed.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const db = require('../server/db')
4 | const {User} = require('../server/db/models')
5 |
6 | async function seed() {
7 | await db.sync({force: true})
8 | console.log('db synced!')
9 |
10 | const users = await Promise.all([
11 | User.create({
12 | username: 'milan',
13 | email: 'milan@email.com',
14 | phone: '+17326158815',
15 | wallet: '1',
16 | balance: 229000,
17 | password: '123'
18 | }),
19 | User.create({
20 | username: 'maurice',
21 | email: 'mshalam04@gmail.com',
22 | phone: '+17328595701',
23 | wallet: '1',
24 | balance: 50000000,
25 | password: '12345678'
26 | }),
27 | User.create({
28 | username: 'tali',
29 | email: 'tali@email.com',
30 | phone: '+16463316367',
31 | wallet: '22',
32 | balance: 500000,
33 | password: '123'
34 | }),
35 | User.create({
36 | username: 'terry',
37 | email: 'terry@email.com',
38 | phone: '+13479259579',
39 | wallet: '22',
40 | balance: 50000,
41 | password: '123'
42 | })
43 | ])
44 |
45 | console.log(`seeded ${users.length} users`)
46 | console.log(`seeded successfully`)
47 | }
48 |
49 | // We've separated the `seed` function from the `runSeed` function.
50 | // This way we can isolate the error handling and exit trapping.
51 | // The `seed` function is concerned only with modifying the database.
52 | async function runSeed() {
53 | console.log('seeding...')
54 | try {
55 | await seed()
56 | } catch (err) {
57 | console.error(err)
58 | process.exitCode = 1
59 | } finally {
60 | console.log('closing db connection')
61 | await db.close()
62 | console.log('db connection closed')
63 | }
64 | }
65 |
66 | // Execute the `seed` function, IF we ran this module directly (`node seed`).
67 | // `Async` functions always return a promise, so we can use `catch` to handle
68 | // any errors that might occur inside of `seed`.
69 | if (module === require.main) {
70 | runSeed()
71 | }
72 |
73 | // we export the seed function for testing purposes (see `./seed.spec.js`)
74 | module.exports = seed
75 |
--------------------------------------------------------------------------------
/script/seed.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /* global describe beforeEach it */
3 |
4 | const seed = require('./seed')
5 |
6 | describe('seed script', () => {
7 | it('completes successfully', seed)
8 | })
9 |
--------------------------------------------------------------------------------
/server/Util/chart.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/server/Util/chart.js
--------------------------------------------------------------------------------
/server/api/admin.macaroon:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/server/api/admin.macaroon
--------------------------------------------------------------------------------
/server/api/crypto.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable handle-callback-err */
2 |
3 | var fs = require('fs')
4 | const request = require('request')
5 | const axios = require('axios')
6 | const {User} = require('../db/models')
7 |
8 | const basePort = 'https://127.0.0.1:8081'
9 | // const basePort = 'https://192.168.1.1:8080'
10 | const walletPassword = 'hello'
11 |
12 | const CronJob = require('cron').CronJob
13 | const macaroon = fs.readFileSync('server/api/admin.macaroon').toString('hex')
14 |
15 | const checkRefill = async address => {
16 | try {
17 | const {data} = await axios.get(
18 | `https://api.blockcypher.com/v1/btc/main/addrs/${address}/balance`
19 | )
20 | return data
21 | } catch (err) {
22 | console.log(
23 | 'The address provided is not yet recognized by BlockCypher. We will try again in 10 minutes.'
24 | )
25 | }
26 | }
27 |
28 | const refill = async (user, amount) => {
29 | let refillBalance = user.balance + amount
30 |
31 | let refilledUser = await User.update(
32 | {balance: refillBalance},
33 | {where: {id: user.id}}
34 | )
35 | console.log('REFILLED USER: ', refilledUser)
36 | }
37 |
38 | const startCron = (address, sender) => {
39 | console.log('Before job instantiation')
40 | const job = new CronJob('0 */10 * * * *', async function() {
41 | const d = new Date()
42 | console.log('Every Ten Minute:', d)
43 | let refillData = await checkRefill(address)
44 | console.log('REFILL DATA FROM BLOCKCYPHER: ', refillData.balance)
45 | console.log('ADDRESS INFORMATION IS: ', refillData)
46 | if (refillData.balance > 0) {
47 | refill(sender, refillData.balance)
48 | job.stop()
49 | }
50 | })
51 | console.log('After job instantiation')
52 | job.start()
53 | }
54 |
55 | const genSeed = () => {
56 | let options = {
57 | url: 'https://127.16.19.16:8080/v1/genseed',
58 | // Work-around for self-signed certificates.
59 | rejectUnauthorized: false,
60 | json: true
61 | }
62 |
63 | return request.get(options, function(error, response, body) {
64 | console.log(body)
65 | console.error(error)
66 | })
67 | }
68 |
69 | const initWallet = async password => {
70 | const seed = await genSeed()
71 | let requestBody = {
72 | wallet_password: password,
73 | cipher_seed_mnemonic: seed
74 | }
75 |
76 | let options = {
77 | url: 'https://127.16.19.16:8080/v1/initwallet',
78 | // Work-around for self-signed certificates.
79 | rejectUnauthorized: false,
80 | json: true,
81 | form: JSON.stringify(requestBody)
82 | }
83 |
84 | request.get(options, function(error, response, body) {
85 | console.log(body)
86 | console.error(error)
87 | })
88 | }
89 |
90 | const unlockwallet = (password, cb) => {
91 | let requestBody = {
92 | wallet_password: Buffer.from(password).toString('base64')
93 | }
94 | let options = {
95 | url: `${basePort}/v1/unlockwallet`,
96 | // Work-around for self-signed certificates.
97 | rejectUnauthorized: false,
98 | json: true,
99 | form: JSON.stringify(requestBody)
100 | }
101 | request.post(options, function(error, response, body) {
102 | console.log(body)
103 | setTimeout(() => {
104 | cb()
105 | }, 3000)
106 | })
107 | }
108 |
109 | const getinfo = () => {
110 | let options = {
111 | url: `${basePort}/v1/getinfo`,
112 | // Work-around for self-signed certificates.
113 | rejectUnauthorized: false,
114 | json: true,
115 | headers: {
116 | 'Grpc-Metadata-macaroon': macaroon
117 | }
118 | }
119 |
120 | request.get(options, function(error, response, body) {
121 | console.log(body)
122 | console.error(error)
123 | })
124 | }
125 |
126 | // newAddress() returns the a new Bitcoin address for refills
127 | const newAddress = async () => {
128 | let refillAddress = ''
129 | let options = {
130 | url: `${basePort}/v1/newaddress`,
131 | // Work-around for self-signed certificates.
132 | rejectUnauthorized: false,
133 | json: true,
134 | headers: {
135 | 'Grpc-Metadata-macaroon': macaroon
136 | },
137 | type: 'np2wkh'
138 | }
139 | let x = new Promise((resolve, reject) => {
140 | request.get(options, function(error, response, body) {
141 | if (error) reject(error)
142 | resolve(body.address)
143 | })
144 | })
145 | return x
146 | }
147 |
148 | // balance() returns the wallet balance
149 | const balance = () => {
150 | let options = {
151 | url: `${basePort}/v1/balance/blockchain`,
152 | // Work-around for self-signed certificates.
153 | rejectUnauthorized: false,
154 | json: true,
155 | headers: {
156 | 'Grpc-Metadata-macaroon': macaroon
157 | }
158 | }
159 | request.get(options, function(error, response, body) {
160 | console.log(body)
161 | })
162 | }
163 |
164 | // getPeers() lists all currently active peers
165 | const getPeers = () => {
166 | let options = {
167 | url: `${basePort}/v1/peers`,
168 | // Work-around for self-signed certificates.
169 | rejectUnauthorized: false,
170 | json: true,
171 | headers: {
172 | 'Grpc-Metadata-macaroon': macaroon
173 | }
174 | }
175 | request.get(options, function(error, response, body) {
176 | console.log(body)
177 | })
178 | }
179 |
180 | /*
181 | CONNECT NEEDS EDITING TO URL PATH
182 |
183 | */
184 | // connect() establishes a connection to remote peers
185 | const connect = addr => {
186 | let requestBody = {
187 | addr: addr,
188 | perm: true
189 | }
190 | let options = {
191 | url: `${basePort}/v1/newaddress`,
192 | // Work-around for self-signed certificates.
193 | rejectUnauthorized: false,
194 | json: true,
195 | headers: {
196 | 'Grpc-Metadata-macaroon': macaroon
197 | },
198 | form: JSON.stringify(requestBody)
199 | }
200 | request.post(options, function(error, response, body) {
201 | console.log(body)
202 | })
203 | }
204 |
205 | // disconnect() destorys a connection to a specified remote peer
206 | const disconnect = addr => {
207 | let options = {
208 | url: `${basePort}/v1/peers/${addr}`,
209 | // Work-around for self-signed certificates.
210 | rejectUnauthorized: false,
211 | json: true,
212 | headers: {
213 | 'Grpc-Metadata-macaroon': macaroon
214 | }
215 | }
216 | request.delete(options, function(error, response, body) {
217 | console.log(body)
218 | })
219 | }
220 |
221 | // listChannels() returns currently open channels with the node
222 | const listChannels = () => {
223 | let options = {
224 | url: `${basePort}/v1/channels`,
225 | // Work-around for self-signed certificates.
226 | rejectUnauthorized: false,
227 | json: true,
228 | headers: {
229 | 'Grpc-Metadata-macaroon': macaroon
230 | }
231 | }
232 | request.get(options, function(error, response, body) {
233 | console.log(body)
234 | })
235 | }
236 |
237 | // openChannel() opens a channel with the specified node
238 | const openChannel = (addr, amount) => {
239 | let requestBody = {
240 | node_pubkey: addr,
241 | local_funding_amount: amount
242 | }
243 | let options = {
244 | url: `${basePort}/v1/channels`,
245 | // Work-around for self-signed certificates.
246 | rejectUnauthorized: false,
247 | json: true,
248 | headers: {
249 | 'Grpc-Metadata-macaroon': macaroon
250 | },
251 | form: JSON.stringify(requestBody)
252 | }
253 | request.post(options, function(error, response, body) {
254 | console.log(body)
255 | })
256 | }
257 |
258 | // getInvoice() returns an invoice based on a payment hash - payRequest must be exactly 32 bytes
259 | const getInvoice = payRequest => {
260 | let options = {
261 | url: `${basePort}/v1/invoices/${payRequest}`,
262 | // requires payment hash in URL above
263 | // Work-around for self-signed certificates.
264 | rejectUnauthorized: false,
265 | json: true,
266 | headers: {
267 | 'Grpc-Metadata-macaroon': macaroon
268 | }
269 | }
270 | request.post(options, function(error, response, body) {
271 | console.log(body)
272 | })
273 | }
274 |
275 | const addInvoice = amount => {
276 | let requestBody = {
277 | value: amount
278 | }
279 |
280 | let options = {
281 | url: `${basePort}/v1/invoices`,
282 | // Work-around for self-signed certificates.
283 | rejectUnauthorized: false,
284 | json: true,
285 | headers: {
286 | 'Grpc-Metadata-macaroon': macaroon
287 | },
288 | form: JSON.stringify(requestBody)
289 | }
290 | request.post(options, function(error, response, body) {
291 | console.log(body)
292 | })
293 | }
294 |
295 | // sendPayment() uses the invoice payment request to send a payment
296 | const sendPayment = async invoice => {
297 | let requestBody = {
298 | payment_request: invoice
299 | }
300 |
301 | let options = {
302 | url: `${basePort}/v1/channels/transactions`,
303 | // Work-around for self-signed certificates.
304 | rejectUnauthorized: false,
305 | json: true,
306 | headers: {
307 | 'Grpc-Metadata-macaroon': macaroon
308 | },
309 | form: JSON.stringify(requestBody)
310 | }
311 | let x = new Promise((resolve, reject) => {
312 | request.post(options, function(error, response, body) {
313 | if (error) reject(error)
314 | console.log('SENDPAYMENT BODY: ', body)
315 | resolve(body)
316 | })
317 | })
318 | return x
319 | }
320 |
321 | module.exports = {
322 | checkRefill,
323 | genSeed,
324 | initWallet,
325 | unlockwallet,
326 | getinfo,
327 | newAddress,
328 | balance,
329 | getPeers,
330 | connect,
331 | disconnect,
332 | openChannel,
333 | listChannels,
334 | getInvoice,
335 | addInvoice,
336 | sendPayment,
337 | startCron
338 | }
339 |
--------------------------------------------------------------------------------
/server/api/index.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | module.exports = router
3 |
4 | router.use('/users', require('./users'))
5 | router.use('/sms', require('./receive_sms'))
6 |
7 | router.use('/lightning', require('./lightning'))
8 | router.use('/transactions', require('./transactions'))
9 |
10 | router.use((req, res, next) => {
11 | const error = new Error('Not Found')
12 | error.status = 404
13 | next(error)
14 | })
15 |
--------------------------------------------------------------------------------
/server/api/lightning.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const axios = require('axios')
3 | const {crypto} = require('./crypto')
4 |
5 | module.exports = router
6 |
7 | // create, unlockwallet, getinfo, balance/blockchain, genseed, initwallet, newaddress
8 | // channels/transactions
9 | // connect, getpeers, openchannel,
10 |
11 | router.get('/genseed', (req, res, next) => {
12 | const password = req.body.password
13 | var body = {}
14 | // '/v1/genseed'
15 | create(body)
16 | })
17 |
18 | router.get('/create', (req, res, next) => {
19 | const x = req.body
20 | var body = {x}
21 | create(body)
22 | })
23 |
24 | // done
25 | router.get('/unlockwallet', (req, res, next) => {})
26 |
27 | //done
28 | router.get('/getinfo', (req, res, next) => {})
29 |
30 | // done
31 | router.get('/newaddress', (req, res, next) => {})
32 |
33 | // done
34 | router.get('/balance', (req, res, next) => {
35 | // '/balance/blockchain'
36 | })
37 |
38 | // done
39 | router.get('/connect', (req, res, next) => {})
40 |
41 | // done
42 | router.get('/getpeers', (req, res, next) => {})
43 |
44 | // done
45 | router.get('/openchannel', (req, res, next) => {})
46 |
47 | //done
48 | router.get('/listchannels', (req, res, next) => {})
49 |
50 | // done
51 | router.get('/addinvoice', (req, res, next) => {
52 | // '/v1/invoices'
53 | })
54 |
55 | //done
56 | router.get('/sendpayment', (req, res, next) => {
57 | // '/v1/channels/transactions'
58 | })
59 |
--------------------------------------------------------------------------------
/server/api/promise.js:
--------------------------------------------------------------------------------
1 | let fs = require('fs')
2 |
3 | const promisify = () => {
4 | let x = new Promise((resolve, reject) => {
5 | fs.readFile('./data.txt', (err, data) => {
6 | if (err) {
7 | reject(err)
8 | }
9 | resolve(data)
10 | })
11 | })
12 |
13 | return x
14 | }
15 |
16 | module.exports = promisify
17 |
--------------------------------------------------------------------------------
/server/api/receive_sms.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | const router = require('express').Router()
4 | const axios = require('axios')
5 | const MessagingResponse = require('twilio').twiml.MessagingResponse
6 | const client = require('twilio')(
7 | process.env.twilioSid,
8 | process.env.twilioAuthToken
9 | )
10 | const {User} = require('../db/models')
11 | const {
12 | checkRefill,
13 | genSeed,
14 | initWallet,
15 | unlockwallet,
16 | getinfo,
17 | newAddress,
18 | balance,
19 | getPeers,
20 | connect,
21 | disconnect,
22 | openChannel,
23 | listChannels,
24 | addInvoice,
25 | sendPayment,
26 | startCron
27 | } = require('./crypto')
28 | const {Transactions} = require('../db/models')
29 |
30 | const twilioPhone = process.env.twilionumber
31 |
32 | module.exports = router
33 |
34 | const sendMessage = (phone, body) => {
35 | client.messages
36 | .create({
37 | body: body,
38 | from: twilioPhone,
39 | to: phone
40 | })
41 | .then(message => console.log('TWILIO MSG ID: ', message.sid))
42 | }
43 |
44 | const findUserByPhone = async phone => {
45 | try {
46 | const findUser = await User.findOne({
47 | where: {phone: phone}
48 | })
49 |
50 | return null || findUser
51 | } catch (err) {
52 | throw new Error(err)
53 | }
54 | }
55 |
56 | const findUserByUsername = async userName => {
57 | try {
58 | const findUser = await User.findOne({
59 | where: {username: userName}
60 | })
61 |
62 | if (!findUser) return null
63 | else {
64 | return {
65 | userName: findUser.dataValues.username,
66 | number: findUser.dataValues.phone,
67 | userId: findUser.dataValues['id'],
68 | balance: findUser.dataValues.balance
69 | }
70 | }
71 | } catch (error) {
72 | throw new Error(error)
73 | }
74 | }
75 |
76 | const getBalance = async phone => {
77 | try {
78 | const findUser = await User.findOne({
79 | where: {phone: phone}
80 | })
81 | if (!findUser) console.log('This user does not exist: ', findUser)
82 | return findUser.dataValues.balance
83 | } catch (err) {
84 | throw new Error(err)
85 | }
86 | }
87 |
88 | const getCurrencies = async () => {
89 | try {
90 | return await axios.get('https://api.coindesk.com/v1/bpi/currentprice.json')
91 | } catch (error) {
92 | console.error(error)
93 | }
94 | }
95 |
96 | const getBody = message => {
97 | let msg = message.split(' ')
98 | return msg.filter(word => {
99 | return word !== ' ' && word.length > 0
100 | })
101 | }
102 |
103 | const updatBalances = async (sender, receiver, amount) => {
104 | //console.log('sender: ', sender.id, ' receiver: ', receiver)
105 |
106 | let senderBal = Number(sender.balance) - Number(amount)
107 | let receiverBal = Number(receiver.balance) + Number(amount)
108 |
109 | //subtract from user
110 | let senderUpdated = await User.update(
111 | {balance: senderBal},
112 | {where: {id: sender.id}}
113 | )
114 |
115 | //add to reciever
116 | let receiverUpdated = User.update(
117 | {balance: receiverBal},
118 | {where: {id: receiver.userId}}
119 | )
120 | }
121 |
122 | const subtract = async (user, amount) => {
123 | let refillBalance = user.balance - amount
124 |
125 | let refilledUser = await User.update(
126 | {balance: refillBalance},
127 | {where: {id: user.id}}
128 | )
129 | }
130 |
131 | router.post('/', async (req, res, next) => {
132 | let body,
133 | action,
134 | amount,
135 | receiverPhone,
136 | senderPhone,
137 | webUserName,
138 | messageFromWeb,
139 | toastMessage
140 |
141 | senderPhone = req.user ? req.user.phone : ''
142 | if (req.body.messages) {
143 | messageFromWeb = await findUserByUsername(
144 | getBody(req.body.messages.toLowerCase())[2]
145 | )
146 | body = getBody(req.body.messages.toLowerCase())
147 | action = body[0].toLowerCase()
148 | amount = body[1]
149 | if (body.length !== 1) {
150 | if (!messageFromWeb) {
151 | toastMessage =
152 | 'The user you are trying to pay is not registered with us. Please try another user.'
153 | res.send(toastMessage)
154 | } else {
155 | receiverPhone = messageFromWeb.number
156 | webUserName = messageFromWeb.userName
157 | }
158 | }
159 | } else {
160 | body = getBody(req.body.Body.toLowerCase())
161 | action = body[0].toLowerCase()
162 | amount = body[1]
163 | receiverPhone = body[2]
164 | senderPhone = req.body.From
165 | }
166 |
167 | try {
168 | const twiml = new MessagingResponse()
169 | twiml.message(req.body.message)
170 |
171 | let ourReceiver = await findUserByUsername(receiverPhone)
172 |
173 | if (ourReceiver === null) {
174 | ourReceiver = {username: false}
175 | }
176 |
177 | const sender = (await findUserByPhone(senderPhone)) || 'undefined'
178 |
179 | const receiver =
180 | (await findUserByPhone(receiverPhone)) ||
181 | (await ourReceiver.userName) ||
182 | 'undefined'
183 |
184 | const balance = await getBalance(senderPhone)
185 | const hasSufficientFunds = balance >= amount
186 | const converterUSD = await getCurrencies()
187 | const usdRate = converterUSD.data.bpi.USD.rate_float
188 | const balanceBTC = balance / 100000000
189 | const balanceUSD = balance * usdRate / 100000000
190 | let paymentHash = 'unavailable'
191 |
192 | const messages = {
193 | helpme: `'BALANCE': checks your balance. \n 'SEND': sends a transaction with 'Amt in satoshis' 'Recipient phone number or username' \n Examples:\n 'SEND 300 +11234567890' \n or 'SEND 300 Maurice' \n 'REFILL': generates a BTC address to refill to \n 'PAYINVOICE': pay any LN invoice with 'PAYINVOICE lnbcXXX...'`,
194 | balance: `Your lightning balance is ${balance} satoshis ($${balanceUSD.toFixed(
195 | 2
196 | )} USD, ${balanceBTC} BTC).`,
197 | signup:
198 | 'You are not registered with LightText. Please go to LightText.io to signup.',
199 | receiver:
200 | 'The user you are trying to pay is not registered with us. Please try another user.',
201 | insufficientBalance:
202 | 'You have insufficient funds. Please enter REFILL to up your funding.',
203 | sent: `Boom. You made a lightning fast payment to ${ourReceiver.userName ||
204 | webUserName} for ${amount} satoshis`,
205 | received: `Boom. You received a lightning fast payment for ${amount} satoshis from ${
206 | sender.username
207 | }`,
208 | refill:
209 | "Lightning Network is in beta, please don't send more than $20 to the following address:",
210 | negativeAmount: 'You can only send positive amounts',
211 | notANumber:
212 | 'You need to enter a valid amount in order to make payments. Example SEND 300 +11234567890',
213 | fractionAmount: `You can't send fractional satoshis. please send a valid amount`,
214 | payinvoice: `Boom. You have successfully paid. Your payment hopped `
215 | }
216 |
217 | if (!sender) {
218 | toastMessage = messages.signup
219 | sendMessage(senderPhone, messages.signup)
220 | } else {
221 | switch (action) {
222 | case 'refill':
223 | let address = await newAddress()
224 | startCron(address, sender)
225 | setTimeout(() => {
226 | toastMessage = address
227 | return sendMessage(senderPhone, address)
228 | }, 400)
229 | toastMessage = messages.refill
230 | sendMessage(senderPhone, messages.refill)
231 | break
232 | case 'balance': {
233 | toastMessage = messages.balance
234 | sendMessage(senderPhone, messages.balance)
235 | break
236 | }
237 | case 'helpme':
238 | toastMessage = messages.helpme
239 | sendMessage(senderPhone, messages.helpme)
240 | break
241 | case 'payinvoice':
242 | toastMessage = messages.payinvoice
243 | let payConfirmation = await sendPayment(amount)
244 | await subtract(sender, payConfirmation.payment_route.total_amt)
245 | let newBalance = await getBalance(senderPhone)
246 | paymentHash = payConfirmation.payment_preimage
247 | let totalFees = payConfirmation.payment_route.total_fees || 0
248 | let hops = payConfirmation.payment_route.hops.length
249 | sendMessage(
250 | senderPhone,
251 | messages.payinvoice +
252 | hops +
253 | ' times for a fee of ' +
254 | totalFees +
255 | ' satoshis.' +
256 | ' Your payment hash is: ' +
257 | paymentHash
258 | )
259 | break
260 | case 'send':
261 | if (receiver === 'undefined') {
262 | console.log(receiver, 'this is')
263 | toastMessage = messages.receiver
264 | sendMessage(senderPhone, messages.receiver)
265 | break
266 | }
267 | if (isNaN(amount)) {
268 | toastMessage = messages.notANumber
269 | sendMessage(senderPhone, messages.notANumber)
270 | break
271 | }
272 | if (!hasSufficientFunds) {
273 | toastMessage = messages.insufficientBalance
274 | sendMessage(senderPhone, messages.insufficientBalance)
275 | break
276 | }
277 | if (amount <= 0) {
278 | toastMessage = messages.negativeAmount
279 | sendMessage(senderPhone, messages.negativeAmount)
280 | break
281 | }
282 | if (!(amount % 1 === 0)) {
283 | sendMessage(senderPhone, messages.fractionAmount)
284 | break
285 | }
286 | //update the balances
287 | updatBalances(sender, ourReceiver, amount)
288 |
289 | toastMessage = messages.sent
290 | // CRYPTO.sendPayment(invoice) .then(sendMessage()) - need to update the message object to reflect an invoice number
291 | sendMessage(senderPhone, messages.sent)
292 |
293 | sendMessage(ourReceiver.number || receiverPhone, messages.received)
294 | Transactions.create({
295 | amount: amount,
296 | receiverId: ourReceiver.userId || messageFromWeb.userId,
297 | senderId: sender['id']
298 | })
299 | break
300 | // NEED a new case payinvoice that checks the same parameters as SEND except calls the sendPayment() function and returns the paymenthash for confirmation
301 | default:
302 | sendMessage(senderPhone, messages.helpme)
303 | }
304 | }
305 | res.send(toastMessage)
306 | toastMessage = 'default'
307 | // res.writeHead(200, {'Content-Type': 'text/xml'})
308 | res.end(twiml.toString())
309 | } catch (err) {
310 | console.error(err)
311 | next(err)
312 | }
313 | })
314 |
--------------------------------------------------------------------------------
/server/api/send_sms.js:
--------------------------------------------------------------------------------
1 | const client = require('twilio')(
2 | process.env.twilioSid,
3 | process.env.twilioAuthToken
4 | )
5 |
6 | client.messages
7 | .create({
8 | body: 'This is the ship that made the Kessel Run in fourteen parsecs?',
9 | from: '+18482202516',
10 | to: '+17328595701'
11 | })
12 | .then(message => console.log(message.sid))
13 |
--------------------------------------------------------------------------------
/server/api/testnet/admin.macaroon:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-text/lightText/53d0220d902ffb46be34398c97d9f2168537c753/server/api/testnet/admin.macaroon
--------------------------------------------------------------------------------
/server/api/testnet/base64Admin.macaroon:
--------------------------------------------------------------------------------
1 | L2hvbWUvbWlsYW5wYXRlbC9Eb2N1bWVudHMvQ2Fwc3RvbmUvbGlnaHRUZXh0L3NlcnZlci9hcGkv
2 | dGVzdG5ldC9hZG1pbi5tYWNhcm9vbgo=
--------------------------------------------------------------------------------
/server/api/testnet/base64tls.cert:
--------------------------------------------------------------------------------
1 | L2hvbWUvbWlsYW5wYXRlbC9Eb2N1bWVudHMvQ2Fwc3RvbmUvbGlnaHRUZXh0L3NlcnZlci9hcGkv
2 | dGVzdG5ldC90c2wuY2VydAo=
--------------------------------------------------------------------------------
/server/api/testnet/tls (1).cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICnjCCAkOgAwIBAgIRAMDYfj+wm8C1nBiH1uEruKAwCgYIKoZIzj0EAwIwNDEf
3 | MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDERMA8GA1UEAxMITWlsYW4t
4 | UEMwHhcNMTkwMzI0MDM1OTMyWhcNMjAwNTE4MDM1OTMyWjA0MR8wHQYDVQQKExZs
5 | bmQgYXV0b2dlbmVyYXRlZCBjZXJ0MREwDwYDVQQDEwhNaWxhbi1QQzBZMBMGByqG
6 | SM49AgEGCCqGSM49AwEHA0IABFoQQmjGjxELwC61qgpdW80wM63Q1s51lCrVDso9
7 | OwvyLte1X7Q17UVXAxZJewsPvwida4g5ABSP7GiPa0cib4qjggE0MIIBMDAOBgNV
8 | HQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zCCAQsGA1UdEQSCAQIwgf+CCE1p
9 | bGFuLVBDgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSHBH8AAAGHEAAAAAAA
10 | AAAAAAAAAAAAAAGHEP6AAAAAAAAA0ZfWkOPgBoiHBKn+BoiHEP6AAAAAAAAAGMW1
11 | JeplNkeHBKn+NkeHECYEIAAUhABwAAC28MLphZ2HECYEIAAUhABwIXH9tkwVqfqH
12 | ECYEIAAUhABw7V5TCBkhCQ2HEP6AAAAAAAAAIXH9tkwVqfqHBMCoAd+HEP6AAAAA
13 | AAAACW0ACFfA9K6HBKn+9K6HEP6AAAAAAAAAAABe/sCoAd+HEP6AAAAAAAAAAOAA
14 | AAAAAACHBMCoAQEwCgYIKoZIzj0EAwIDSQAwRgIhAMEXR5r+TApignTCOEfCEbqF
15 | vxL5os2wawhdMsyS2yyTAiEA21j0M8XqJlHjE80Hz7XHiDLLejrCatDeOv+1FdFh
16 | H/U=
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/server/api/testnet/tls (1).key:
--------------------------------------------------------------------------------
1 | -----BEGIN EC PRIVATE KEY-----
2 | MHcCAQEEIPTJ4228w64tRw5GgWRkVGmR8uh6EHUp3xKibgkc1yyeoAoGCCqGSM49
3 | AwEHoUQDQgAE30NW8iWbSH2731bYXHNxAbSmnfRVjgfmHnIKG4hnomDyhoaebhDv
4 | 6fIpTyaFxJwVe+i71ZrNIRTSmB1YZ/Bkjg==
5 | -----END EC PRIVATE KEY-----
6 |
--------------------------------------------------------------------------------
/server/api/testnet/tls (2).cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICuTCCAl6gAwIBAgIQdq3A847unx1bgtoQs0RTxjAKBggqhkjOPQQDAjA0MR8w
3 | HQYDVQQKExZsbmQgYXV0b2dlbmVyYXRlZCBjZXJ0MREwDwYDVQQDEwhNaWxhbi1Q
4 | QzAeFw0xOTAzMjQwNDA2MDRaFw0yMDA1MTgwNDA2MDRaMDQxHzAdBgNVBAoTFmxu
5 | ZCBhdXRvZ2VuZXJhdGVkIGNlcnQxETAPBgNVBAMTCE1pbGFuLVBDMFkwEwYHKoZI
6 | zj0CAQYIKoZIzj0DAQcDQgAETe9/vu/INugrFsZFlCCbR+JVNqgaTVRfGtC4iT1U
7 | 4Z0FT4j0iGIJtuPWoxIcV6+mZ4zofzuLWJ+3OxglZVIb1aOCAVAwggFMMA4GA1Ud
8 | DwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MIIBJwYDVR0RBIIBHjCCARqCCE1p
9 | bGFuLVBDgglsb2NhbGhvc3SCGWh0dHBzOi8vZDg5MzJmMzMubmdyb2suaW+CBHVu
10 | aXiCCnVuaXhwYWNrZXSHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHEP6AAAAAAAAA
11 | 0ZfWkOPgBoiHBKn+BoiHEP6AAAAAAAAAGMW1JeplNkeHBKn+NkeHECYEIAAUhABw
12 | AAC28MLphZ2HECYEIAAUhABwIXH9tkwVqfqHECYEIAAUhABw7V5TCBkhCQ2HEP6A
13 | AAAAAAAAIXH9tkwVqfqHBMCoAd+HEP6AAAAAAAAACW0ACFfA9K6HBKn+9K6HEP6A
14 | AAAAAAAAAABe/sCoAd+HEP6AAAAAAAAAAOAAAAAAAACHBMCoAQEwCgYIKoZIzj0E
15 | AwIDSQAwRgIhAMu6pCUluFSMghvKxB+yLtCsRbSeeio3rtqeNtfj4uiSAiEA8mfz
16 | ytnz5AVvm7t9ULat7VM1raEl+d/EbpoSo76rSlY=
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/server/api/testnet/tls (2).key:
--------------------------------------------------------------------------------
1 | -----BEGIN EC PRIVATE KEY-----
2 | MHcCAQEEIPTJ4228w64tRw5GgWRkVGmR8uh6EHUp3xKibgkc1yyeoAoGCCqGSM49
3 | AwEHoUQDQgAE30NW8iWbSH2731bYXHNxAbSmnfRVjgfmHnIKG4hnomDyhoaebhDv
4 | 6fIpTyaFxJwVe+i71ZrNIRTSmB1YZ/Bkjg==
5 | -----END EC PRIVATE KEY-----
6 |
--------------------------------------------------------------------------------
/server/api/testnet/tls (3).cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICsTCCAligAwIBAgIQC4WtI6QhxkyVorR6Ulcw1jAKBggqhkjOPQQDAjA0MR8w
3 | HQYDVQQKExZsbmQgYXV0b2dlbmVyYXRlZCBjZXJ0MREwDwYDVQQDEwhNaWxhbi1Q
4 | QzAeFw0xOTAzMjQwNDEyMThaFw0yMDA1MTgwNDEyMThaMDQxHzAdBgNVBAoTFmxu
5 | ZCBhdXRvZ2VuZXJhdGVkIGNlcnQxETAPBgNVBAMTCE1pbGFuLVBDMFkwEwYHKoZI
6 | zj0CAQYIKoZIzj0DAQcDQgAEEeRo8HL0YJxnLUcGRlZ5NLO3Yt6x8etAcS7sJYxC
7 | oTN+mqXmK9ILqaSNVUMigzeKHXIJ8RHYY0O5Z6YF46kpdKOCAUowggFGMA4GA1Ud
8 | DwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MIIBIQYDVR0RBIIBGDCCARSCCE1p
9 | bGFuLVBDgglsb2NhbGhvc3SCGWh0dHBzOi8vZDg5MzJmMzMubmdyb2suaW+CBHVu
10 | aXiCCnVuaXhwYWNrZXSHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHEP6AAAAAAAAA
11 | 0ZfWkOPgBoiHBKn+BoiHEP6AAAAAAAAAGMW1JeplNkeHBKn+NkeHECYEIAAUhABw
12 | AAC28MLphZ2HECYEIAAUhABwIXH9tkwVqfqHECYEIAAUhABw7V5TCBkhCQ2HEP6A
13 | AAAAAAAAIXH9tkwVqfqHBMCoAd+HEP6AAAAAAAAACW0ACFfA9K6HBKn+9K6HEP6A
14 | AAAAAAAAAABe/sCoAd+HEP6AAAAAAAAAAOAAAAAAAAAwCgYIKoZIzj0EAwIDRwAw
15 | RAIgIlWcFzHwNMSACY4sk5WSc/Zv2E9F4ezwDKE9I81sRjMCIAEMZEwxcy58QLzp
16 | Y9nKjYRrB2CTmubBRlQ0MFmxPrlS
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/server/api/testnet/tls (4).cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICljCCAjygAwIBAgIRAKgz0qFLtI4a7bSBBmtj9+kwCgYIKoZIzj0EAwIwNDEf
3 | MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDERMA8GA1UEAxMITWlsYW4t
4 | UEMwHhcNMTkwMzI0MDUwMjMwWhcNMjAwNTE4MDUwMjMwWjA0MR8wHQYDVQQKExZs
5 | bmQgYXV0b2dlbmVyYXRlZCBjZXJ0MREwDwYDVQQDEwhNaWxhbi1QQzBZMBMGByqG
6 | SM49AgEGCCqGSM49AwEHA0IABN9DVvIlm0h9u99W2FxzcQG0pp30VY4H5h5yChuI
7 | Z6Jg8oaGnm4Q7+nyKU8mhcScFXvou9WazSEU0pgdWGfwZI6jggEtMIIBKTAOBgNV
8 | HQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zCCAQQGA1UdEQSB/DCB+YIITWls
9 | YW4tUEOCCWxvY2FsaG9zdIIEdW5peIIKdW5peHBhY2tldIcEfwAAAYcQAAAAAAAA
10 | AAAAAAAAAAAAAYcQ/oAAAAAAAADRl9aQ4+AGiIcEqf4GiIcQ/oAAAAAAAAAYxbUl
11 | 6mU2R4cEqf42R4cQJgQgABSEAHAAALbwwumFnYcQJgQgABSEAHAhcf22TBWp+ocQ
12 | JgQgABSEAHDtXlMIGSEJDYcQ/oAAAAAAAAAhcf22TBWp+ocEwKgB34cQ/oAAAAAA
13 | AAAJbQAIV8D0rocEqf70rocQ/oAAAAAAAAAAAF7+wKgB34cQ/oAAAAAAAAAA4AAA
14 | AAAAADAKBggqhkjOPQQDAgNIADBFAiA1BIkq7IlruWTfzJBHYc0Sdgv9EL2JKRBo
15 | TCXOQMgZfQIhAIxPnb274aGcTIypIfQbPOZubjRyh9VQpZd0/QpmOTUV
16 | -----END CERTIFICATE-----
17 |
--------------------------------------------------------------------------------
/server/api/testnet/tls (5).cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICljCCAjygAwIBAgIRAKgz0qFLtI4a7bSBBmtj9+kwCgYIKoZIzj0EAwIwNDEf
3 | MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDERMA8GA1UEAxMITWlsYW4t
4 | UEMwHhcNMTkwMzI0MDUwMjMwWhcNMjAwNTE4MDUwMjMwWjA0MR8wHQYDVQQKExZs
5 | bmQgYXV0b2dlbmVyYXRlZCBjZXJ0MREwDwYDVQQDEwhNaWxhbi1QQzBZMBMGByqG
6 | SM49AgEGCCqGSM49AwEHA0IABN9DVvIlm0h9u99W2FxzcQG0pp30VY4H5h5yChuI
7 | Z6Jg8oaGnm4Q7+nyKU8mhcScFXvou9WazSEU0pgdWGfwZI6jggEtMIIBKTAOBgNV
8 | HQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zCCAQQGA1UdEQSB/DCB+YIITWls
9 | YW4tUEOCCWxvY2FsaG9zdIIEdW5peIIKdW5peHBhY2tldIcEfwAAAYcQAAAAAAAA
10 | AAAAAAAAAAAAAYcQ/oAAAAAAAADRl9aQ4+AGiIcEqf4GiIcQ/oAAAAAAAAAYxbUl
11 | 6mU2R4cEqf42R4cQJgQgABSEAHAAALbwwumFnYcQJgQgABSEAHAhcf22TBWp+ocQ
12 | JgQgABSEAHDtXlMIGSEJDYcQ/oAAAAAAAAAhcf22TBWp+ocEwKgB34cQ/oAAAAAA
13 | AAAJbQAIV8D0rocEqf70rocQ/oAAAAAAAAAAAF7+wKgB34cQ/oAAAAAAAAAA4AAA
14 | AAAAADAKBggqhkjOPQQDAgNIADBFAiA1BIkq7IlruWTfzJBHYc0Sdgv9EL2JKRBo
15 | TCXOQMgZfQIhAIxPnb274aGcTIypIfQbPOZubjRyh9VQpZd0/QpmOTUV
16 | -----END CERTIFICATE-----
17 |
--------------------------------------------------------------------------------
/server/api/testnet/tls (6).cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICljCCAjygAwIBAgIRAKgz0qFLtI4a7bSBBmtj9+kwCgYIKoZIzj0EAwIwNDEf
3 | MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDERMA8GA1UEAxMITWlsYW4t
4 | UEMwHhcNMTkwMzI0MDUwMjMwWhcNMjAwNTE4MDUwMjMwWjA0MR8wHQYDVQQKExZs
5 | bmQgYXV0b2dlbmVyYXRlZCBjZXJ0MREwDwYDVQQDEwhNaWxhbi1QQzBZMBMGByqG
6 | SM49AgEGCCqGSM49AwEHA0IABN9DVvIlm0h9u99W2FxzcQG0pp30VY4H5h5yChuI
7 | Z6Jg8oaGnm4Q7+nyKU8mhcScFXvou9WazSEU0pgdWGfwZI6jggEtMIIBKTAOBgNV
8 | HQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zCCAQQGA1UdEQSB/DCB+YIITWls
9 | YW4tUEOCCWxvY2FsaG9zdIIEdW5peIIKdW5peHBhY2tldIcEfwAAAYcQAAAAAAAA
10 | AAAAAAAAAAAAAYcQ/oAAAAAAAADRl9aQ4+AGiIcEqf4GiIcQ/oAAAAAAAAAYxbUl
11 | 6mU2R4cEqf42R4cQJgQgABSEAHAAALbwwumFnYcQJgQgABSEAHAhcf22TBWp+ocQ
12 | JgQgABSEAHDtXlMIGSEJDYcQ/oAAAAAAAAAhcf22TBWp+ocEwKgB34cQ/oAAAAAA
13 | AAAJbQAIV8D0rocEqf70rocQ/oAAAAAAAAAAAF7+wKgB34cQ/oAAAAAAAAAA4AAA
14 | AAAAADAKBggqhkjOPQQDAgNIADBFAiA1BIkq7IlruWTfzJBHYc0Sdgv9EL2JKRBo
15 | TCXOQMgZfQIhAIxPnb274aGcTIypIfQbPOZubjRyh9VQpZd0/QpmOTUV
16 | -----END CERTIFICATE-----
17 |
--------------------------------------------------------------------------------
/server/api/testnet/tls.cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICnjCCAkOgAwIBAgIRAIVqysFqvEo96w6iQEN7WScwCgYIKoZIzj0EAwIwNDEf
3 | MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDERMA8GA1UEAxMITWlsYW4t
4 | UEMwHhcNMTkwMzI0MDE0MzUwWhcNMjAwNTE4MDE0MzUwWjA0MR8wHQYDVQQKExZs
5 | bmQgYXV0b2dlbmVyYXRlZCBjZXJ0MREwDwYDVQQDEwhNaWxhbi1QQzBZMBMGByqG
6 | SM49AgEGCCqGSM49AwEHA0IABBdoogK9AlycJ0EtErMLHPd9okgaohJPNGFE0p+I
7 | mff1r2boCMrBQVJxF62jJN3S6BH2iO+dFUnKG9qyI8j1OCWjggE0MIIBMDAOBgNV
8 | HQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zCCAQsGA1UdEQSCAQIwgf+CCE1p
9 | bGFuLVBDgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSHBH8AAAGHEAAAAAAA
10 | AAAAAAAAAAAAAAGHEP6AAAAAAAAA0ZfWkOPgBoiHBKn+BoiHEP6AAAAAAAAAGMW1
11 | JeplNkeHBKn+NkeHECYEIAAUhABwAAC28MLphZ2HECYEIAAUhABwIXH9tkwVqfqH
12 | ECYEIAAUhABw7V5TCBkhCQ2HEP6AAAAAAAAAIXH9tkwVqfqHBMCoAd+HEP6AAAAA
13 | AAAACW0ACFfA9K6HBKn+9K6HEP6AAAAAAAAAAABe/sCoAd+HEP6AAAAAAAAAAOAA
14 | AAAAAACHBAAAAAAwCgYIKoZIzj0EAwIDSQAwRgIhALLU5H4pDC8xcY5QxjPlqKLQ
15 | expNLSTj6eiSJNSRSWxNAiEAoRe/4SNqtQ/pYzlgduH4qxG0Gvs4K+m1udCujVWa
16 | q9w=
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/server/api/testnet/tls.key:
--------------------------------------------------------------------------------
1 | -----BEGIN EC PRIVATE KEY-----
2 | MHcCAQEEIPTJ4228w64tRw5GgWRkVGmR8uh6EHUp3xKibgkc1yyeoAoGCCqGSM49
3 | AwEHoUQDQgAE30NW8iWbSH2731bYXHNxAbSmnfRVjgfmHnIKG4hnomDyhoaebhDv
4 | 6fIpTyaFxJwVe+i71ZrNIRTSmB1YZ/Bkjg==
5 | -----END EC PRIVATE KEY-----
6 |
--------------------------------------------------------------------------------
/server/api/transactions.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const {Transactions} = require('../db/models')
3 |
4 | module.exports = router
5 |
6 | router.get('/', async (req, res, next) => {
7 | try {
8 | const receiver = await Transactions.findAll({
9 | where: {
10 | receiverId: req.user.id
11 | },
12 | include: ['sender']
13 | })
14 | const sender = await Transactions.findAll({
15 | where: {
16 | senderId: req.user.id
17 | },
18 | include: ['receiver']
19 | })
20 | const message = req.body.message
21 | res.json({receiver, sender, message})
22 | } catch (err) {
23 | next(err)
24 | }
25 | })
26 |
--------------------------------------------------------------------------------
/server/api/users.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const {User} = require('../db/models')
3 |
4 | module.exports = router
5 |
6 | router.get('/', async (req, res, next) => {
7 | try {
8 | const users = await User.findAll({
9 | // explicitly select only the id and email fields - even though
10 | // users' passwords are encrypted, it won't help if we just
11 | // send everything to anyone who asks!
12 | attributes: ['id', 'email', 'username']
13 | })
14 | res.json(users)
15 | } catch (err) {
16 | next(err)
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/server/api/users.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe beforeEach it */
2 |
3 | const {expect} = require('chai')
4 | const request = require('supertest')
5 | const db = require('../db')
6 | const app = require('../index')
7 | const User = db.model('user')
8 |
9 | describe('User routes', () => {
10 | beforeEach(() => {
11 | return db.sync({force: true})
12 | })
13 |
14 | describe('/api/users/', () => {
15 | const codysEmail = 'cody@puppybook.com'
16 |
17 | beforeEach(() => {
18 | return User.create({
19 | email: codysEmail
20 | })
21 | })
22 |
23 | it('GET /api/users', async () => {
24 | const res = await request(app)
25 | .get('/api/users')
26 | .expect(200)
27 |
28 | expect(res.body).to.be.an('array')
29 | expect(res.body[0].email).to.be.equal(codysEmail)
30 | })
31 | }) // end describe('/api/users')
32 | }) // end describe('User routes')
33 |
--------------------------------------------------------------------------------
/server/auth/google.js:
--------------------------------------------------------------------------------
1 | const passport = require('passport')
2 | const router = require('express').Router()
3 | const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy
4 | const {User} = require('../db/models')
5 | module.exports = router
6 |
7 | /**
8 | * For OAuth keys and other secrets, your Node process will search
9 | * process.env to find environment variables. On your production server,
10 | * you will be able to set these environment variables with the appropriate
11 | * values. In development, a good practice is to keep a separate file with
12 | * these secrets that you only share with your team - it should NOT be tracked
13 | * by git! In this case, you may use a file called `secrets.js`, which will
14 | * set these environment variables like so:
15 | *
16 | * process.env.GOOGLE_CLIENT_ID = 'your google client id'
17 | * process.env.GOOGLE_CLIENT_SECRET = 'your google client secret'
18 | * process.env.GOOGLE_CALLBACK = '/your/google/callback'
19 | */
20 |
21 | if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET) {
22 | console.log('Google client ID / secret not found. Skipping Google OAuth.')
23 | } else {
24 | const googleConfig = {
25 | clientID: process.env.GOOGLE_CLIENT_ID,
26 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
27 | callbackURL: process.env.GOOGLE_CALLBACK
28 | }
29 |
30 | const strategy = new GoogleStrategy(
31 | googleConfig,
32 | (token, refreshToken, profile, done) => {
33 | const googleId = profile.id
34 | const name = profile.displayName
35 | const email = profile.emails[0].value
36 |
37 | User.findOrCreate({
38 | where: {googleId},
39 | defaults: {name, email}
40 | })
41 | .then(([user]) => done(null, user))
42 | .catch(done)
43 | }
44 | )
45 |
46 | passport.use(strategy)
47 |
48 | router.get('/', passport.authenticate('google', {scope: 'email'}))
49 |
50 | router.get(
51 | '/callback',
52 | passport.authenticate('google', {
53 | successRedirect: '/home',
54 | failureRedirect: '/login'
55 | })
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/server/auth/index.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 | const User = require('../db/models/user')
3 | module.exports = router
4 |
5 | const MessagingResponse = require('twilio').twiml.MessagingResponse
6 | const client = require('twilio')(
7 | process.env.twilioSid,
8 | process.env.twilioAuthToken
9 | )
10 |
11 | const twilioPhone = '+18482202516'
12 |
13 | const sendMessage = (phone, body) => {
14 | client.messages
15 | .create({
16 | body: body,
17 | from: twilioPhone,
18 | to: phone
19 | })
20 | .then(message => console.log(message.sid))
21 | }
22 |
23 | router.post('/login', async (req, res, next) => {
24 | try {
25 | const user = await User.findOne({
26 | where: {email: req.body.email.toLowerCase()}
27 | })
28 | console.log(req.body.email, 'This is the email')
29 | if (!user) {
30 | console.log('No such user found:', req.body.email)
31 | res.status(401).send('Wrong username and/or password')
32 | } else if (!user.correctPassword(req.body.password)) {
33 | console.log('Incorrect password for user:', req.body.email)
34 | res.status(401).send('Wrong username and/or password')
35 | } else {
36 | req.login(user, err => (err ? next(err) : res.json(user)))
37 | }
38 | } catch (err) {
39 | next(err)
40 | }
41 | })
42 |
43 | router.post('/signup', async (req, res, next) => {
44 | try {
45 | let userInfo = {
46 | username: req.body.username.toLowerCase(),
47 | email: req.body.email,
48 | password: req.body.password,
49 | phone: req.body.phone
50 | }
51 |
52 | console.log(userInfo)
53 | const user = await User.create(userInfo)
54 | client.validationRequests
55 | .create({
56 | friendlyName: req.body.firstName,
57 | phoneNumber: req.body.phone,
58 | callDelay: 7
59 | })
60 | .then(validationRequest =>
61 | sendMessage(
62 | req.body.phone,
63 | `Your Twilio code is :
64 | ${validationRequest.validationCode} `
65 | )
66 | )
67 |
68 | req.login(user, err => (err ? next(err) : res.json(user)))
69 | } catch (err) {
70 | if (err.name === 'SequelizeUniqueConstraintError') {
71 | res.status(401).send('User already exists')
72 | } else {
73 | next(err)
74 | }
75 | }
76 | })
77 |
78 | router.post('/logout', (req, res) => {
79 | req.logout()
80 | req.session.destroy()
81 | res.redirect('/')
82 | })
83 |
84 | router.get('/me', (req, res) => {
85 | res.json(req.user)
86 | })
87 |
88 | router.use('/google', require('./google'))
89 |
--------------------------------------------------------------------------------
/server/db/db.js:
--------------------------------------------------------------------------------
1 | const Sequelize = require('sequelize')
2 | const pkg = require('../../package.json')
3 |
4 | const databaseName = pkg.name + (process.env.NODE_ENV === 'test' ? '-test' : '')
5 |
6 | const db = new Sequelize(
7 | process.env.DATABASE_URL || `postgres://localhost:5432/${databaseName}`,
8 | {
9 | logging: false
10 | }
11 | )
12 | module.exports = db
13 |
14 | // This is a global Mocha hook used for resource cleanup.
15 | // Otherwise, Mocha v4+ does not exit after tests.
16 | if (process.env.NODE_ENV === 'test') {
17 | after('close database connection', () => db.close())
18 | }
19 |
--------------------------------------------------------------------------------
/server/db/index.js:
--------------------------------------------------------------------------------
1 | const db = require('./db')
2 |
3 | // register models
4 | require('./models')
5 |
6 | module.exports = db
7 |
--------------------------------------------------------------------------------
/server/db/models/index.js:
--------------------------------------------------------------------------------
1 | const User = require('./user')
2 | const Transactions = require('./transactions')
3 | /**
4 | * If we had any associations to make, this would be a great place to put them!
5 | * ex. if we had another model called BlogPost, we might say:
6 | *
7 | * BlogPost.belongsTo(User)
8 | */
9 |
10 | Transactions.belongsTo(User, {as: 'sender'})
11 | Transactions.belongsTo(User, {as: 'receiver'})
12 | /**
13 | * We'll export all of our models here, so that any time a module needs a model,
14 | * we can just require it from 'db/models'
15 | * for example, we can say: const {User} = require('../db/models')
16 | * instead of: const User = require('../db/models/user')
17 | */
18 | module.exports = {
19 | User,
20 | Transactions
21 | }
22 |
--------------------------------------------------------------------------------
/server/db/models/transactions.js:
--------------------------------------------------------------------------------
1 | const Sequelize = require('sequelize')
2 | const db = require('../db')
3 |
4 | const Transactions = db.define('transactions', {
5 | amount: {
6 | type: Sequelize.INTEGER
7 | }
8 | })
9 |
10 | module.exports = Transactions
11 |
--------------------------------------------------------------------------------
/server/db/models/user.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto')
2 | const Sequelize = require('sequelize')
3 | const db = require('../db')
4 |
5 | const User = db.define('user', {
6 | username: {
7 | type: Sequelize.STRING,
8 | allowNull: false,
9 | unique: true,
10 | validate: {
11 | notEmpty: true
12 | }
13 | },
14 | email: {
15 | type: Sequelize.STRING,
16 | unique: true,
17 | allowNull: false,
18 | validate: {
19 | notEmpty: true
20 | }
21 | },
22 | phone: {
23 | type: Sequelize.STRING,
24 | allowNull: false,
25 | unique: true,
26 | validate: {
27 | notEmpty: true
28 | }
29 | },
30 | wallet: {
31 | type: Sequelize.STRING
32 | },
33 | balance: {
34 | type: Sequelize.FLOAT,
35 | defaultValue: 0.0
36 | },
37 | password: {
38 | type: Sequelize.STRING,
39 | // Making `.password` act like a func hides it when serializing to JSON.
40 | // This is a hack to get around Sequelize's lack of a "private" option.
41 | get() {
42 | return () => this.getDataValue('password')
43 | }
44 | },
45 | salt: {
46 | type: Sequelize.STRING,
47 | // Making `.salt` act like a function hides it when serializing to JSON.
48 | // This is a hack to get around Sequelize's lack of a "private" option.
49 | get() {
50 | return () => this.getDataValue('salt')
51 | }
52 | },
53 | googleId: {
54 | type: Sequelize.STRING
55 | }
56 | })
57 |
58 | module.exports = User
59 |
60 | /**
61 | * instanceMethods
62 | */
63 | User.prototype.correctPassword = function(candidatePwd) {
64 | return User.encryptPassword(candidatePwd, this.salt()) === this.password()
65 | }
66 |
67 | /**
68 | * classMethods
69 | */
70 | User.generateSalt = function() {
71 | return crypto.randomBytes(16).toString('base64')
72 | }
73 |
74 | User.encryptPassword = function(plainText, salt) {
75 | return crypto
76 | .createHash('RSA-SHA256')
77 | .update(plainText)
78 | .update(salt)
79 | .digest('hex')
80 | }
81 |
82 | /**
83 | * hooks
84 | */
85 | const setSaltAndPassword = user => {
86 | if (user.changed('password')) {
87 | user.salt = User.generateSalt()
88 | user.password = User.encryptPassword(user.password(), user.salt())
89 | }
90 | }
91 |
92 | User.beforeCreate(setSaltAndPassword)
93 | User.beforeUpdate(setSaltAndPassword)
94 | User.beforeBulkCreate(users => {
95 | users.forEach(setSaltAndPassword)
96 | })
97 |
--------------------------------------------------------------------------------
/server/db/models/user.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe beforeEach it */
2 |
3 | const {expect} = require('chai')
4 | const db = require('../index')
5 | const User = db.model('user')
6 |
7 | describe('User model', () => {
8 | beforeEach(() => {
9 | return db.sync({force: true})
10 | })
11 |
12 | describe('instanceMethods', () => {
13 | describe('correctPassword', () => {
14 | let cody
15 |
16 | beforeEach(async () => {
17 | cody = await User.create({
18 | email: 'cody@puppybook.com',
19 | password: 'bones'
20 | })
21 | })
22 |
23 | it('returns true if the password is correct', () => {
24 | expect(cody.correctPassword('bones')).to.be.equal(true)
25 | })
26 |
27 | it('returns false if the password is incorrect', () => {
28 | expect(cody.correctPassword('bonez')).to.be.equal(false)
29 | })
30 | }) // end describe('correctPassword')
31 | }) // end describe('instanceMethods')
32 | }) // end describe('User model')
33 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const express = require('express')
3 | const morgan = require('morgan')
4 | const compression = require('compression')
5 | const session = require('express-session')
6 | const passport = require('passport')
7 | const SequelizeStore = require('connect-session-sequelize')(session.Store)
8 | const db = require('./db')
9 | const sessionStore = new SequelizeStore({db})
10 | const PORT = process.env.PORT || 8080
11 | const app = express()
12 | const socketio = require('socket.io')
13 | module.exports = app
14 |
15 | // This is a global Mocha hook, used for resource cleanup.
16 | // Otherwise, Mocha v4+ never quits after tests.
17 | if (process.env.NODE_ENV === 'test') {
18 | after('close the session store', () => sessionStore.stopExpiringSessions())
19 | }
20 |
21 | /**
22 | * In your development environment, you can keep all of your
23 | * app's secret API keys in a file called `secrets.js`, in your project
24 | * root. This file is included in the .gitignore - it will NOT be tracked
25 | * or show up on Github. On your production server, you can add these
26 | * keys as environment variables, so that they can still be read by the
27 | * Node process on process.env
28 | */
29 | if (process.env.NODE_ENV !== 'production') require('../secrets')
30 |
31 | // passport registration
32 | passport.serializeUser((user, done) => done(null, user.id))
33 |
34 | passport.deserializeUser(async (id, done) => {
35 | try {
36 | const user = await db.models.user.findById(id)
37 | done(null, user)
38 | } catch (err) {
39 | done(err)
40 | }
41 | })
42 |
43 | const createApp = () => {
44 | // logging middleware
45 | app.use(morgan('dev'))
46 |
47 | // body parsing middleware
48 | app.use(express.json())
49 | app.use(express.urlencoded({extended: true}))
50 |
51 | // compression middleware
52 | app.use(compression())
53 |
54 | // session middleware with passport
55 | app.use(
56 | session({
57 | secret: process.env.SESSION_SECRET || 'my best friend is Cody',
58 | store: sessionStore,
59 | resave: false,
60 | saveUninitialized: false
61 | })
62 | )
63 | app.use(passport.initialize())
64 | app.use(passport.session())
65 |
66 | // auth and api routes
67 | app.use('/auth', require('./auth'))
68 | app.use('/api', require('./api'))
69 |
70 | // static file-serving middleware
71 | app.use(express.static(path.join(__dirname, '..', 'public')))
72 |
73 | // any remaining requests with an extension (.js, .css, etc.) send 404
74 | app.use((req, res, next) => {
75 | if (path.extname(req.path).length) {
76 | const err = new Error('Not found')
77 | err.status = 404
78 | next(err)
79 | } else {
80 | next()
81 | }
82 | })
83 |
84 | // sends index.html
85 | app.use('*', (req, res) => {
86 | res.sendFile(path.join(__dirname, '..', 'public/index.html'))
87 | })
88 |
89 | // error handling endware
90 | app.use((err, req, res, next) => {
91 | console.error(err)
92 | console.error(err.stack)
93 | res.status(err.status || 500).send(err.message || 'Internal server error.')
94 | })
95 | }
96 |
97 | const startListening = () => {
98 | // start listening (and create a 'server' object representing our server)
99 | const server = app.listen(PORT, () =>
100 | console.log(`Mixing it up on port ${PORT}`)
101 | )
102 |
103 | // set up our socket control center
104 | const io = socketio(server)
105 | require('./socket')(io)
106 | }
107 |
108 | const syncDb = () => db.sync()
109 |
110 | async function bootApp() {
111 | await sessionStore.sync()
112 | await syncDb()
113 | await createApp()
114 | await startListening()
115 | }
116 | // This evaluates as true when this file is run directly from the command line,
117 | // i.e. when we say 'node server/index.js' (or 'nodemon server/index.js', or 'nodemon server', etc)
118 | // It will evaluate false when this module is required by another module - for example,
119 | // if we wanted to require our app in a test spec
120 | if (require.main === module) {
121 | bootApp()
122 | } else {
123 | createApp()
124 | }
125 |
--------------------------------------------------------------------------------
/server/socket/index.js:
--------------------------------------------------------------------------------
1 | module.exports = io => {
2 | io.on('connection', socket => {
3 | console.log(`A socket connection to the server has been made: ${socket.id}`)
4 |
5 | socket.on('disconnect', () => {
6 | console.log(`Connection ${socket.id} has left the building`)
7 | })
8 | })
9 | }
10 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const isDev = process.env.NODE_ENV === 'development'
2 |
3 | module.exports = {
4 | mode: isDev ? 'development' : 'production',
5 | entry: [
6 | '@babel/polyfill', // enables async-await
7 | './client/index.js'
8 | ],
9 | output: {
10 | path: __dirname,
11 | filename: './public/bundle.js'
12 | },
13 | resolve: {
14 | extensions: ['.js', '.jsx']
15 | },
16 | devtool: 'source-map',
17 | watchOptions: {
18 | ignored: /node_modules/
19 | },
20 | module: {
21 | rules: [
22 | {
23 | test: /\.jsx?$/,
24 | exclude: /node_modules/,
25 | loader: 'babel-loader'
26 | }
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------