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:
dni ⚡
2024-10-29 09:58:22 +01:00
committed by GitHub
co-authored by Vlad Stan Tiago Vasconcelos Arc Arc
parent ae4eda04ba
commit 2940cf97c5
84 changed files with 4219 additions and 3775 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+11 -8
View File
@@ -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;
}
+2 -1
View File
@@ -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'
}
+27 -2
View File
@@ -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()
}
}
})
+39 -6
View File
@@ -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))
+51 -6
View File
@@ -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 () {
+5 -7
View File
@@ -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
View File
@@ -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))
},
+15 -9
View File
@@ -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,
+10 -9
View File
@@ -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;
}
File diff suppressed because one or more lines are too long