From 9bbf2056e98b9af82ed5df4b27ab73ee2e9038d1 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 5 Jul 2024 21:00:37 +0200 Subject: [PATCH] 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 --- api/resolvers/wallet.js | 24 ++++++------------- api/typeDefs/wallet.js | 6 +++-- components/autowithdraw-shared.js | 7 +++--- components/wallet/index.js | 19 ++++++++++----- fragments/wallet.js | 1 + lib/validate.js | 2 +- pages/settings/wallets/[wallet].js | 18 +++++++------- pages/settings/wallets/index.js | 17 +++++++++++-- .../migration.sql | 2 ++ prisma/schema.prisma | 1 + worker/autowithdraw.js | 6 ++++- 11 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 prisma/migrations/20240705062557_wallet_enabled/migration.sql diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index c73e0dc7..dfec9a62 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -627,7 +627,7 @@ async function upsertWallet ( } const { id, ...walletData } = data - const { autoWithdrawThreshold, autoWithdrawMaxFeePercent, priority } = settings + const { autoWithdrawThreshold, autoWithdrawMaxFeePercent, enabled, priority } = settings const txs = [ 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) { txs.push( models.wallet.update({ where: { id: Number(id), userId: me.id }, data: { - priority: priority ? 1 : 0, + enabled, + priority, [wallet.field]: { update: { where: { walletId: Number(id) }, @@ -670,7 +659,8 @@ async function upsertWallet ( txs.push( models.wallet.create({ data: { - priority: Number(priority), + enabled, + priority, userId: me.id, type: wallet.type, [wallet.field]: { @@ -694,8 +684,8 @@ async function upsertWallet ( data: { userId: me.id, wallet: wallet.type, - level: priority ? 'SUCCESS' : 'INFO', - message: priority ? 'wallet enabled' : 'wallet disabled' + level: enabled ? 'SUCCESS' : 'INFO', + message: enabled ? 'wallet enabled' : 'wallet disabled' } }) ) diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index faedb976..00fe0cc5 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -30,7 +30,8 @@ export default gql` id: ID! createdAt: Date! type: String! - priority: Boolean! + enabled: Boolean! + priority: Int! wallet: WalletDetails! } @@ -55,7 +56,8 @@ export default gql` input AutowithdrawSettings { autoWithdrawThreshold: Int! autoWithdrawMaxFeePercent: Float! - priority: Boolean! + priority: Int + enabled: Boolean } type Invoice { diff --git a/components/autowithdraw-shared.js b/components/autowithdraw-shared.js index d5c844fd..e5f28244 100644 --- a/components/autowithdraw-shared.js +++ b/components/autowithdraw-shared.js @@ -8,9 +8,8 @@ function autoWithdrawThreshold ({ me }) { return isNumber(me?.privates?.autoWithdrawThreshold) ? me?.privates?.autoWithdrawThreshold : 10000 } -export function autowithdrawInitial ({ me, priority = false }) { +export function autowithdrawInitial ({ me }) { return { - priority, autoWithdrawThreshold: autoWithdrawThreshold({ me }), autoWithdrawMaxFeePercent: isNumber(me?.privates?.autoWithdrawMaxFeePercent) ? me?.privates?.autoWithdrawMaxFeePercent : 1 } @@ -31,8 +30,8 @@ export function AutowithdrawSettings ({ wallet }) {
diff --git a/components/wallet/index.js b/components/wallet/index.js index afe6ab08..dc4558bc 100644 --- a/components/wallet/index.js +++ b/components/wallet/index.js @@ -32,7 +32,7 @@ export function useWallet (name) { const [config, saveConfig, clearConfig] = useConfig(wallet) 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 priority = config?.priority @@ -81,9 +81,9 @@ export function useWallet (name) { }, [_isConfigured, saveConfig, me, logger]) // delete is a reserved keyword - const delete_ = useCallback(() => { + const delete_ = useCallback(async () => { try { - clearConfig() + await clearConfig() logger.ok('wallet detached') disable() } 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 walletId = data?.walletByType?.id - const serverConfig = { id: walletId, priority: data?.walletByType?.priority, ...data?.walletByType?.wallet } - const autowithdrawSettings = autowithdrawInitial({ me, priority: serverConfig?.priority }) + const serverConfig = { + id: walletId, + priority: data?.walletByType?.priority, + enabled: data?.walletByType?.enabled, + ...data?.walletByType?.wallet + } + const autowithdrawSettings = autowithdrawInitial({ me }) const config = { ...serverConfig, ...autowithdrawSettings } const saveConfig = useCallback(async ({ autoWithdrawThreshold, autoWithdrawMaxFeePercent, priority, + enabled, ...config }) => { try { @@ -179,7 +185,8 @@ function useServerConfig (wallet) { settings: { autoWithdrawThreshold: Number(autoWithdrawThreshold), autoWithdrawMaxFeePercent: Number(autoWithdrawMaxFeePercent), - priority: !!priority + priority, + enabled } } }) diff --git a/fragments/wallet.js b/fragments/wallet.js index 6acf0c3e..329e6c1d 100644 --- a/fragments/wallet.js +++ b/fragments/wallet.js @@ -160,6 +160,7 @@ export const WALLET_BY_TYPE = gql` walletByType(type: $type) { id createdAt + enabled priority type wallet { diff --git a/lib/validate.js b/lib/validate.js index d80f38c2..b693ae3e 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -423,7 +423,7 @@ export function CLNAutowithdrawSchema ({ me } = {}) { export function autowithdrawSchemaMembers ({ me } = {}) { 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))}`), autoWithdrawMaxFeePercent: floatValidator.required('required').min(0, 'must be at least 0').max(50, 'must not exceed 50') } diff --git a/pages/settings/wallets/[wallet].js b/pages/settings/wallets/[wallet].js index 27a90ef0..7ee64189 100644 --- a/pages/settings/wallets/[wallet].js +++ b/pages/settings/wallets/[wallet].js @@ -22,9 +22,11 @@ export default function WalletSettings () { const wallet = useWallet(name) 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 - // 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 { ...acc, [field.name]: wallet.config?.[field.name] || '' @@ -39,22 +41,18 @@ export default function WalletSettings () {
{ + onSubmit={async (values) => { try { const newConfig = !wallet.isConfigured // 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) { - values.priority = true - enabled = true + values.enabled = true } await wallet.save(values) - if (enabled) wallet.enable() + if (values.enabled) wallet.enable() else wallet.disable() toaster.success('saved settings') @@ -80,7 +78,7 @@ export default function WalletSettings () { { try { - wallet.delete() + await wallet.delete() toaster.success('saved settings') router.push('/settings/wallets') } catch (err) { diff --git a/pages/settings/wallets/index.js b/pages/settings/wallets/index.js index 9951a5d2..1bec8d13 100644 --- a/pages/settings/wallets/index.js +++ b/pages/settings/wallets/index.js @@ -64,10 +64,23 @@ export default function Wallet ({ ssrData }) {
{wallets .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 + } 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) => { const draggable = w.isConfigured diff --git a/prisma/migrations/20240705062557_wallet_enabled/migration.sql b/prisma/migrations/20240705062557_wallet_enabled/migration.sql new file mode 100644 index 00000000..519ef61e --- /dev/null +++ b/prisma/migrations/20240705062557_wallet_enabled/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Wallet" ADD COLUMN "enabled" BOOLEAN NOT NULL DEFAULT true; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a0f1834f..653e59ce 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -173,6 +173,7 @@ model Wallet { updatedAt DateTime @default(now()) @updatedAt @map("updated_at") userId Int label String? + enabled Boolean @default(true) priority Int @default(0) user User @relation(fields: [userId], references: [id], onDelete: Cascade) diff --git a/worker/autowithdraw.js b/worker/autowithdraw.js index 5f35c96d..1a75fc3e 100644 --- a/worker/autowithdraw.js +++ b/worker/autowithdraw.js @@ -42,7 +42,11 @@ export async function autoWithdraw ({ data: { id }, models, lnd }) { // get the wallets in order of priority const wallets = await models.wallet.findMany({ 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) {