mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-13 12:33:25 +02:00
Add global sell price setting (#1791)
* Add sell price setting to db * Add rpc method for set and get sell price setting * Fix servicer methods for get and set sell price * Add settings page in frontend * Include default sell price in get sell price rpc * Show sell price in settings page * Got set sell price working in frontend * Remove frontend validation of sell price dialog * Use sell price from settings when determining price * Add rpc to clear sell price setting * Add clear sell price to frontend * Remove unused imports in frontend * Update frontend build
This commit is contained in:
parent
3674595683
commit
488ed5dfc4
37 changed files with 937 additions and 28 deletions
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
clearSellPriceRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function ClearSellPriceDialog({
|
||||
open,
|
||||
handleClose,
|
||||
reloadSellPriceFn,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
const clearSellPrice = () => {
|
||||
clearSellPriceRequest(() => {
|
||||
reloadSellPriceFn();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('clear price msat');
|
||||
clearSellPrice();
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SubmitButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Clear Sell Price
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SellPriceForm() {
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Clear Sell Price</FormLabel>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Clear Sell Price</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{SellPriceForm()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{SubmitButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "ClearSellPriceDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "ClearSellPriceDialog.js"
|
||||
}
|
||||
43
frontend/src/components/ClearSellPriceDialog/styles.js
Normal file
43
frontend/src/components/ClearSellPriceDialog/styles.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -40,6 +40,7 @@ import {
|
|||
import {
|
||||
reloadRoute,
|
||||
goToSearchPage,
|
||||
goToSettingsPage,
|
||||
} from '../../navigation/navigation';
|
||||
|
||||
const notifications = [];
|
||||
|
|
@ -206,6 +207,19 @@ export default function Header(props) {
|
|||
{network}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography
|
||||
className={classes.settingsMenuLink}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
goToSettingsPage(history)
|
||||
setProfileMenu(null);
|
||||
}
|
||||
}
|
||||
>
|
||||
Settings
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.profileMenuUser}>
|
||||
<Typography
|
||||
className={classes.profileMenuLink}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,13 @@ export default makeStyles((theme) => ({
|
|||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
settingsMenuLink: {
|
||||
fontSize: 16,
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
messageNotification: {
|
||||
height: 'auto',
|
||||
display: 'flex',
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import Peers from '../../pages/peers';
|
|||
import Peer from '../../pages/peer';
|
||||
import PeerAddress from '../../pages/peeraddress';
|
||||
import Twitter from '../../pages/twitter';
|
||||
import Settings from '../../pages/settings';
|
||||
|
||||
// context
|
||||
import { useLayoutState } from '../../context/LayoutContext';
|
||||
|
|
@ -81,6 +82,7 @@ function Layout(props) {
|
|||
<Route path="/app/peeraddress/:network/:host/:port" component={PeerAddress} />
|
||||
<Route path="/app/notifications" component={Notifications} />
|
||||
<Route path="/app/twitter" component={Twitter} />
|
||||
<Route path="/app/settings" component={Settings} />
|
||||
<Route
|
||||
exact
|
||||
path="/app/ui"
|
||||
|
|
|
|||
133
frontend/src/components/SetSellPriceDialog/SetSellPriceDialog.js
Normal file
133
frontend/src/components/SetSellPriceDialog/SetSellPriceDialog.js
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
TextField,
|
||||
} from '@material-ui/core';
|
||||
|
||||
// styles
|
||||
import useStyles from './styles';
|
||||
|
||||
import {
|
||||
setSellPriceRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
export default function SetSellPriceDialog({
|
||||
open,
|
||||
handleClose,
|
||||
reloadSellPriceFn,
|
||||
...props
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const [sellPriceMsat, setSellPriceMsat] = useState(0);
|
||||
|
||||
|
||||
const setSellPrice = (priceMsat) => {
|
||||
setSellPriceRequest(priceMsat, () => {
|
||||
reloadSellPriceFn();
|
||||
});
|
||||
};
|
||||
|
||||
// const setUseCustomPrice = (id, useCustomPrice) => {
|
||||
// setSqueakProfileUseCustomPriceRequest(id, useCustomPrice, () => {
|
||||
// reloadProfile();
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// const setCustomPriceMsat = (id, customPriceMsat) => {
|
||||
// setSqueakProfileCustomPriceRequest(id, customPriceMsat, () => {
|
||||
// reloadProfile();
|
||||
// });
|
||||
// };
|
||||
|
||||
// const handleSettingsFollowingChange = (event) => {
|
||||
// console.log(`Following changed for profile id: ${squeakProfile.getProfileId()}`);
|
||||
// console.log(`Following changed to: ${event.target.checked}`);
|
||||
// setFollowing(squeakProfile.getProfileId(), event.target.checked);
|
||||
// };
|
||||
//
|
||||
// const handleSettingsUseCustomPriceChange = (event) => {
|
||||
// console.log(`UseCustomPrice changed for profile id: ${squeakProfile.getProfileId()}`);
|
||||
// console.log(`UseCustomPrice changed to: ${event.target.checked}`);
|
||||
// setUseCustomPrice(squeakProfile.getProfileId(), event.target.checked);
|
||||
// };
|
||||
|
||||
const handlePriceMsatChange = (event) => {
|
||||
console.log(`Price changed:`);
|
||||
const newPriceMsat = event.target.value;
|
||||
console.log(`Price changed to: ${newPriceMsat}`);
|
||||
setSellPriceMsat(newPriceMsat);
|
||||
};
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
console.log('sell price msat:', sellPriceMsat);
|
||||
if (!sellPriceMsat) {
|
||||
alert('Sell price cannot be empty.');
|
||||
return;
|
||||
}
|
||||
setSellPrice(sellPriceMsat);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function CancelButton() {
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SubmitButton() {
|
||||
return (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
>
|
||||
Set Sell Price
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SellPriceForm() {
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Set Sell Price</FormLabel>
|
||||
<TextField
|
||||
required
|
||||
id="standard-required"
|
||||
label="Price (msats)"
|
||||
type="number"
|
||||
defaultValue={0}
|
||||
onChange={handlePriceMsatChange}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle id="form-dialog-title">Sell Price</DialogTitle>
|
||||
<form className={classes.root} onSubmit={handleSubmit} noValidate autoComplete="off">
|
||||
<DialogContent>
|
||||
{SellPriceForm()}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{CancelButton()}
|
||||
{SubmitButton()}
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
6
frontend/src/components/SetSellPriceDialog/package.json
Normal file
6
frontend/src/components/SetSellPriceDialog/package.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "SetSellPriceDialog",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "SetSellPriceDialog.js"
|
||||
}
|
||||
43
frontend/src/components/SetSellPriceDialog/styles.js
Normal file
43
frontend/src/components/SetSellPriceDialog/styles.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
widgetWrapper: {
|
||||
display: 'flex',
|
||||
minHeight: '100%',
|
||||
},
|
||||
widgetHeader: {
|
||||
padding: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(1),
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
widgetRoot: {
|
||||
boxShadow: theme.customShadows.widget,
|
||||
},
|
||||
widgetBody: {
|
||||
paddingBottom: theme.spacing(3),
|
||||
paddingRight: theme.spacing(3),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
noPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
moreButton: {
|
||||
margin: -theme.spacing(1),
|
||||
padding: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
color: theme.palette.text.hint,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: 'rgba(255, 255, 255, 0.35)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -52,3 +52,7 @@ export const goToSentPaymentsPage = (history) => {
|
|||
export const goToReceivedPaymentsPage = (history) => {
|
||||
history.push('/app/receivedpayments/');
|
||||
};
|
||||
|
||||
export const goToSettingsPage = (history) => {
|
||||
history.push('/app/settings/');
|
||||
};
|
||||
|
|
|
|||
213
frontend/src/pages/settings/Settings.js
Normal file
213
frontend/src/pages/settings/Settings.js
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
Grid,
|
||||
Button,
|
||||
Tabs,
|
||||
Tab,
|
||||
AppBar,
|
||||
Box,
|
||||
FormLabel,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
|
||||
|
||||
// components
|
||||
import Widget from '../../components/Widget';
|
||||
import SetSellPriceDialog from '../../components/SetSellPriceDialog';
|
||||
import ClearSellPriceDialog from '../../components/ClearSellPriceDialog';
|
||||
|
||||
|
||||
import {
|
||||
getSellPriceRequest,
|
||||
} from '../../squeakclient/requests';
|
||||
|
||||
|
||||
export default function Settings() {
|
||||
const [value, setValue] = useState(0);
|
||||
const [sellPriceMsat, setSellPriceMsat] = useState(null);
|
||||
const [waitingForSellPriceMsat, setWaitingForSellPriceMsat] = useState(false);
|
||||
const [setSellPriceDialogOpen, setSetSellPriceDialogOpen] = useState(false);
|
||||
const [clearSellPriceDialogOpen, setClearSellPriceDialogOpen] = useState(false);
|
||||
|
||||
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
const handleCloseSetSellPriceDialog = () => {
|
||||
setSetSellPriceDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleClickSetSellPriceDialog = () => {
|
||||
setSetSellPriceDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseClearSellPriceDialog = () => {
|
||||
setClearSellPriceDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleClickClearSellPriceDialog = () => {
|
||||
setClearSellPriceDialogOpen(true);
|
||||
};
|
||||
|
||||
const loadSellPrice = useCallback(() => {
|
||||
setWaitingForSellPriceMsat(true);
|
||||
getSellPriceRequest((resp => {
|
||||
setWaitingForSellPriceMsat(false);
|
||||
console.log(resp);
|
||||
setSellPriceMsat(resp);
|
||||
}));
|
||||
},
|
||||
[]);
|
||||
|
||||
useEffect(() => {
|
||||
loadSellPrice();
|
||||
}, [loadSellPrice]);
|
||||
|
||||
function TabPanel(props) {
|
||||
const {
|
||||
children, value, index, ...other
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<div>{children}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SentPaymentContent() {
|
||||
const usingDefault = !sellPriceMsat.getPriceMsatIsSet();
|
||||
const priceSats = sellPriceMsat.getPriceMsat() / 1000;
|
||||
const defaultPriceSats = sellPriceMsat.getDefaultPriceMsat() / 1000;
|
||||
console.log('sell price' + priceSats);
|
||||
console.log('default sell price' + defaultPriceSats);
|
||||
console.log('use default' + usingDefault);
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Widget disableWidgetMenu>
|
||||
<Grid item>
|
||||
<FormLabel>
|
||||
Sell price
|
||||
</FormLabel>
|
||||
<Typography size="md">
|
||||
{usingDefault
|
||||
? defaultPriceSats
|
||||
: priceSats}
|
||||
{' sats'}
|
||||
{usingDefault && ' (using default)'}
|
||||
</Typography>
|
||||
{SetSellPriceButtonContent()}
|
||||
{ClearSellPriceButtonContent()}
|
||||
</Grid>
|
||||
</Widget>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsTabs() {
|
||||
return (
|
||||
<>
|
||||
<AppBar position="static" color="default">
|
||||
<Tabs value={value} onChange={handleChange} aria-label="simple tabs example">
|
||||
<Tab label="Settings" {...a11yProps(0)} />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
<TabPanel value={value} index={0}>
|
||||
{sellPriceMsat && SentPaymentContent()}
|
||||
</TabPanel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function GridContent() {
|
||||
return (
|
||||
<Grid container spacing={0}>
|
||||
<Grid item xs={12} sm={9}>
|
||||
{SettingsTabs()}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3} />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
function SetSellPriceDialogContent() {
|
||||
return (
|
||||
<>
|
||||
<SetSellPriceDialog
|
||||
open={setSellPriceDialogOpen}
|
||||
handleClose={handleCloseSetSellPriceDialog}
|
||||
reloadSellPriceFn={loadSellPrice}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ClearSellPriceDialogContent() {
|
||||
return (
|
||||
<>
|
||||
<ClearSellPriceDialog
|
||||
open={clearSellPriceDialogOpen}
|
||||
handleClose={handleCloseClearSellPriceDialog}
|
||||
reloadSellPriceFn={loadSellPrice}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SetSellPriceButtonContent() {
|
||||
return (
|
||||
<>
|
||||
<Box p={1}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleClickSetSellPriceDialog}
|
||||
>
|
||||
Set Sell Price
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ClearSellPriceButtonContent() {
|
||||
return (
|
||||
<>
|
||||
<Box p={1}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleClickClearSellPriceDialog}
|
||||
>
|
||||
Clear Sell Price
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!waitingForSellPriceMsat && GridContent()}
|
||||
{SetSellPriceDialogContent()}
|
||||
{ClearSellPriceDialogContent()}
|
||||
< />
|
||||
);
|
||||
}
|
||||
6
frontend/src/pages/settings/package.json
Normal file
6
frontend/src/pages/settings/package.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Settings",
|
||||
"version": "0.0.0",
|
||||
"main": "Settings.js",
|
||||
"private": true
|
||||
}
|
||||
|
|
@ -143,6 +143,12 @@ import {
|
|||
DeleteTwitterAccountReply,
|
||||
SetPeerShareForFreeRequest,
|
||||
SetPeerShareForFreeReply,
|
||||
SetSellPriceRequest,
|
||||
SetSellPriceReply,
|
||||
GetSellPriceRequest,
|
||||
GetSellPriceReply,
|
||||
ClearSellPriceRequest,
|
||||
ClearSellPriceReply,
|
||||
} from '../proto/squeak_admin_pb';
|
||||
|
||||
console.log('The value of REACT_APP_DEV_MODE_ENABLED is:', Boolean(process.env.REACT_APP_DEV_MODE_ENABLED));
|
||||
|
|
@ -1263,6 +1269,39 @@ export function deleteTwitterAccountRequest(twitterAccountId, handleResponse) {
|
|||
);
|
||||
}
|
||||
|
||||
export function setSellPriceRequest(priceMsat, handleResponse) {
|
||||
const request = new SetSellPriceRequest();
|
||||
request.setPriceMsat(priceMsat);
|
||||
makeRequest(
|
||||
'setsellprice',
|
||||
request,
|
||||
SetSellPriceReply.deserializeBinary,
|
||||
handleResponse,
|
||||
);
|
||||
}
|
||||
|
||||
export function clearSellPriceRequest(handleResponse) {
|
||||
const request = new ClearSellPriceRequest();
|
||||
makeRequest(
|
||||
'clearsellprice',
|
||||
request,
|
||||
ClearSellPriceReply.deserializeBinary,
|
||||
handleResponse,
|
||||
);
|
||||
}
|
||||
|
||||
export function getSellPriceRequest(handleResponse) {
|
||||
const request = new GetSellPriceRequest();
|
||||
makeRequest(
|
||||
'getsellprice',
|
||||
request,
|
||||
GetSellPriceReply.deserializeBinary,
|
||||
(response) => {
|
||||
handleResponse(response);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// export function subscribeConnectedPeersRequest(handleResponse) {
|
||||
// const request = new SubscribeConnectedPeersRequest();
|
||||
// const stream = client.subscribeConnectedPeers(request);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ from squeak.core import CSqueak
|
|||
from proto import lnd_pb2 as ln
|
||||
from proto import squeak_admin_pb2
|
||||
from tests.util import channel
|
||||
from tests.util import clear_sell_price
|
||||
from tests.util import connect_squeak_peer
|
||||
from tests.util import create_contact_profile
|
||||
from tests.util import create_saved_peer
|
||||
|
|
@ -49,6 +50,7 @@ from tests.util import get_hash
|
|||
from tests.util import get_network
|
||||
from tests.util import get_peer_by_address
|
||||
from tests.util import get_search_squeaks
|
||||
from tests.util import get_sell_price
|
||||
from tests.util import get_squeak_display
|
||||
from tests.util import get_squeak_profile
|
||||
from tests.util import get_twitter_bearer_token
|
||||
|
|
@ -57,6 +59,7 @@ from tests.util import make_squeak
|
|||
from tests.util import open_peer_connection
|
||||
from tests.util import peer_connection
|
||||
from tests.util import send_coins
|
||||
from tests.util import set_sell_price
|
||||
from tests.util import set_twitter_bearer_token
|
||||
from tests.util import subscribe_connected_peers
|
||||
from tests.util import subscribe_squeak_ancestor_entries
|
||||
|
|
@ -71,6 +74,29 @@ def test_get_network(admin_stub):
|
|||
assert network == "simnet"
|
||||
|
||||
|
||||
def test_get_sell_price(admin_stub):
|
||||
# Get the sell price
|
||||
price = get_sell_price(admin_stub)
|
||||
|
||||
assert price.price_msat == 0
|
||||
assert not price.price_msat_is_set
|
||||
assert price.default_price_msat == 1000000
|
||||
|
||||
set_sell_price(admin_stub, 98765)
|
||||
price = get_sell_price(admin_stub)
|
||||
|
||||
assert price.price_msat == 98765
|
||||
assert price.price_msat_is_set
|
||||
assert price.default_price_msat == 1000000
|
||||
|
||||
clear_sell_price(admin_stub)
|
||||
price = get_sell_price(admin_stub)
|
||||
|
||||
assert price.price_msat == 0
|
||||
assert not price.price_msat_is_set
|
||||
assert price.default_price_msat == 1000000
|
||||
|
||||
|
||||
def test_get_twitter_bearer_token(admin_stub):
|
||||
# Get the twitter bearer token
|
||||
bearer_token = get_twitter_bearer_token(admin_stub)
|
||||
|
|
|
|||
|
|
@ -307,6 +307,27 @@ def get_network(node_stub):
|
|||
return get_network_response.network
|
||||
|
||||
|
||||
def set_sell_price(node_stub, price_msat):
|
||||
node_stub.SetSellPrice(
|
||||
squeak_admin_pb2.SetSellPriceRequest(
|
||||
price_msat=price_msat,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def clear_sell_price(node_stub):
|
||||
node_stub.ClearSellPrice(
|
||||
squeak_admin_pb2.ClearSellPriceRequest()
|
||||
)
|
||||
|
||||
|
||||
def get_sell_price(node_stub):
|
||||
get_sell_price_response = node_stub.GetSellPrice(
|
||||
squeak_admin_pb2.GetSellPriceRequest()
|
||||
)
|
||||
return get_sell_price_response
|
||||
|
||||
|
||||
def set_twitter_bearer_token(node_stub, bearer_token):
|
||||
node_stub.SetTwitterBearerToken(
|
||||
squeak_admin_pb2.SetTwitterBearerTokenRequest(
|
||||
|
|
|
|||
|
|
@ -336,6 +336,18 @@ service SqueakAdmin {
|
|||
*/
|
||||
rpc GetDefaultPeerPort (GetDefaultPeerPortRequest) returns (GetDefaultPeerPortReply) {}
|
||||
|
||||
/** sqkadmin: `setsellprice`
|
||||
*/
|
||||
rpc SetSellPrice (SetSellPriceRequest) returns (SetSellPriceReply) {}
|
||||
|
||||
/** sqkadmin: `clearsellprice`
|
||||
*/
|
||||
rpc ClearSellPrice (ClearSellPriceRequest) returns (ClearSellPriceReply) {}
|
||||
|
||||
/** sqkadmin: `getsellprice`
|
||||
*/
|
||||
rpc GetSellPrice (GetSellPriceRequest) returns (GetSellPriceReply) {}
|
||||
|
||||
/** sqkadmin: `settwitterbearertoken`
|
||||
*/
|
||||
rpc SetTwitterBearerToken (SetTwitterBearerTokenRequest) returns (SetTwitterBearerTokenReply) {}
|
||||
|
|
@ -1262,6 +1274,34 @@ message GetDefaultPeerPortReply {
|
|||
int32 port = 1;
|
||||
}
|
||||
|
||||
message SetSellPriceRequest {
|
||||
/// The price in msats
|
||||
int64 price_msat = 1;
|
||||
}
|
||||
|
||||
message SetSellPriceReply {
|
||||
}
|
||||
|
||||
message ClearSellPriceRequest {
|
||||
}
|
||||
|
||||
message ClearSellPriceReply {
|
||||
}
|
||||
|
||||
message GetSellPriceRequest {
|
||||
}
|
||||
|
||||
message GetSellPriceReply {
|
||||
/// The price in msats
|
||||
int64 price_msat = 1;
|
||||
|
||||
/// Price is set
|
||||
bool price_msat_is_set = 2;
|
||||
|
||||
/// The defauly price in msats
|
||||
int64 default_price_msat = 3;
|
||||
}
|
||||
|
||||
message SetTwitterBearerTokenRequest {
|
||||
/// The bearer token string.
|
||||
string bearer_token = 1;
|
||||
|
|
|
|||
|
|
@ -1067,6 +1067,34 @@ class SqueakAdminServerHandler(object):
|
|||
port=default_peer_port,
|
||||
)
|
||||
|
||||
def handle_set_sell_price(self, request):
|
||||
sell_price_msat = request.price_msat
|
||||
logger.info("Handle set sell price msat to: {}".format(
|
||||
sell_price_msat,
|
||||
))
|
||||
self.squeak_controller.set_sell_price_msat(sell_price_msat)
|
||||
return squeak_admin_pb2.SetSellPriceReply()
|
||||
|
||||
def handle_clear_sell_price(self, request):
|
||||
logger.info("Handle clear sell price.")
|
||||
self.squeak_controller.clear_sell_price_msat()
|
||||
return squeak_admin_pb2.ClearSellPriceReply()
|
||||
|
||||
def handle_get_sell_price(self, request):
|
||||
logger.info("Handle get sell price")
|
||||
sell_price_msat = self.squeak_controller.get_sell_price_msat()
|
||||
price_msat_is_set = sell_price_msat is not None
|
||||
default_sell_price_msat = self.squeak_controller.get_default_sell_price_msat()
|
||||
logger.info("sell price: {}".format(sell_price_msat))
|
||||
logger.info("price_msat_is_set: {}".format(price_msat_is_set))
|
||||
logger.info("default_sell_price_msat: {}".format(
|
||||
default_sell_price_msat))
|
||||
return squeak_admin_pb2.GetSellPriceReply(
|
||||
price_msat=sell_price_msat,
|
||||
price_msat_is_set=price_msat_is_set,
|
||||
default_price_msat=default_sell_price_msat,
|
||||
)
|
||||
|
||||
def handle_set_twitter_bearer_token(self, request):
|
||||
twitter_bearer_token = request.bearer_token
|
||||
logger.info("Handle set twitter bearer token with value: {}".format(
|
||||
|
|
|
|||
|
|
@ -386,6 +386,15 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
|
|||
def GetDefaultPeerPort(self, request, context):
|
||||
return self.handler.handle_get_default_peer_port(request)
|
||||
|
||||
def SetSellPrice(self, request, context):
|
||||
return self.handler.handle_set_sell_price(request)
|
||||
|
||||
def ClearSellPrice(self, request, context):
|
||||
return self.handler.handle_clear_sell_price(request)
|
||||
|
||||
def GetSellPrice(self, request, context):
|
||||
return self.handler.handle_get_sell_price(request)
|
||||
|
||||
def SetTwitterBearerToken(self, request, context):
|
||||
return self.handler.handle_set_twitter_bearer_token(request)
|
||||
|
||||
|
|
|
|||
|
|
@ -529,6 +529,24 @@ def create_app(handler, username, password):
|
|||
def getdefaultpeerport(msg):
|
||||
return handler.handle_get_default_peer_port(msg)
|
||||
|
||||
@app.route("/setsellprice", methods=["POST"])
|
||||
@login_required
|
||||
@protobuf_serialized(squeak_admin_pb2.SetSellPriceRequest())
|
||||
def setsellprice(msg):
|
||||
return handler.handle_set_sell_price(msg)
|
||||
|
||||
@app.route("/clearsellprice", methods=["POST"])
|
||||
@login_required
|
||||
@protobuf_serialized(squeak_admin_pb2.ClearSellPriceRequest())
|
||||
def clearsellprice(msg):
|
||||
return handler.handle_clear_sell_price(msg)
|
||||
|
||||
@app.route("/getsellprice", methods=["POST"])
|
||||
@login_required
|
||||
@protobuf_serialized(squeak_admin_pb2.GetSellPriceRequest())
|
||||
def getsellprice(msg):
|
||||
return handler.handle_get_sell_price(msg)
|
||||
|
||||
@app.route("/settwitterbearertoken", methods=["POST"])
|
||||
@login_required
|
||||
@protobuf_serialized(squeak_admin_pb2.SetTwitterBearerTokenRequest())
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"files": {
|
||||
"main.js": "/static/js/main.9f21cade.chunk.js",
|
||||
"main.js.map": "/static/js/main.9f21cade.chunk.js.map",
|
||||
"main.js": "/static/js/main.944b5f3d.chunk.js",
|
||||
"main.js.map": "/static/js/main.944b5f3d.chunk.js.map",
|
||||
"runtime-main.js": "/static/js/runtime-main.9f0ba400.js",
|
||||
"runtime-main.js.map": "/static/js/runtime-main.9f0ba400.js.map",
|
||||
"static/css/2.ea4ba2f0.chunk.css": "/static/css/2.ea4ba2f0.chunk.css",
|
||||
"static/js/2.8ecc8a7d.chunk.js": "/static/js/2.8ecc8a7d.chunk.js",
|
||||
"static/js/2.8ecc8a7d.chunk.js.map": "/static/js/2.8ecc8a7d.chunk.js.map",
|
||||
"static/js/2.180244ea.chunk.js": "/static/js/2.180244ea.chunk.js",
|
||||
"static/js/2.180244ea.chunk.js.map": "/static/js/2.180244ea.chunk.js.map",
|
||||
"index.html": "/index.html",
|
||||
"precache-manifest.2a415349b9ea2acdcf58091b9ea62b17.js": "/precache-manifest.2a415349b9ea2acdcf58091b9ea62b17.js",
|
||||
"precache-manifest.4e0fbc5da97436978be7e48838bc6485.js": "/precache-manifest.4e0fbc5da97436978be7e48838bc6485.js",
|
||||
"service-worker.js": "/service-worker.js",
|
||||
"static/css/2.ea4ba2f0.chunk.css.map": "/static/css/2.ea4ba2f0.chunk.css.map",
|
||||
"static/js/2.8ecc8a7d.chunk.js.LICENSE.txt": "/static/js/2.8ecc8a7d.chunk.js.LICENSE.txt",
|
||||
"static/js/2.180244ea.chunk.js.LICENSE.txt": "/static/js/2.180244ea.chunk.js.LICENSE.txt",
|
||||
"static/media/font-awesome.min.css": "/static/media/fontawesome-webfont.fee66e71.woff",
|
||||
"static/media/google.svg": "/static/media/google.695a3160.svg",
|
||||
"static/media/logo.svg": "/static/media/logo.a0185b04.svg"
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"entrypoints": [
|
||||
"static/js/runtime-main.9f0ba400.js",
|
||||
"static/css/2.ea4ba2f0.chunk.css",
|
||||
"static/js/2.8ecc8a7d.chunk.js",
|
||||
"static/js/main.9f21cade.chunk.js"
|
||||
"static/js/2.180244ea.chunk.js",
|
||||
"static/js/main.944b5f3d.chunk.js"
|
||||
]
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Squeaknode</title><meta name="description" content="Squeaknode is a frontend for accessing a squeak node"><meta name="keywords" content="squeak, bitcoin, lightning"><meta name="author" content="Flatlogic LLC."><link href="/static/css/2.ea4ba2f0.chunk.css" rel="stylesheet"></head><body style="font-family:Roboto,sans-serif"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,l=r[0],a=r[1],i=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var l=this["webpackJsonpsqueak-node-frontend"]=this["webpackJsonpsqueak-node-frontend"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var i=0;i<l.length;i++)r(l[i]);var p=a;t()}([])</script><script src="/static/js/2.8ecc8a7d.chunk.js"></script><script src="/static/js/main.9f21cade.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Squeaknode</title><meta name="description" content="Squeaknode is a frontend for accessing a squeak node"><meta name="keywords" content="squeak, bitcoin, lightning"><meta name="author" content="Flatlogic LLC."><link href="/static/css/2.ea4ba2f0.chunk.css" rel="stylesheet"></head><body style="font-family:Roboto,sans-serif"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,l=r[0],a=r[1],i=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var l=this["webpackJsonpsqueak-node-frontend"]=this["webpackJsonpsqueak-node-frontend"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var i=0;i<l.length;i++)r(l[i]);var p=a;t()}([])</script><script src="/static/js/2.180244ea.chunk.js"></script><script src="/static/js/main.944b5f3d.chunk.js"></script></body></html>
|
||||
|
|
@ -1,23 +1,23 @@
|
|||
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
{
|
||||
"revision": "4130c19a04cad84365d566866ca6d862",
|
||||
"revision": "3f3a1b0ed1a3bea48e2734d35986cea1",
|
||||
"url": "/index.html"
|
||||
},
|
||||
{
|
||||
"revision": "e75dba031da32477061e",
|
||||
"revision": "36d7b205176816f91b11",
|
||||
"url": "/static/css/2.ea4ba2f0.chunk.css"
|
||||
},
|
||||
{
|
||||
"revision": "e75dba031da32477061e",
|
||||
"url": "/static/js/2.8ecc8a7d.chunk.js"
|
||||
"revision": "36d7b205176816f91b11",
|
||||
"url": "/static/js/2.180244ea.chunk.js"
|
||||
},
|
||||
{
|
||||
"revision": "ce334ad48c4dc2f747f813c26790dfba",
|
||||
"url": "/static/js/2.8ecc8a7d.chunk.js.LICENSE.txt"
|
||||
"url": "/static/js/2.180244ea.chunk.js.LICENSE.txt"
|
||||
},
|
||||
{
|
||||
"revision": "6d9bdb1984d80a44b61e",
|
||||
"url": "/static/js/main.9f21cade.chunk.js"
|
||||
"revision": "229ed8e46986393e095e",
|
||||
"url": "/static/js/main.944b5f3d.chunk.js"
|
||||
},
|
||||
{
|
||||
"revision": "cc9816de5a8639d377ea",
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||
|
||||
importScripts(
|
||||
"/precache-manifest.2a415349b9ea2acdcf58091b9ea62b17.js"
|
||||
"/precache-manifest.4e0fbc5da97436978be7e48838bc6485.js"
|
||||
);
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -27,3 +27,4 @@ class UserConfig(NamedTuple):
|
|||
"""Represents a config for a user."""
|
||||
username: str
|
||||
twitter_bearer_token: Optional[str] = None
|
||||
sell_price_msat: Optional[int] = None
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2020 Jonathan Zernik
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
"""Add sell price column to config table.
|
||||
|
||||
Revision ID: 5bd8f4075339
|
||||
Revises: c2d80c9fcbfa
|
||||
Create Date: 2021-11-08 21:04:08.193324
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5bd8f4075339'
|
||||
down_revision = 'c2d80c9fcbfa'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table('config', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('sell_price_msat',
|
||||
sa.Integer(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table('config', schema=None) as batch_op:
|
||||
batch_op.drop_column('sell_price_msat')
|
||||
|
|
@ -186,6 +186,7 @@ class Models:
|
|||
self.metadata,
|
||||
Column("username", String, primary_key=True),
|
||||
Column("twitter_bearer_token", String, nullable=True),
|
||||
Column("sell_price_msat", Integer, nullable=True),
|
||||
)
|
||||
|
||||
self.twitter_accounts = Table(
|
||||
|
|
|
|||
|
|
@ -1403,6 +1403,26 @@ class SqueakDb:
|
|||
with self.get_connection() as connection:
|
||||
connection.execute(stmt)
|
||||
|
||||
def set_config_sell_price_msat(self, username: str, sell_price_msat: int) -> None:
|
||||
""" Set a config sell price msat. """
|
||||
stmt = (
|
||||
self.configs.update()
|
||||
.where(self.configs.c.username == username)
|
||||
.values(sell_price_msat=sell_price_msat)
|
||||
)
|
||||
with self.get_connection() as connection:
|
||||
connection.execute(stmt)
|
||||
|
||||
def clear_config_sell_price_msat(self, username: str) -> None:
|
||||
""" Clear a config sell price msat. """
|
||||
stmt = (
|
||||
self.configs.update()
|
||||
.where(self.configs.c.username == username)
|
||||
.values(sell_price_msat=None)
|
||||
)
|
||||
with self.get_connection() as connection:
|
||||
connection.execute(stmt)
|
||||
|
||||
def insert_twitter_account(self, twitter_account: TwitterAccount) -> Optional[int]:
|
||||
""" Insert a new twitter account mapping to a squeak profile.
|
||||
|
||||
|
|
@ -1595,6 +1615,7 @@ class SqueakDb:
|
|||
return UserConfig(
|
||||
username=row["username"],
|
||||
twitter_bearer_token=row["twitter_bearer_token"],
|
||||
sell_price_msat=row["sell_price_msat"],
|
||||
)
|
||||
|
||||
def _parse_twitter_account_entry(self, row) -> TwitterAccountEntry:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from squeaknode.config.config import SqueaknodeConfig
|
|||
from squeaknode.core.peer_address import PeerAddress
|
||||
from squeaknode.core.squeak_peer import SqueakPeer
|
||||
from squeaknode.core.squeak_profile import SqueakProfile
|
||||
from squeaknode.core.user_config import UserConfig
|
||||
from squeaknode.db.squeak_db import SqueakDb
|
||||
|
||||
|
||||
|
|
@ -53,6 +54,10 @@ class PricePolicy:
|
|||
squeak_profile = self.get_profile(squeak_address)
|
||||
if squeak_profile is not None and squeak_profile.use_custom_price:
|
||||
return squeak_profile.custom_price_msat
|
||||
# Return sell price from settings if configured
|
||||
sell_price = self.get_sell_price_msat()
|
||||
if sell_price is not None:
|
||||
return sell_price
|
||||
return self.get_default_price()
|
||||
|
||||
def get_peer(self, peer_address: PeerAddress) -> Optional[SqueakPeer]:
|
||||
|
|
@ -63,3 +68,14 @@ class PricePolicy:
|
|||
|
||||
def get_default_price(self) -> int:
|
||||
return self.config.node.price_msat
|
||||
|
||||
def get_user_config(self) -> Optional[UserConfig]:
|
||||
return self.squeak_db.get_config(
|
||||
username=self.config.webadmin.username,
|
||||
)
|
||||
|
||||
def get_sell_price_msat(self) -> Optional[int]:
|
||||
user_config = self.get_user_config()
|
||||
if user_config is None:
|
||||
return None
|
||||
return user_config.sell_price_msat
|
||||
|
|
|
|||
|
|
@ -858,6 +858,32 @@ class SqueakController:
|
|||
user_config = UserConfig(username=self.config.webadmin.username)
|
||||
return self.squeak_db.insert_config(user_config)
|
||||
|
||||
def set_sell_price_msat(self, sell_price_msat: int) -> None:
|
||||
self.insert_user_config()
|
||||
if sell_price_msat < 0:
|
||||
raise Exception("Sell price cannot be negative.")
|
||||
self.squeak_db.set_config_sell_price_msat(
|
||||
username=self.config.webadmin.username,
|
||||
sell_price_msat=sell_price_msat,
|
||||
)
|
||||
|
||||
def clear_sell_price_msat(self) -> None:
|
||||
self.insert_user_config()
|
||||
self.squeak_db.clear_config_sell_price_msat(
|
||||
username=self.config.webadmin.username,
|
||||
)
|
||||
|
||||
def get_sell_price_msat(self) -> Optional[int]:
|
||||
user_config = self.squeak_db.get_config(
|
||||
username=self.config.webadmin.username,
|
||||
)
|
||||
if user_config is None:
|
||||
return None
|
||||
return user_config.sell_price_msat
|
||||
|
||||
def get_default_sell_price_msat(self) -> int:
|
||||
return self.config.node.price_msat
|
||||
|
||||
def set_twitter_bearer_token(self, twitter_bearer_token: str) -> None:
|
||||
self.insert_user_config()
|
||||
self.squeak_db.set_config_twitter_bearer_token(
|
||||
|
|
|
|||
|
|
@ -423,6 +423,20 @@ def user_config_with_twitter_bearer_token_username(
|
|||
yield inserted_user_config_username
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user_config_with_sell_price_msat_username(
|
||||
squeak_db,
|
||||
user_config,
|
||||
inserted_user_config_username,
|
||||
price_msat,
|
||||
):
|
||||
squeak_db.set_config_sell_price_msat(
|
||||
inserted_user_config_username,
|
||||
price_msat,
|
||||
)
|
||||
yield inserted_user_config_username
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def twitter_account(inserted_signing_profile_id):
|
||||
yield TwitterAccount(
|
||||
|
|
@ -1608,6 +1622,17 @@ def test_set_twitter_bearer_token(
|
|||
assert retrieved_config.twitter_bearer_token == twitter_bearer_token
|
||||
|
||||
|
||||
def test_set_sell_price_msat(
|
||||
squeak_db,
|
||||
user_config_with_sell_price_msat_username,
|
||||
price_msat,
|
||||
):
|
||||
retrieved_config = squeak_db.get_config(
|
||||
user_config_with_sell_price_msat_username)
|
||||
|
||||
assert retrieved_config.sell_price_msat == price_msat
|
||||
|
||||
|
||||
def test_get_twitter_account(
|
||||
squeak_db,
|
||||
twitter_account,
|
||||
|
|
|
|||
|
|
@ -30,66 +30,91 @@ def price_policy():
|
|||
yield PricePolicy(None, None)
|
||||
|
||||
|
||||
def test_get_price(price_policy, squeak, peer_address):
|
||||
def test_get_price(price_policy, squeak, peer_address, user_config):
|
||||
with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \
|
||||
mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \
|
||||
mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \
|
||||
mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price:
|
||||
mock_get_peer.return_value = None
|
||||
mock_get_profile.return_value = None
|
||||
mock_get_user_config.return_value = user_config
|
||||
mock_get_default_price.return_value = 555
|
||||
|
||||
assert price_policy.get_price(squeak, peer_address) == 555
|
||||
|
||||
|
||||
def test_get_price_profile_custom_price(price_policy, squeak, peer_address, signing_profile):
|
||||
def test_get_price_profile_custom_price(price_policy, squeak, peer_address, signing_profile, user_config):
|
||||
with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \
|
||||
mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \
|
||||
mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \
|
||||
mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price:
|
||||
mock_get_peer.return_value = None
|
||||
mock_get_profile.return_value = signing_profile._replace(
|
||||
use_custom_price=True,
|
||||
custom_price_msat=54321,
|
||||
)
|
||||
mock_get_user_config.return_value = user_config
|
||||
mock_get_default_price.return_value = 555
|
||||
|
||||
assert price_policy.get_price(squeak, peer_address) == 54321
|
||||
|
||||
|
||||
def test_get_price_profile_no_custom_price(price_policy, squeak, peer_address, signing_profile):
|
||||
def test_get_price_profile_no_custom_price(price_policy, squeak, peer_address, signing_profile, user_config):
|
||||
with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \
|
||||
mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \
|
||||
mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \
|
||||
mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price:
|
||||
mock_get_peer.return_value = None
|
||||
mock_get_profile.return_value = signing_profile._replace(
|
||||
use_custom_price=False,
|
||||
custom_price_msat=54321,
|
||||
)
|
||||
mock_get_user_config.return_value = user_config
|
||||
mock_get_default_price.return_value = 555
|
||||
|
||||
assert price_policy.get_price(squeak, peer_address) == 555
|
||||
|
||||
|
||||
def test_get_price_profile_share_free_peer(price_policy, squeak, peer_address, peer):
|
||||
def test_get_price_profile_share_free_peer(price_policy, squeak, peer_address, peer, user_config):
|
||||
with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \
|
||||
mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \
|
||||
mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \
|
||||
mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price:
|
||||
mock_get_peer.return_value = peer._replace(
|
||||
share_for_free=True,
|
||||
)
|
||||
mock_get_profile.return_value = None
|
||||
mock_get_user_config.return_value = user_config
|
||||
mock_get_default_price.return_value = 555
|
||||
|
||||
assert price_policy.get_price(squeak, peer_address) == 0
|
||||
|
||||
|
||||
def test_get_price_profile_no_share_free_peer(price_policy, squeak, peer_address, peer):
|
||||
def test_get_price_profile_no_share_free_peer(price_policy, squeak, peer_address, peer, user_config):
|
||||
with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \
|
||||
mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \
|
||||
mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \
|
||||
mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price:
|
||||
mock_get_peer.return_value = peer._replace(
|
||||
share_for_free=False,
|
||||
)
|
||||
mock_get_profile.return_value = None
|
||||
mock_get_user_config.return_value = user_config
|
||||
mock_get_default_price.return_value = 555
|
||||
|
||||
assert price_policy.get_price(squeak, peer_address) == 555
|
||||
|
||||
|
||||
def test_get_price_sell_price_set(price_policy, squeak, peer_address, user_config):
|
||||
with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \
|
||||
mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \
|
||||
mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \
|
||||
mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price:
|
||||
mock_get_peer.return_value = None
|
||||
mock_get_profile.return_value = None
|
||||
mock_get_user_config.return_value = user_config._replace(
|
||||
sell_price_msat=7777,
|
||||
)
|
||||
mock_get_default_price.return_value = 555
|
||||
|
||||
assert price_policy.get_price(squeak, peer_address) == 7777
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue