feat: parse nested pydantic models fetchone and fetchall + add shortcuts for insert_query and update_query into Database (#2714)
* feat: add shortcuts for insert_query and update_query into `Database`
example: await db.insert("table_name", base_model)
* remove where from argument
* chore: code clean-up
* extension manager
* lnbits-qrcode components
* parse date from dict
* refactor: make `settings` a fixture
* chore: remove verbose key names
* fix: time column
* fix: cast balance to `int`
* extension toggle vue3
* vue3 @input migration
* fix: payment extra and payment hash
* fix dynamic fields and ext db migration
* remove shadow on cards in dark theme
* screwed up and made more css pushes to this branch
* attempt to make chip component in settings dynamic fields
* dynamic chips
* qrscanner
* clean init admin settings
* make get_user better
* add dbversion model
* remove update_payment_status/extra/details
* traces for value and assertion errors
* refactor services
* add PaymentFiatAmount
* return Payment on api endpoints
* rename to get_user_from_account
* refactor: just refactor (#2740)
* rc5
* Fix db cache (#2741)
* [refactor] split services.py (#2742)
* refactor: spit `core.py` (#2743)
* refactor: make QR more customizable
* fix: print.html
* fix: qrcode options
* fix: white shadow on dark theme
* fix: datetime wasnt parsed in dict_to_model
* add timezone for conversion
* only parse timestamp for sqlite, postgres does it
* log internal payment success
* fix: export wallet to phone QR
* Adding a customisable border theme, like gradient (#2746)
* fixed mobile scan btn
* fix test websocket
* fix get_payments tests
* dict_to_model skip none values
* preimage none instead of defaulting to 0000...
* fixup test real invoice tests
* fixed pheonixd for wss
* fix nodemanager test settings
* fix lnbits funding
* only insert extension when they dont exist
---------
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
Co-authored-by: Arc <ben@arc.wales>
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
This commit is contained in:
co-authored by
Vlad Stan
Tiago Vasconcelos
Arc
Arc
parent
ae4eda04ba
commit
2940cf97c5
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
@@ -530,23 +530,26 @@ video {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.qrcode__wrapper canvas {
|
||||
.qrcode__wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qrcode__wrapper canvas {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
height: 100% !important;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.qrcode__image {
|
||||
position: absolute;
|
||||
max-width: 52px;
|
||||
width: 15%;
|
||||
height: 15%;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
left: 50%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 0.2rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
@@ -268,5 +268,6 @@ window.localisation.en = {
|
||||
contributors: 'Contributors',
|
||||
license: 'License',
|
||||
reset_key: 'Reset Key',
|
||||
reset_password: 'Reset Password'
|
||||
reset_password: 'Reset Password',
|
||||
border_choices: 'Border Choices'
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ window.app = Vue.createApp({
|
||||
'confettiFireworks',
|
||||
'confettiStars'
|
||||
],
|
||||
borderOptions: ['retro-border', 'hard-border', 'no-border'],
|
||||
tab: 'user',
|
||||
credentialsData: {
|
||||
show: false,
|
||||
@@ -63,6 +64,27 @@ window.app = Vue.createApp({
|
||||
this.$q.localStorage.set('lnbits.gradientBg', false)
|
||||
}
|
||||
},
|
||||
applyBorder: function () {
|
||||
slef = this
|
||||
if (this.borderChoice) {
|
||||
this.$q.localStorage.setItem('lnbits.border', this.borderChoice)
|
||||
}
|
||||
let borderStyle = this.$q.localStorage.getItem('lnbits.border')
|
||||
this.borderChoice = borderStyle
|
||||
let borderStyleCSS
|
||||
if (borderStyle == 'hard-border') {
|
||||
borderStyleCSS = `box-shadow: 0 0 0 1px rgba(0,0,0,.12), 0 0 0 1px #ffffff47; border: none;`
|
||||
}
|
||||
if (borderStyle == 'no-border') {
|
||||
borderStyleCSS = `box-shadow: none; border: none;`
|
||||
}
|
||||
if (borderStyle == 'retro-border') {
|
||||
borderStyleCSS = `border: none; border-color: rgba(255, 255, 255, 0.28); box-shadow: 0 1px 5px rgba(255, 255, 255, 0.2), 0 2px 2px rgba(255, 255, 255, 0.14), 0 3px 1px -2px rgba(255, 255, 255, 0.12);`
|
||||
}
|
||||
let style = document.createElement('style')
|
||||
style.innerHTML = `body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card.q-card--dark, .q-date--dark { ${borderStyleCSS} }`
|
||||
document.head.appendChild(style)
|
||||
},
|
||||
toggleGradient: function () {
|
||||
this.gradientChoice = !this.gradientChoice
|
||||
this.applyGradient()
|
||||
@@ -92,7 +114,7 @@ window.app = Vue.createApp({
|
||||
user_id: this.user.id,
|
||||
username: this.user.username,
|
||||
email: this.user.email,
|
||||
config: this.user.config
|
||||
extra: this.user.extra
|
||||
}
|
||||
)
|
||||
this.user = data
|
||||
@@ -183,12 +205,15 @@ window.app = Vue.createApp({
|
||||
const {data} = await LNbits.api.getAuthenticatedUser()
|
||||
this.user = data
|
||||
this.hasUsername = !!data.username
|
||||
if (!this.user.config) this.user.config = {}
|
||||
if (!this.user.extra) this.user.extra = {}
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
}
|
||||
if (this.$q.localStorage.getItem('lnbits.gradientBg')) {
|
||||
this.applyGradient()
|
||||
}
|
||||
if (this.$q.localStorage.getItem('lnbits.border')) {
|
||||
this.applyBorder()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -278,7 +278,7 @@ window.LNbits = {
|
||||
preimage: data.preimage,
|
||||
payment_hash: data.payment_hash,
|
||||
expiry: data.expiry,
|
||||
extra: data.extra,
|
||||
extra: data.extra ?? {},
|
||||
wallet_id: data.wallet_id,
|
||||
webhook: data.webhook,
|
||||
webhook_status: data.webhook_status,
|
||||
@@ -286,13 +286,10 @@ window.LNbits = {
|
||||
fiat_currency: data.fiat_currency
|
||||
}
|
||||
|
||||
obj.date = Quasar.date.formatDate(
|
||||
new Date(obj.time * 1000),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
)
|
||||
obj.date = Quasar.date.formatDate(new Date(obj.time), 'YYYY-MM-DD HH:mm')
|
||||
obj.dateFrom = moment(obj.date).fromNow()
|
||||
obj.expirydate = Quasar.date.formatDate(
|
||||
new Date(obj.expiry * 1000),
|
||||
new Date(obj.expiry),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
)
|
||||
obj.expirydateFrom = moment(obj.expirydate).fromNow()
|
||||
@@ -337,6 +334,12 @@ window.LNbits = {
|
||||
.join('')
|
||||
return hashHex
|
||||
},
|
||||
formatDate: function (timestamp) {
|
||||
return Quasar.date.formatDate(
|
||||
new Date(timestamp * 1000),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
)
|
||||
},
|
||||
formatCurrency: function (value, currency) {
|
||||
return new Intl.NumberFormat(window.LOCALE, {
|
||||
style: 'currency',
|
||||
@@ -476,6 +479,7 @@ window.windowMixin = {
|
||||
return {
|
||||
toggleSubs: true,
|
||||
reactionChoice: 'confettiBothSides',
|
||||
borderChoice: '',
|
||||
gradientChoice:
|
||||
this.$q.localStorage.getItem('lnbits.gradientBg') || false,
|
||||
isUserAuthorized: false,
|
||||
@@ -517,6 +521,30 @@ window.windowMixin = {
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
},
|
||||
applyBorder: function () {
|
||||
if (this.borderChoice) {
|
||||
this.$q.localStorage.setItem('lnbits.border', this.borderChoice)
|
||||
}
|
||||
let borderStyle = this.$q.localStorage.getItem('lnbits.border')
|
||||
if (!borderStyle) {
|
||||
this.$q.localStorage.set('lnbits.border', 'retro-border')
|
||||
borderStyle = 'hard-border'
|
||||
}
|
||||
this.borderChoice = borderStyle
|
||||
let borderStyleCSS
|
||||
if (borderStyle == 'hard-border') {
|
||||
borderStyleCSS = `box-shadow: 0 0 0 1px rgba(0,0,0,.12), 0 0 0 1px #ffffff47; border: none;`
|
||||
}
|
||||
if (borderStyle == 'no-border') {
|
||||
borderStyleCSS = `box-shadow: none; border: none;`
|
||||
}
|
||||
if (borderStyle == 'retro-border') {
|
||||
borderStyleCSS = `border: none; border-color: rgba(255, 255, 255, 0.28); box-shadow: 0 1px 5px rgba(255, 255, 255, 0.2), 0 2px 2px rgba(255, 255, 255, 0.14), 0 3px 1px -2px rgba(255, 255, 255, 0.12);`
|
||||
}
|
||||
let style = document.createElement('style')
|
||||
style.innerHTML = `body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card.q-card--dark, .q-date--dark { ${borderStyleCSS} }`
|
||||
document.head.appendChild(style)
|
||||
},
|
||||
setColors: function () {
|
||||
this.$q.localStorage.set(
|
||||
'lnbits.primaryColor',
|
||||
@@ -592,6 +620,7 @@ window.windowMixin = {
|
||||
const theme = params.get('theme')
|
||||
const darkMode = params.get('dark')
|
||||
const gradient = params.get('gradient')
|
||||
const border = params.get('border')
|
||||
|
||||
if (
|
||||
theme &&
|
||||
@@ -617,6 +646,9 @@ window.windowMixin = {
|
||||
this.$q.localStorage.set('lnbits.darkMode', true)
|
||||
}
|
||||
}
|
||||
if (border) {
|
||||
this.$q.localStorage.set('lnbits.border', border)
|
||||
}
|
||||
|
||||
// Remove processed parameters
|
||||
fields.forEach(param => params.delete(param))
|
||||
@@ -678,6 +710,7 @@ window.windowMixin = {
|
||||
}
|
||||
|
||||
this.applyGradient()
|
||||
this.applyBorder()
|
||||
|
||||
if (window.user) {
|
||||
this.g.user = Object.freeze(window.LNbits.map.user(window.user))
|
||||
|
||||
@@ -200,11 +200,25 @@ window.app.component('lnbits-qrcode', {
|
||||
components: {
|
||||
QrcodeVue
|
||||
},
|
||||
props: ['value'],
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
options: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
logo: LNBITS_QR_LOGO
|
||||
custom: {
|
||||
margin: 1,
|
||||
width: 350,
|
||||
size: 350,
|
||||
logo: LNBITS_QR_LOGO
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.custom = {...this.custom, ...this.options}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -405,7 +419,7 @@ window.app.component('lnbits-notifications-btn', {
|
||||
window.app.component('lnbits-dynamic-fields', {
|
||||
template: '#lnbits-dynamic-fields',
|
||||
mixins: [window.windowMixin],
|
||||
props: ['options', 'value'],
|
||||
props: ['options', 'modelValue'],
|
||||
data() {
|
||||
return {
|
||||
formData: null,
|
||||
@@ -427,11 +441,42 @@ window.app.component('lnbits-dynamic-fields', {
|
||||
}, {})
|
||||
},
|
||||
handleValueChanged() {
|
||||
this.$emit('input', this.formData)
|
||||
this.$emit('update:model-value', this.formData)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.formData = this.buildData(this.options, this.value)
|
||||
this.formData = this.buildData(this.options, this.modelValue)
|
||||
}
|
||||
})
|
||||
|
||||
window.app.component('lnbits-dynamic-chips', {
|
||||
template: '#lnbits-dynamic-chips',
|
||||
mixins: [window.windowMixin],
|
||||
props: ['modelValue'],
|
||||
data() {
|
||||
return {
|
||||
chip: '',
|
||||
chips: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addChip() {
|
||||
if (!this.chip) return
|
||||
this.chips.push(this.chip)
|
||||
this.chip = ''
|
||||
this.$emit('update:model-value', this.chips.join(','))
|
||||
},
|
||||
removeChip(index) {
|
||||
this.chips.splice(index, 1)
|
||||
this.$emit('update:model-value', this.chips.join(','))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (typeof this.modelValue === 'string') {
|
||||
this.chips = this.modelValue.split(',')
|
||||
} else {
|
||||
this.chips = [...this.modelValue]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -444,7 +489,7 @@ window.app.component('lnbits-update-balance', {
|
||||
return LNBITS_DENOMINATION
|
||||
},
|
||||
admin() {
|
||||
return this.g.user.admin
|
||||
return user.super_user
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
function shortenNodeId(nodeId) {
|
||||
return nodeId
|
||||
? nodeId.substring(0, 5) + '...' + nodeId.substring(nodeId.length - 5)
|
||||
: '...'
|
||||
}
|
||||
|
||||
window.app.component('lnbits-node-ranks', {
|
||||
props: ['ranks'],
|
||||
data: function () {
|
||||
@@ -141,7 +135,11 @@ window.app.component('lnbits-node-info', {
|
||||
},
|
||||
mixins: [window.windowMixin],
|
||||
methods: {
|
||||
shortenNodeId
|
||||
shortenNodeId(nodeId) {
|
||||
return nodeId
|
||||
? nodeId.substring(0, 5) + '...' + nodeId.substring(nodeId.length - 5)
|
||||
: '...'
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class='row items-baseline q-gutter-x-sm'>
|
||||
|
||||
+16
-23
@@ -165,32 +165,22 @@ window.app = Vue.createApp({
|
||||
type: 'bubble',
|
||||
options: {
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: 'linear',
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
},
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'Tx count'
|
||||
}
|
||||
x: {
|
||||
type: 'linear',
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
text: 'Transaction count'
|
||||
}
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
type: 'linear',
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
},
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'User balance in million sats'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
text: 'User balance in million sats'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
tooltips: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (tooltipItem, data) {
|
||||
const dataset = data.datasets[tooltipItem.datasetIndex]
|
||||
@@ -215,6 +205,9 @@ window.app = Vue.createApp({
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
formatDate: function (value) {
|
||||
return LNbits.utils.formatDate(value)
|
||||
},
|
||||
formatSat: function (value) {
|
||||
return LNbits.utils.formatSat(Math.floor(value / 1000))
|
||||
},
|
||||
|
||||
@@ -5,7 +5,10 @@ window.app = Vue.createApp({
|
||||
return {
|
||||
updatePayments: false,
|
||||
origin: window.location.origin,
|
||||
wallet: LNbits.map.wallet(window.wallet),
|
||||
user: LNbits.map.user(window.user),
|
||||
exportUrl: `${window.location.origin}/wallet?usr=${window.user.id}&wal=${window.wallet.id}`,
|
||||
baseUrl: `${window.location.protocol}//${window.location.host}/`,
|
||||
receive: {
|
||||
show: false,
|
||||
status: 'pending',
|
||||
@@ -142,9 +145,11 @@ window.app = Vue.createApp({
|
||||
)
|
||||
.then(response => {
|
||||
this.receive.status = 'success'
|
||||
this.receive.paymentReq = response.data.payment_request
|
||||
this.receive.paymentReq = response.data.bolt11
|
||||
this.receive.paymentHash = response.data.payment_hash
|
||||
|
||||
// TODO: lnurl_callback and lnurl_response
|
||||
// WITHDRAW
|
||||
if (response.data.lnurl_response !== null) {
|
||||
if (response.data.lnurl_response === false) {
|
||||
response.data.lnurl_response = `Unable to connect`
|
||||
@@ -255,7 +260,7 @@ window.app = Vue.createApp({
|
||||
})
|
||||
},
|
||||
decodeQR: function (res) {
|
||||
this.parse.data.request = res
|
||||
this.parse.data.request = res[0].rawValue
|
||||
this.decodeRequest()
|
||||
this.parse.camera.show = false
|
||||
},
|
||||
@@ -391,12 +396,13 @@ window.app = Vue.createApp({
|
||||
dismissPaymentMsg()
|
||||
clearInterval(this.parse.paymentChecker)
|
||||
// show lnurlpay success action
|
||||
if (response.data.success_action) {
|
||||
switch (response.data.success_action.tag) {
|
||||
const extra = response.data.extra
|
||||
if (extra.success_action) {
|
||||
switch (extra.success_action.tag) {
|
||||
case 'url':
|
||||
Quasar.Notify.create({
|
||||
message: `<a target="_blank" style="color: inherit" href="${response.data.success_action.url}">${response.data.success_action.url}</a>`,
|
||||
caption: response.data.success_action.description,
|
||||
message: `<a target="_blank" style="color: inherit" href="${extra.success_action.url}">${extra.success_action.url}</a>`,
|
||||
caption: extra.success_action.description,
|
||||
html: true,
|
||||
type: 'positive',
|
||||
timeout: 0,
|
||||
@@ -405,7 +411,7 @@ window.app = Vue.createApp({
|
||||
break
|
||||
case 'message':
|
||||
Quasar.Notify.create({
|
||||
message: response.data.success_action.message,
|
||||
message: extra.success_action.message,
|
||||
type: 'positive',
|
||||
timeout: 0,
|
||||
closeBtn: true
|
||||
@@ -416,14 +422,14 @@ window.app = Vue.createApp({
|
||||
.getPayment(this.g.wallet, response.data.payment_hash)
|
||||
.then(({data: payment}) =>
|
||||
decryptLnurlPayAES(
|
||||
response.data.success_action,
|
||||
extra.success_action,
|
||||
payment.preimage
|
||||
)
|
||||
)
|
||||
.then(value => {
|
||||
Quasar.Notify.create({
|
||||
message: value,
|
||||
caption: response.data.success_action.description,
|
||||
caption: extra.success_action.description,
|
||||
html: true,
|
||||
type: 'positive',
|
||||
timeout: 0,
|
||||
|
||||
@@ -207,23 +207,24 @@ video {
|
||||
}
|
||||
|
||||
// qrcode
|
||||
.qrcode__wrapper canvas {
|
||||
.qrcode__wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.qrcode__wrapper canvas {
|
||||
width: 100% !important; // important to override qrcode inline width
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
height: 100% !important;
|
||||
max-width: 350px; // default width of <lnbits-qrcode> component
|
||||
}
|
||||
|
||||
.qrcode__image {
|
||||
position: absolute;
|
||||
max-width: 52px;
|
||||
width: 15%;
|
||||
height: 15%;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
left: 50%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 0.2rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user