feat: improve on create wallet frontend and api. (BREAKING CHANGE) (#3635)

This commit is contained in:
dni ⚡ 2025-12-08 11:09:43 +01:00 committed by GitHub
parent 3af3838995
commit 5f86627eae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 101 additions and 71 deletions

View file

@ -197,11 +197,14 @@ async def api_delete_wallet(
@wallet_router.post("") @wallet_router.post("")
async def api_create_wallet( async def api_create_wallet(
data: CreateWallet, data: CreateWallet, account_id: AccountId = Depends(check_account_id_exists)
key_info: WalletTypeInfo = Depends(require_admin_key),
) -> Wallet: ) -> Wallet:
if data.wallet_type == WalletType.LIGHTNING:
return await create_wallet(user_id=key_info.wallet.user, wallet_name=data.name) if data.wallet_type not in list(WalletType):
raise HTTPException(
HTTPStatus.BAD_REQUEST,
f"Wallet type {data.wallet_type} does not exist.",
)
if data.wallet_type == WalletType.LIGHTNING_SHARED: if data.wallet_type == WalletType.LIGHTNING_SHARED:
if not data.shared_wallet_id: if not data.shared_wallet_id:
@ -210,11 +213,9 @@ async def api_create_wallet(
"Shared wallet ID is required for shared wallets.", "Shared wallet ID is required for shared wallets.",
) )
return await create_lightning_shared_wallet( return await create_lightning_shared_wallet(
user_id=key_info.wallet.user, user_id=account_id.id,
source_wallet_id=data.shared_wallet_id, source_wallet_id=data.shared_wallet_id,
) )
raise HTTPException( # default WalletType.LIGHTNING:
HTTPStatus.BAD_REQUEST, return await create_wallet(user_id=account_id.id, wallet_name=data.name)
f"Unknown wallet type: {data.wallet_type}.",
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -129,14 +129,12 @@ window._lnbitsApi = {
getWallet(wallet) { getWallet(wallet) {
return this.request('get', '/api/v1/wallet', wallet.inkey) return this.request('get', '/api/v1/wallet', wallet.inkey)
}, },
createWallet(wallet, name, walletType, ops = {}) { createWallet(name, walletType, opts = {}) {
return this.request('post', '/api/v1/wallet', wallet.adminkey, { return this.request('post', '/api/v1/wallet', null, {
name: name, name: name,
wallet_type: walletType, wallet_type: walletType,
...ops ...opts
}).then(res => { }).catch(LNbits.utils.notifyApiError)
window.location = '/wallet?wal=' + res.data.id
})
}, },
updateWallet(name, wallet) { updateWallet(name, wallet) {
return this.request('patch', '/api/v1/wallet', wallet.adminkey, { return this.request('patch', '/api/v1/wallet', wallet.adminkey, {

View file

@ -26,6 +26,13 @@ window.app.component('lnbits-manage-wallet-list', {
} }
}, },
methods: { methods: {
openNewWalletDialog() {
if (this.g.user.walletInvitesCount) {
this.g.newWalletType = 'lightning-shared'
} else {
this.g.newWalletType = 'lightning'
}
},
onWebsocketMessage(ev) { onWebsocketMessage(ev) {
const data = JSON.parse(ev.data) const data = JSON.parse(ev.data)
if (!data.payment) { if (!data.payment) {

View file

@ -4,10 +4,27 @@ window.app.component('lnbits-wallet-new', {
data() { data() {
return { return {
walletTypes: [{label: 'Lightning Wallet', value: 'lightning'}], walletTypes: [{label: 'Lightning Wallet', value: 'lightning'}],
newWallet: {name: '', sharedWalletId: ''} wallet: {name: '', sharedWalletId: ''},
showNewWalletDialog: false
}
},
watch: {
'g.newWalletType'(val) {
if (val === null) return
this.showNewWalletDialog = true
},
showNewWalletDialog(val) {
if (val === true) return
this.reset()
} }
}, },
computed: { computed: {
isLightning() {
return this.g.newWalletType === 'lightning'
},
isLightningShared() {
return this.g.newWalletType === 'lightning-shared'
},
inviteWalletOptions() { inviteWalletOptions() {
return (this.g.user?.extra?.wallet_invite_requests || []).map(i => ({ return (this.g.user?.extra?.wallet_invite_requests || []).map(i => ({
label: `${i.to_wallet_name} (from ${i.from_user_name})`, label: `${i.to_wallet_name} (from ${i.from_user_name})`,
@ -16,11 +33,16 @@ window.app.component('lnbits-wallet-new', {
} }
}, },
methods: { methods: {
reset() {
this.showNewWalletDialog = false
this.g.newWalletType = null
this.wallet = {name: '', sharedWalletId: ''}
},
async submitRejectWalletInvitation() { async submitRejectWalletInvitation() {
try { try {
const inviteRequests = this.g.user.extra.wallet_invite_requests || [] const inviteRequests = this.g.user.extra.wallet_invite_requests || []
const invite = inviteRequests.find( const invite = inviteRequests.find(
invite => invite.to_wallet_id === this.newWallet.sharedWalletId invite => invite.to_wallet_id === this.wallet.sharedWalletId
) )
if (!invite) { if (!invite) {
Quasar.Notify.create({ Quasar.Notify.create({
@ -46,8 +68,8 @@ window.app.component('lnbits-wallet-new', {
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
} }
}, },
async submitAddWallet() { submitAddWallet() {
const data = this.newWallet const data = this.wallet
if (this.g.newWalletType === 'lightning' && !data.name) { if (this.g.newWalletType === 'lightning' && !data.name) {
this.$q.notify({ this.$q.notify({
message: 'Please enter a name for the wallet', message: 'Please enter a name for the wallet',
@ -55,7 +77,6 @@ window.app.component('lnbits-wallet-new', {
}) })
return return
} }
if (this.g.newWalletType === 'lightning-shared' && !data.sharedWalletId) { if (this.g.newWalletType === 'lightning-shared' && !data.sharedWalletId) {
this.$q.notify({ this.$q.notify({
message: 'Missing a shared wallet ID', message: 'Missing a shared wallet ID',
@ -63,19 +84,20 @@ window.app.component('lnbits-wallet-new', {
}) })
return return
} }
try { LNbits.api
await LNbits.api.createWallet( .createWallet(data.name, this.g.newWalletType, {
this.g.user.wallets[0], shared_wallet_id: data.sharedWalletId
data.name, })
this.g.newWalletType, .then(res => {
{ this.$q.notify({
shared_wallet_id: data.sharedWalletId message: 'Wallet created successfully',
} color: 'positive'
) })
} catch (e) { this.reset()
console.warn(e) this.g.user.wallets.push(LNbits.map.wallet(res.data))
LNbits.utils.notifyApiError(e) this.g.lastWalletId = res.data.id
} this.$router.push(`/wallet/${res.data.id}`)
})
} }
}, },
created() { created() {

View file

@ -24,8 +24,6 @@ window.g = Vue.reactive({
fiatTracking: false, fiatTracking: false,
payments: [], payments: [],
walletEventListeners: [], walletEventListeners: [],
showNewWalletDialog: false,
newWalletType: 'lightning',
updatePayments: false, // used for updating the lnbits-payment-list updatePayments: false, // used for updating the lnbits-payment-list
updatePaymentsHash: false, // used for closing the receive dialog updatePaymentsHash: false, // used for closing the receive dialog
currencies: WINDOW_SETTINGS.LNBITS_CURRENCIES ?? [], currencies: WINDOW_SETTINGS.LNBITS_CURRENCIES ?? [],
@ -57,7 +55,8 @@ window.g = Vue.reactive({
ads: WINDOW_SETTINGS.AD_SPACE.split(',').map(ad => ad.split(';')), ads: WINDOW_SETTINGS.AD_SPACE.split(',').map(ad => ad.split(';')),
denomination: WINDOW_SETTINGS.LNBITS_DENOMINATION, denomination: WINDOW_SETTINGS.LNBITS_DENOMINATION,
isSatsDenomination: WINDOW_SETTINGS.LNBITS_DENOMINATION == 'sats', isSatsDenomination: WINDOW_SETTINGS.LNBITS_DENOMINATION == 'sats',
scanner: null scanner: null,
newWalletType: null
}) })
window.dateFormat = 'YYYY-MM-DD HH:mm' window.dateFormat = 'YYYY-MM-DD HH:mm'

View file

@ -1,8 +1 @@
window.windowMixin = { window.windowMixin = {}
methods: {
openNewWalletDialog(walletType = 'lightning') {
this.g.newWalletType = walletType
this.g.showNewWalletDialog = true
}
}
}

View file

@ -18,7 +18,7 @@
<q-card-section class="flex flex-center column full-height text-center"> <q-card-section class="flex flex-center column full-height text-center">
<div> <div>
<q-btn <q-btn
@click="openNewWalletDialog()" @click="g.newWalletType = 'lightning'"
round round
color="primary" color="primary"
icon="add" icon="add"
@ -29,7 +29,7 @@
<div> <div>
<q-badge <q-badge
v-if="g.user.walletInvitesCount" v-if="g.user.walletInvitesCount"
@click="openNewWalletDialog('lightning-shared')" @click="g.newWalletType = 'lightning-shared'"
dense dense
outline outline
class="q-mt-sm" class="q-mt-sm"

View file

@ -61,14 +61,7 @@
></q-item-label> ></q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item <q-item clickable @click="openNewWalletDialog">
clickable
@click="
g.user.walletInvitesCount
? openNewWalletDialog('lightning-shared')
: openNewWalletDialog('lightning')
"
>
<q-item-section side> <q-item-section side>
<q-icon name="add" color="grey-5" size="md"></q-icon> <q-icon name="add" color="grey-5" size="md"></q-icon>
</q-item-section> </q-item-section>

View file

@ -1,5 +1,5 @@
<template id="lnbits-wallet-new"> <template id="lnbits-wallet-new">
<q-dialog v-model="g.showNewWalletDialog" position="top"> <q-dialog v-model="showNewWalletDialog" position="top">
<q-card class="q-pa-lg q-pt-md lnbits__dialog-card"> <q-card class="q-pa-lg q-pt-md lnbits__dialog-card">
<q-card-section> <q-card-section>
<div class="text-h6"> <div class="text-h6">
@ -33,18 +33,17 @@
dense dense
></q-select> ></q-select>
<q-input <q-input
v-if="g.newWalletType == 'lightning'" v-if="isLightning"
dense dense
v-model="newWallet.name" v-model="wallet.name"
:label="$t('wallet_name')" :label="$t('wallet_name')"
autofocus autofocus
@keyup.enter="submitAddWallet()" @keyup.enter="submitAddWallet()"
class="q-mt-md" class="q-mt-md"
></q-input> ></q-input>
<q-select <q-select
v-if="g.newWalletType == 'lightning-shared'" v-if="isLightningShared"
v-model="newWallet.sharedWalletId" v-model="wallet.sharedWalletId"
:label="$t('shared_wallet_id')" :label="$t('shared_wallet_id')"
emit-value emit-value
map-options map-options
@ -52,7 +51,7 @@
:options="inviteWalletOptions" :options="inviteWalletOptions"
class="q-mt-md" class="q-mt-md"
></q-select> ></q-select>
<div v-if="g.newWalletType == 'lightning-shared'" class="q-mt-md"> <div v-if="isLightningShared" class="q-mt-md">
<span v-text="$t('shared_wallet_desc')" class="q-mt-lg"></span> <span v-text="$t('shared_wallet_desc')" class="q-mt-lg"></span>
</div> </div>
</q-card-section> </q-card-section>
@ -69,8 +68,8 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<q-btn <q-btn
v-if="g.newWalletType == 'lightning-shared'" v-if="isLightningShared"
:disabled="!newWallet.sharedWalletId" :disabled="!wallet.sharedWalletId"
flat flat
:label="$t('reject_wallet')" :label="$t('reject_wallet')"
v-close-popup v-close-popup

View file

@ -6,7 +6,7 @@
<div class="row items-center justify-between q-gutter-xs"> <div class="row items-center justify-between q-gutter-xs">
<div class="col"> <div class="col">
<q-btn <q-btn
@click="openNewWalletDialog()" @click="g.newWalletType = 'lightning'"
:label="$t('add_wallet')" :label="$t('add_wallet')"
color="primary" color="primary"
> >

View file

@ -44,11 +44,28 @@ async def test_create_account(client, settings: Settings):
# check POST and DELETE /api/v1/wallet with adminkey and user token: # check POST and DELETE /api/v1/wallet with adminkey and user token:
# create additional wallet and delete it # create additional wallet and delete it
@pytest.mark.anyio @pytest.mark.anyio
async def test_create_wallet_and_delete( async def test_create_wallet_and_delete(client, user_headers_from):
client, adminkey_headers_from, user_headers_from tiny_id = shortuuid.uuid()[:8]
):
response = await client.post( response = await client.post(
"/api/v1/wallet", json={"name": "test"}, headers=adminkey_headers_from "/api/v1/auth/register",
json={
"username": f"u21.{tiny_id}",
"password": "secret1234",
"password_repeat": "secret1234",
"email": f"u21.{tiny_id}@lnbits.com",
},
)
client.cookies.clear()
access_token = response.json().get("access_token")
assert response.status_code == 200, "User created."
assert response.json().get("access_token") is not None
response = await client.post(
"/api/v1/wallet",
json={"name": "test"},
headers={"Authorization": f"Bearer {access_token}"},
) )
assert response.status_code == 200 assert response.status_code == 200
result = response.json() result = response.json()
@ -58,6 +75,7 @@ async def test_create_wallet_and_delete(
assert "id" in result assert "id" in result
assert "adminkey" in result assert "adminkey" in result
# should not work with admin key only with user
invalid_response = await client.delete( invalid_response = await client.delete(
f"/api/v1/wallet/{result['id']}", f"/api/v1/wallet/{result['id']}",
headers={ headers={
@ -69,7 +87,7 @@ async def test_create_wallet_and_delete(
response = await client.delete( response = await client.delete(
f"/api/v1/wallet/{result['id']}", f"/api/v1/wallet/{result['id']}",
headers=user_headers_from, headers={"Authorization": f"Bearer {access_token}"},
) )
assert response.status_code == 200 assert response.status_code == 200