Save dedicated enabled flag for server wallets

* wallet table now contains boolean column 'enabled'
* 'priority' is now a number everywhere
* use consistent order between how autowithdrawals are attempted and server wallets cards
This commit is contained in:
ekzyis 2024-07-05 21:00:37 +02:00
parent 8acf74c787
commit 9bbf2056e9
11 changed files with 60 additions and 43 deletions

View File

@ -627,7 +627,7 @@ async function upsertWallet (
} }
const { id, ...walletData } = data const { id, ...walletData } = data
const { autoWithdrawThreshold, autoWithdrawMaxFeePercent, priority } = settings const { autoWithdrawThreshold, autoWithdrawMaxFeePercent, enabled, priority } = settings
const txs = [ const txs = [
models.user.update({ models.user.update({
@ -639,24 +639,13 @@ async function upsertWallet (
}) })
] ]
if (priority) {
txs.push(
models.wallet.updateMany({
where: {
userId: me.id
},
data: {
priority: 0
}
}))
}
if (id) { if (id) {
txs.push( txs.push(
models.wallet.update({ models.wallet.update({
where: { id: Number(id), userId: me.id }, where: { id: Number(id), userId: me.id },
data: { data: {
priority: priority ? 1 : 0, enabled,
priority,
[wallet.field]: { [wallet.field]: {
update: { update: {
where: { walletId: Number(id) }, where: { walletId: Number(id) },
@ -670,7 +659,8 @@ async function upsertWallet (
txs.push( txs.push(
models.wallet.create({ models.wallet.create({
data: { data: {
priority: Number(priority), enabled,
priority,
userId: me.id, userId: me.id,
type: wallet.type, type: wallet.type,
[wallet.field]: { [wallet.field]: {
@ -694,8 +684,8 @@ async function upsertWallet (
data: { data: {
userId: me.id, userId: me.id,
wallet: wallet.type, wallet: wallet.type,
level: priority ? 'SUCCESS' : 'INFO', level: enabled ? 'SUCCESS' : 'INFO',
message: priority ? 'wallet enabled' : 'wallet disabled' message: enabled ? 'wallet enabled' : 'wallet disabled'
} }
}) })
) )

View File

@ -30,7 +30,8 @@ export default gql`
id: ID! id: ID!
createdAt: Date! createdAt: Date!
type: String! type: String!
priority: Boolean! enabled: Boolean!
priority: Int!
wallet: WalletDetails! wallet: WalletDetails!
} }
@ -55,7 +56,8 @@ export default gql`
input AutowithdrawSettings { input AutowithdrawSettings {
autoWithdrawThreshold: Int! autoWithdrawThreshold: Int!
autoWithdrawMaxFeePercent: Float! autoWithdrawMaxFeePercent: Float!
priority: Boolean! priority: Int
enabled: Boolean
} }
type Invoice { type Invoice {

View File

@ -8,9 +8,8 @@ function autoWithdrawThreshold ({ me }) {
return isNumber(me?.privates?.autoWithdrawThreshold) ? me?.privates?.autoWithdrawThreshold : 10000 return isNumber(me?.privates?.autoWithdrawThreshold) ? me?.privates?.autoWithdrawThreshold : 10000
} }
export function autowithdrawInitial ({ me, priority = false }) { export function autowithdrawInitial ({ me }) {
return { return {
priority,
autoWithdrawThreshold: autoWithdrawThreshold({ me }), autoWithdrawThreshold: autoWithdrawThreshold({ me }),
autoWithdrawMaxFeePercent: isNumber(me?.privates?.autoWithdrawMaxFeePercent) ? me?.privates?.autoWithdrawMaxFeePercent : 1 autoWithdrawMaxFeePercent: isNumber(me?.privates?.autoWithdrawMaxFeePercent) ? me?.privates?.autoWithdrawMaxFeePercent : 1
} }
@ -31,8 +30,8 @@ export function AutowithdrawSettings ({ wallet }) {
<Checkbox <Checkbox
disabled={!wallet.isConfigured} disabled={!wallet.isConfigured}
label='enabled' label='enabled'
id='priority' id='enabled'
name='priority' name='enabled'
/> />
<div className='my-4 border border-3 rounded'> <div className='my-4 border border-3 rounded'>
<div className='p-3'> <div className='p-3'>

View File

@ -32,7 +32,7 @@ export function useWallet (name) {
const [config, saveConfig, clearConfig] = useConfig(wallet) const [config, saveConfig, clearConfig] = useConfig(wallet)
const _isConfigured = isConfigured({ ...wallet, config }) const _isConfigured = isConfigured({ ...wallet, config })
const status = (config?.enabled || config?.priority) ? Status.Enabled : Status.Initialized const status = config?.enabled ? Status.Enabled : Status.Initialized
const enabled = status === Status.Enabled const enabled = status === Status.Enabled
const priority = config?.priority const priority = config?.priority
@ -81,9 +81,9 @@ export function useWallet (name) {
}, [_isConfigured, saveConfig, me, logger]) }, [_isConfigured, saveConfig, me, logger])
// delete is a reserved keyword // delete is a reserved keyword
const delete_ = useCallback(() => { const delete_ = useCallback(async () => {
try { try {
clearConfig() await clearConfig()
logger.ok('wallet detached') logger.ok('wallet detached')
disable() disable()
} catch (err) { } catch (err) {
@ -160,14 +160,20 @@ function useServerConfig (wallet) {
const { data, refetch: refetchConfig } = useQuery(WALLET_BY_TYPE, { variables: { type: wallet?.server?.walletType }, skip: !wallet?.server }) const { data, refetch: refetchConfig } = useQuery(WALLET_BY_TYPE, { variables: { type: wallet?.server?.walletType }, skip: !wallet?.server })
const walletId = data?.walletByType?.id const walletId = data?.walletByType?.id
const serverConfig = { id: walletId, priority: data?.walletByType?.priority, ...data?.walletByType?.wallet } const serverConfig = {
const autowithdrawSettings = autowithdrawInitial({ me, priority: serverConfig?.priority }) id: walletId,
priority: data?.walletByType?.priority,
enabled: data?.walletByType?.enabled,
...data?.walletByType?.wallet
}
const autowithdrawSettings = autowithdrawInitial({ me })
const config = { ...serverConfig, ...autowithdrawSettings } const config = { ...serverConfig, ...autowithdrawSettings }
const saveConfig = useCallback(async ({ const saveConfig = useCallback(async ({
autoWithdrawThreshold, autoWithdrawThreshold,
autoWithdrawMaxFeePercent, autoWithdrawMaxFeePercent,
priority, priority,
enabled,
...config ...config
}) => { }) => {
try { try {
@ -179,7 +185,8 @@ function useServerConfig (wallet) {
settings: { settings: {
autoWithdrawThreshold: Number(autoWithdrawThreshold), autoWithdrawThreshold: Number(autoWithdrawThreshold),
autoWithdrawMaxFeePercent: Number(autoWithdrawMaxFeePercent), autoWithdrawMaxFeePercent: Number(autoWithdrawMaxFeePercent),
priority: !!priority priority,
enabled
} }
} }
}) })

View File

@ -160,6 +160,7 @@ export const WALLET_BY_TYPE = gql`
walletByType(type: $type) { walletByType(type: $type) {
id id
createdAt createdAt
enabled
priority priority
type type
wallet { wallet {

View File

@ -423,7 +423,7 @@ export function CLNAutowithdrawSchema ({ me } = {}) {
export function autowithdrawSchemaMembers ({ me } = {}) { export function autowithdrawSchemaMembers ({ me } = {}) {
return { return {
priority: boolean(), enabled: boolean(),
autoWithdrawThreshold: intValidator.required('required').min(0, 'must be at least 0').max(msatsToSats(BALANCE_LIMIT_MSATS), `must be at most ${abbrNum(msatsToSats(BALANCE_LIMIT_MSATS))}`), autoWithdrawThreshold: intValidator.required('required').min(0, 'must be at least 0').max(msatsToSats(BALANCE_LIMIT_MSATS), `must be at most ${abbrNum(msatsToSats(BALANCE_LIMIT_MSATS))}`),
autoWithdrawMaxFeePercent: floatValidator.required('required').min(0, 'must be at least 0').max(50, 'must not exceed 50') autoWithdrawMaxFeePercent: floatValidator.required('required').min(0, 'must be at least 0').max(50, 'must not exceed 50')
} }

View File

@ -22,9 +22,11 @@ export default function WalletSettings () {
const wallet = useWallet(name) const wallet = useWallet(name)
const initial = wallet.fields.reduce((acc, field) => { const initial = wallet.fields.reduce((acc, field) => {
// we still need to run over all wallet fields via reduce // We still need to run over all wallet fields via reduce
// even though we use wallet.config as the initial value // even though we use wallet.config as the initial value
// since wallet.config is empty when wallet is not configured // since wallet.config is empty when wallet is not configured.
// Also, wallet.config includes general fields like
// 'enabled' and 'priority' which are not defined in wallet.fields.
return { return {
...acc, ...acc,
[field.name]: wallet.config?.[field.name] || '' [field.name]: wallet.config?.[field.name] || ''
@ -39,22 +41,18 @@ export default function WalletSettings () {
<Form <Form
initial={initial} initial={initial}
schema={wallet.schema} schema={wallet.schema}
onSubmit={async ({ enabled, ...values }) => { onSubmit={async (values) => {
try { try {
const newConfig = !wallet.isConfigured const newConfig = !wallet.isConfigured
// enable wallet if wallet was just configured // enable wallet if wallet was just configured
// local wallets use 'enabled' property
// server wallets use 'priority' property
// TODO: make both wallet types use 'priority' property
if (newConfig) { if (newConfig) {
values.priority = true values.enabled = true
enabled = true
} }
await wallet.save(values) await wallet.save(values)
if (enabled) wallet.enable() if (values.enabled) wallet.enable()
else wallet.disable() else wallet.disable()
toaster.success('saved settings') toaster.success('saved settings')
@ -80,7 +78,7 @@ export default function WalletSettings () {
<WalletButtonBar <WalletButtonBar
wallet={wallet} onDelete={async () => { wallet={wallet} onDelete={async () => {
try { try {
wallet.delete() await wallet.delete()
toaster.success('saved settings') toaster.success('saved settings')
router.push('/settings/wallets') router.push('/settings/wallets')
} catch (err) { } catch (err) {

View File

@ -64,10 +64,23 @@ export default function Wallet ({ ssrData }) {
<div className={styles.walletGrid} onDragEnd={onDragEnd}> <div className={styles.walletGrid} onDragEnd={onDragEnd}>
{wallets {wallets
.sort((w1, w2) => { .sort((w1, w2) => {
if (!w2.isConfigured || !w2.enabled) { // enabled/configured wallets always come before disabled/unconfigured wallets
if ((w1.enabled && !w2.enabled) || (w1.isConfigured && !w2.isConfigured)) {
return -1 return -1
} else if ((w2.enabled && !w1.enabled) || (w2.isConfigured && !w1.isConfigured)) {
return 1
} }
return w1.priority - w2.priority
const delta = w1.priority - w2.priority
// delta is NaN if either priority is undefined
if (!Number.isNaN(delta) && delta !== 0) return delta
// if both wallets have an id, use that as tie breaker
// since that's the order in which autowithdrawals are attempted
if (w1.id && w2.id) return Number(w1.id) - Number(w2.id)
// else we will use the card title as tie breaker
return w1.card.title < w2.card.title ? -1 : 1
}) })
.map((w, i) => { .map((w, i) => {
const draggable = w.isConfigured const draggable = w.isConfigured

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Wallet" ADD COLUMN "enabled" BOOLEAN NOT NULL DEFAULT true;

View File

@ -173,6 +173,7 @@ model Wallet {
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
userId Int userId Int
label String? label String?
enabled Boolean @default(true)
priority Int @default(0) priority Int @default(0)
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)

View File

@ -42,7 +42,11 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) {
// get the wallets in order of priority // get the wallets in order of priority
const wallets = await models.wallet.findMany({ const wallets = await models.wallet.findMany({
where: { userId: user.id }, where: { userId: user.id },
orderBy: { priority: 'desc' } orderBy: [
{ priority: 'desc' },
// use id as tie breaker (older wallet first)
{ id: 'asc' }
]
}) })
for (const wallet of wallets) { for (const wallet of wallets) {