mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-27 14:22:58 +03:00
Add setup background threads. Minor fixes and cosmetic.
This commit is contained in:
parent
64115a8bb5
commit
58ecb607c3
@ -122,22 +122,22 @@ class LNNode():
|
||||
lnpayment.save()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def check_until_invoice_locked(cls, payment_hash, expiration):
|
||||
'''Checks until hold invoice is locked.
|
||||
When invoice is locked, returns true.
|
||||
If time expires, return False.'''
|
||||
# Experimental, might need asyncio. Best if subscribing all invoices and running a background task
|
||||
# Maybe best to pass LNpayment object and change status live.
|
||||
# @classmethod
|
||||
# def check_until_invoice_locked(cls, payment_hash, expiration):
|
||||
# '''Checks until hold invoice is locked.
|
||||
# When invoice is locked, returns true.
|
||||
# If time expires, return False.'''
|
||||
# # Experimental, might need asyncio. Best if subscribing all invoices and running a background task
|
||||
# # Maybe best to pass LNpayment object and change status live.
|
||||
|
||||
request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash)
|
||||
for invoice in cls.invoicesstub.SubscribeSingleInvoice(request):
|
||||
print(invoice)
|
||||
if timezone.now > expiration:
|
||||
break
|
||||
if invoice.state == 3: # True if hold invoice is accepted.
|
||||
return True
|
||||
return False
|
||||
# request = invoicesrpc.SubscribeSingleInvoiceRequest(r_hash=payment_hash)
|
||||
# for invoice in cls.invoicesstub.SubscribeSingleInvoice(request):
|
||||
# print(invoice)
|
||||
# if timezone.now > expiration:
|
||||
# break
|
||||
# if invoice.state == 3: # True if hold invoice is accepted.
|
||||
# return True
|
||||
# return False
|
||||
|
||||
|
||||
@classmethod
|
||||
|
@ -50,9 +50,9 @@ class Logics():
|
||||
def validate_order_size(order):
|
||||
'''Validates if order is withing limits in satoshis at t0'''
|
||||
if order.t0_satoshis > MAX_TRADE:
|
||||
return False, {'bad_request': 'Your order is too big. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MAX_TRADE)+ ' Sats'}
|
||||
return False, {'bad_request': 'Your order is too big. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now, but the limit is '+'{:,}'.format(MAX_TRADE)+ ' Sats'}
|
||||
if order.t0_satoshis < MIN_TRADE:
|
||||
return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now. But limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'}
|
||||
return False, {'bad_request': 'Your order is too small. It is worth '+'{:,}'.format(order.t0_satoshis)+' Sats now, but the limit is '+'{:,}'.format(MIN_TRADE)+ ' Sats'}
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
@ -386,11 +386,12 @@ class Logics():
|
||||
return True, None
|
||||
|
||||
# 2) When maker cancels after bond
|
||||
'''The order dissapears from book and goes to cancelled. Maker is charged the bond to prevent DDOS
|
||||
on the LN node and order book. TODO Only charge a small part of the bond (requires maker submitting an invoice)'''
|
||||
'''The order dissapears from book and goes to cancelled. If strict, maker is charged the bond
|
||||
to prevent DDOS on the LN node and order book. If not strict, maker is returned
|
||||
the bond (more user friendly).'''
|
||||
elif order.status == Order.Status.PUB and order.maker == user:
|
||||
#Settle the maker bond (Maker loses the bond for cancelling public order)
|
||||
if cls.settle_bond(order.maker_bond):
|
||||
if cls.return_bond(order.maker_bond): # strict: cls.settle_bond(order.maker_bond):
|
||||
order.status = Order.Status.UCA
|
||||
order.save()
|
||||
return True, None
|
||||
|
10
api/views.py
10
api/views.py
@ -39,6 +39,9 @@ class MakerView(CreateAPIView):
|
||||
def post(self,request):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return Response({'bad_request':'Woops! It seems you do not have a robot avatar'}, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not serializer.is_valid(): return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
type = serializer.data.get('type')
|
||||
@ -413,15 +416,16 @@ class UserView(APIView):
|
||||
def delete(self,request):
|
||||
''' Pressing "give me another" deletes the logged in user '''
|
||||
user = request.user
|
||||
if not user:
|
||||
if not user.is_authenticated:
|
||||
return Response(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Only delete if user life is shorter than 30 minutes. Helps deleting users by mistake
|
||||
# Only delete if user life is shorter than 30 minutes. Helps to avoid deleting users by mistake
|
||||
if user.date_joined < (timezone.now() - timedelta(minutes=30)):
|
||||
return Response(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Check if it is not a maker or taker!
|
||||
if not Logics.validate_already_maker_or_taker(user):
|
||||
not_participant, _, _ = Logics.validate_already_maker_or_taker(user)
|
||||
if not not_participant:
|
||||
return Response({'bad_request':'User cannot be deleted while he is part of an order'}, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
logout(request)
|
||||
|
@ -93,7 +93,7 @@ export default class BookPage extends Component {
|
||||
renderCell: (params) => {return (
|
||||
<ListItemButton style={{ cursor: "pointer" }}>
|
||||
<ListItemAvatar>
|
||||
<Avatar alt={params.row.robosat} src={params.row.avatar} />
|
||||
<Avatar className="flippedSmallAvatar" alt={params.row.robosat} src={params.row.avatar} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={params.row.robosat}/>
|
||||
</ListItemButton>
|
||||
@ -147,7 +147,7 @@ export default class BookPage extends Component {
|
||||
{ field: 'robosat', headerName: 'Robot', width: 80,
|
||||
renderCell: (params) => {return (
|
||||
<ListItemButton style={{ cursor: "pointer" }}>
|
||||
<Avatar alt={params.row.robosat} src={params.row.avatar} />
|
||||
<Avatar className="flippedSmallAvatar" alt={params.row.robosat} src={params.row.avatar} />
|
||||
</ListItemButton>
|
||||
);
|
||||
} },
|
||||
|
@ -17,6 +17,7 @@ import SendIcon from '@mui/icons-material/Send';
|
||||
import PublicIcon from '@mui/icons-material/Public';
|
||||
import NumbersIcon from '@mui/icons-material/Numbers';
|
||||
import PasswordIcon from '@mui/icons-material/Password';
|
||||
import ContentCopy from "@mui/icons-material/ContentCopy";
|
||||
|
||||
// pretty numbers
|
||||
function pn(x) {
|
||||
@ -198,7 +199,7 @@ export default class BottomBar extends Component {
|
||||
</Typography>
|
||||
</ListItemText>
|
||||
<ListItemAvatar>
|
||||
<Avatar className='avatar'
|
||||
<Avatar className='profileAvatar'
|
||||
sx={{ width: 65, height:65 }}
|
||||
alt={this.props.nickname}
|
||||
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
|
||||
@ -226,14 +227,21 @@ export default class BottomBar extends Component {
|
||||
<ListItemIcon>
|
||||
<PasswordIcon/>
|
||||
</ListItemIcon>
|
||||
<ListItemText secondary="Your token.">
|
||||
<ListItemText secondary="Your token">
|
||||
{this.props.token ?
|
||||
<TextField
|
||||
disabled
|
||||
label='Store safely'
|
||||
value={this.props.token }
|
||||
variant='filled'
|
||||
size='small'/>
|
||||
size='small'
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<IconButton onClick= {()=>navigator.clipboard.writeText(this.props.token)}>
|
||||
<ContentCopy />
|
||||
</IconButton>,
|
||||
}}
|
||||
/>
|
||||
:
|
||||
'Cannot remember'}
|
||||
</ListItemText>
|
||||
@ -258,7 +266,7 @@ bottomBarDesktop =()=>{
|
||||
<ListItemButton onClick={this.handleClickOpenProfile} >
|
||||
<ListItemAvatar sx={{ width: 30, height: 30 }} >
|
||||
<Badge badgeContent={(this.state.active_order_id > 0 & !this.state.profileShown) ? "": null} color="primary">
|
||||
<Avatar className='rotatedAvatar' sx={{margin: 0, top: -13}}
|
||||
<Avatar className='flippedSmallAvatar' sx={{margin: 0, top: -13}}
|
||||
alt={this.props.nickname}
|
||||
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
|
||||
/>
|
||||
@ -462,7 +470,7 @@ bottomBarPhone =()=>{
|
||||
<Grid item xs={1.6}>
|
||||
<IconButton onClick={this.handleClickOpenProfile} sx={{margin: 0, top: -13, }} >
|
||||
<Badge badgeContent={(this.state.active_order_id >0 & !this.state.profileShown) ? "1": null} color="primary">
|
||||
<Avatar className='rotatedAvatar'
|
||||
<Avatar className='flippedSmallAvatar'
|
||||
alt={this.props.nickname}
|
||||
src={this.props.nickname ? window.location.origin +'/static/assets/avatars/' + this.props.nickname + '.png' : null}
|
||||
/>
|
||||
|
@ -309,7 +309,7 @@ export default class OrderPage extends Component {
|
||||
|
||||
// If maker and Waiting for Bond. Or if taker and Waiting for bond.
|
||||
// Simply allow to cancel without showing the cancel dialog.
|
||||
if ((this.state.is_maker & this.state.status == 0) || this.state.is_taker & this.state.status == 3){
|
||||
if ((this.state.is_maker & [0,1].includes(this.state.status)) || this.state.is_taker & this.state.status == 3){
|
||||
return(
|
||||
<Grid item xs={12} align="center">
|
||||
<Button variant='contained' color='secondary' onClick={this.handleClickConfirmCancelButton}>Cancel</Button>
|
||||
@ -317,7 +317,7 @@ export default class OrderPage extends Component {
|
||||
)}
|
||||
// If the order does not yet have an escrow deposited. Show dialog
|
||||
// to confirm forfeiting the bond
|
||||
if ([1,3,6,7].includes(this.state.status)){
|
||||
if ([3,6,7].includes(this.state.status)){
|
||||
return(
|
||||
<div id="openDialogCancelButton">
|
||||
<Grid item xs={12} align="center">
|
||||
@ -354,7 +354,7 @@ export default class OrderPage extends Component {
|
||||
<List dense="true">
|
||||
<ListItem >
|
||||
<ListItemAvatar sx={{ width: 56, height: 56 }}>
|
||||
<Avatar
|
||||
<Avatar className="flippedSmallAvatar"
|
||||
alt={this.state.maker_nick}
|
||||
src={window.location.origin +'/static/assets/avatars/' + this.state.maker_nick + '.png'}
|
||||
/>
|
||||
@ -370,7 +370,7 @@ export default class OrderPage extends Component {
|
||||
<ListItem align="left">
|
||||
<ListItemText primary={this.state.taker_nick + (this.state.type ? " (Buyer)" : " (Seller)")} secondary="Order taker"/>
|
||||
<ListItemAvatar >
|
||||
<Avatar
|
||||
<Avatar className="smallAvatar"
|
||||
alt={this.state.maker_nick}
|
||||
src={window.location.origin +'/static/assets/avatars/' + this.state.taker_nick + '.png'}
|
||||
/>
|
||||
|
@ -3,7 +3,7 @@ import { Button , Dialog, Grid, Typography, TextField, ButtonGroup, CircularProg
|
||||
import { Link } from 'react-router-dom'
|
||||
import Image from 'material-ui-image'
|
||||
import InfoDialog from './InfoDialog'
|
||||
import PublishIcon from '@mui/icons-material/Publish';
|
||||
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
||||
import CasinoIcon from '@mui/icons-material/Casino';
|
||||
import ContentCopy from "@mui/icons-material/ContentCopy";
|
||||
|
||||
@ -161,14 +161,11 @@ export default class UserGenPage extends Component {
|
||||
}
|
||||
<Grid container align="center">
|
||||
<Grid item xs={12} align="center">
|
||||
<IconButton sx={{top:6}} onClick= {()=>navigator.clipboard.writeText(this.state.token)}>
|
||||
<ContentCopy sx={{width:18, height:18}} />
|
||||
</IconButton>
|
||||
<TextField
|
||||
<TextField sx={{maxWidth: 280}}
|
||||
//sx={{ input: { color: 'purple' } }}
|
||||
InputLabelProps={{
|
||||
style: { color: 'green' },
|
||||
}}
|
||||
// InputLabelProps={{
|
||||
// style: { color: 'green' },
|
||||
// }}
|
||||
error={this.state.bad_request}
|
||||
label='Store your token safely'
|
||||
required='true'
|
||||
@ -183,16 +180,21 @@ export default class UserGenPage extends Component {
|
||||
this.handleClickSubmitToken();
|
||||
}
|
||||
}}
|
||||
InputProps={{
|
||||
startAdornment:
|
||||
<IconButton onClick= {()=>navigator.clipboard.writeText(this.state.token)}>
|
||||
<ContentCopy color={this.state.tokenHasChanged ? 'inherit' : 'primary' } sx={{width:18, height:18}} />
|
||||
</IconButton>,
|
||||
endAdornment:
|
||||
<IconButton onClick={this.handleClickNewRandomToken}><CasinoIcon/></IconButton>,
|
||||
}}
|
||||
/>
|
||||
<IconButton sx={{top:8}} onClick={this.handleClickNewRandomToken}>
|
||||
<CasinoIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
<Button disabled={!this.state.tokenHasChanged} type="submit" size='small' onClick= {this.handleClickSubmitToken}>
|
||||
<PublishIcon />
|
||||
<span> Generate Robot</span>
|
||||
<SmartToyIcon sx={{width:18, height:18}} />
|
||||
<span> Generate Robot</span>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12} align="center">
|
||||
|
@ -39,7 +39,7 @@ body {
|
||||
|
||||
.profileNickname {
|
||||
margin: 0;
|
||||
left: -22px;
|
||||
left: -16px;
|
||||
}
|
||||
|
||||
.newAvatar {
|
||||
@ -51,14 +51,19 @@ body {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
.profileAvatar {
|
||||
border: 0.5px solid #555;
|
||||
filter: drop-shadow(0.5px 0.5px 0.5px #000000);
|
||||
left: 35px;
|
||||
}
|
||||
|
||||
.rotatedAvatar {
|
||||
transform: scaleX(-1);
|
||||
.smallAvatar {
|
||||
border: 0.5px solid #555;
|
||||
filter: drop-shadow(0.5px 0.5px 0.5px #000000);
|
||||
}
|
||||
|
||||
.flippedSmallAvatar {
|
||||
transform: scaleX(-1);
|
||||
border: 0.3px solid #555;
|
||||
filter: drop-shadow(0.5px 0.5px 0.5px #000000);
|
||||
}
|
77
setup.md
77
setup.md
@ -133,4 +133,79 @@ Note we are using mostly MaterialUI V5 (@mui/material) but Image loading from V4
|
||||
|
||||
### Launch the React render
|
||||
from frontend/ directory
|
||||
`npm run dev`
|
||||
`npm run dev`
|
||||
|
||||
## Robosats background threads.
|
||||
|
||||
There is 3 processes that run asynchronously: two admin commands and a celery beat scheduler.
|
||||
The celery worker will run the task of caching external API market prices and cleaning(deleting) the generated robots that were never used.
|
||||
`celery -A robosats worker --beat -l debug -S django`
|
||||
|
||||
The admin commands are used to keep an eye on the state of LND hold invoices and check whether orders have expired
|
||||
```
|
||||
python3 manage.py follow_invoices
|
||||
python3 manage.py clean_order
|
||||
```
|
||||
|
||||
It might be best to set up system services to continuously run these background processes.
|
||||
|
||||
### Follow invoices admin command as system service
|
||||
|
||||
Create `/etc/systemd/system/follow_invoices.service` and edit with:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=RoboSats Follow LND Invoices
|
||||
After=lnd.service
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/<USER>/robosats/
|
||||
StandardOutput=file:/home/<USER>/robosats/follow_invoices.log
|
||||
StandardError=file:/home/<USER>/robosats/follow_invoices.log
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
User=<USER>
|
||||
ExecStart=python3 manage.py follow_invoices
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Then launch it with
|
||||
|
||||
```
|
||||
systemctl start follow_invoices
|
||||
systemctl enable follow_invoices
|
||||
```
|
||||
### Clean orders admin command as system service
|
||||
|
||||
Create `/etc/systemd/system/clean_orders.service` and edit with (replace <USER> for your username):
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=RoboSats Clean Orders
|
||||
After=lnd.service
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/<USER>/robosats/
|
||||
StandardOutput=file:/home/<USER>/robosats/clean_orders.log
|
||||
StandardError=file:/home/<USER>/robosats/clean_orders.log
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
User=<USER>
|
||||
ExecStart=python3 manage.py clean_orders
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Then launch it with
|
||||
|
||||
```
|
||||
systemctl start clean_orders
|
||||
systemctl enable clean_orders
|
||||
```
|
Loading…
Reference in New Issue
Block a user