working subdomains frontend, table, popup, payments

This commit is contained in:
Kristjan 2020-12-29 20:52:54 +01:00
parent c0d7371137
commit 6c4b5ea406
7 changed files with 143 additions and 52 deletions

View File

@ -16,13 +16,14 @@ async def create_subdomain(
email: str, email: str,
ip: str, ip: str,
sats: int, sats: int,
duration: int
) -> Subdomains: ) -> Subdomains:
await db.execute( await db.execute(
""" """
INSERT INTO subdomain (id, domain, email, subdomain, ip, wallet, sats, paid) INSERT INTO subdomain (id, domain, email, subdomain, ip, wallet, sats, duration, paid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?,?)
""", """,
(payment_hash, domain, email, subdomain, ip, wallet, sats, False), (payment_hash, domain, email, subdomain, ip, wallet, sats, duration, False),
) )
subdomain = await get_subdomain(payment_hash) subdomain = await get_subdomain(payment_hash)
@ -32,7 +33,7 @@ async def create_subdomain(
async def set_subdomain_paid(payment_hash: str) -> Subdomains: async def set_subdomain_paid(payment_hash: str) -> Subdomains:
row = await db.fetchone("SELECT * FROM subdomain WHERE id = ?", (payment_hash,)) row = await db.fetchone("SELECT * FROM subdomain WHERE id = ?", (payment_hash,))
if row[7] == False: if row[8] == False:
await db.execute( await db.execute(
""" """
UPDATE subdomain UPDATE subdomain
@ -45,7 +46,7 @@ async def set_subdomain_paid(payment_hash: str) -> Subdomains:
domaindata = await get_domain(row[1]) domaindata = await get_domain(row[1])
assert domaindata, "Couldn't get domain from paid subdomain" assert domaindata, "Couldn't get domain from paid subdomain"
amount = domaindata.amountmade + row[7] amount = domaindata.amountmade + row[8]
await db.execute( await db.execute(
""" """
UPDATE domain UPDATE domain
@ -77,7 +78,7 @@ async def set_subdomain_paid(payment_hash: str) -> Subdomains:
async def get_subdomain(subdomain_id: str) -> Optional[Subdomains]: async def get_subdomain(subdomain_id: str) -> Optional[Subdomains]:
row = await db.fetchone("SELECT * FROM subdomain WHERE id = ?", (subdomain_id,)) row = await db.fetchone("SELECT * FROM subdomain s INNER JOIN domain d ON (s.domain = d.id) WHERE s.id = ?", (subdomain_id,))
return Subdomains(**row) if row else None return Subdomains(**row) if row else None
@ -86,7 +87,7 @@ async def get_subdomains(wallet_ids: Union[str, List[str]]) -> List[Subdomains]:
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids)) q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM subdomain WHERE wallet IN ({q})", (*wallet_ids,)) rows = await db.fetchall(f"SELECT s.*, d.domain as domain_name FROM subdomain s INNER JOIN domain d ON (s.domain = d.id) WHERE s.wallet IN ({q})", (*wallet_ids,))
return [Subdomains(**row) for row in rows] return [Subdomains(**row) for row in rows]

View File

@ -27,6 +27,7 @@ async def m001_initial(db):
ip TEXT NOT NULL, ip TEXT NOT NULL,
wallet TEXT NOT NULL, wallet TEXT NOT NULL,
sats INTEGER NOT NULL, sats INTEGER NOT NULL,
duration INTEGER NOT NULL,
paid BOOLEAN NOT NULL, paid BOOLEAN NOT NULL,
time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')) time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
); );

View File

@ -18,9 +18,11 @@ class Subdomains(NamedTuple):
id: str id: str
wallet: str wallet: str
domain: str domain: str
domain_name: str
subdomain: str subdomain: str
email: str email: str
ip: str ip: str
sats: int sats: int
duration: int
paid: bool paid: bool
time: int time: int

View File

@ -3,9 +3,9 @@
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md"> <div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
<q-card class="q-pa-lg"> <q-card class="q-pa-lg">
<q-card-section class="q-pa-none"> <q-card-section class="q-pa-none">
<h3 class="q-my-none">{{ form_domain }}</h3> <h3 class="q-my-none">{{ domain_domain }}</h3>
<br /> <br />
<h5 class="q-my-none">{{ form_desc }}</h5> <h5 class="q-my-none">{{ domain_desc }}</h5>
<br /> <br />
<q-form @submit="Invoice()" class="q-gutter-md"> <q-form @submit="Invoice()" class="q-gutter-md">
<q-input filled dense v-model.trim="formDialog.data.email" type="email" <q-input filled dense v-model.trim="formDialog.data.email" type="email"
@ -14,7 +14,7 @@
</q-input> </q-input>
<q-input filled dense v-model.trim="formDialog.data.ip" type="text" label="ip of your server"> <q-input filled dense v-model.trim="formDialog.data.ip" type="text" label="ip of your server">
</q-input> </q-input>
<q-input filled dense v-model.trim="formDialog.data.duration" type="number" label="{{ form_cost }} sats per day"> <q-input filled dense v-model.trim="formDialog.data.duration" type="number" label="{{ domain_cost }} sats per day">
</q-input> </q-input>
<p>{% raw %}{{amountSats}}{% endraw %}</p> <p>{% raw %}{{amountSats}}{% endraw %}</p>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
@ -48,7 +48,7 @@
{% endblock %} {% block scripts %} {% endblock %} {% block scripts %}
<script> <script>
console.log('{{ form_cost }}') console.log('{{ domain_cost }}')
Vue.component(VueQrcode.name, VueQrcode) Vue.component(VueQrcode.name, VueQrcode)
new Vue({ new Vue({
@ -76,8 +76,8 @@
}, },
computed: { computed: {
amountSats() { amountSats() {
var sats = this.formDialog.data.duration * parseInt('{{ form_cost }}') var sats = this.formDialog.data.duration * parseInt('{{ domain_cost }}')
if (sats === parseInt('{{ form_cost }}')) { if (sats === parseInt('{{ domain_cost }}')) {
return '0 Sats to pay' return '0 Sats to pay'
} else { } else {
this.formDialog.data.sats = sats this.formDialog.data.sats = sats
@ -111,6 +111,7 @@
ip: self.formDialog.data.ip, ip: self.formDialog.data.ip,
email: self.formDialog.data.email, email: self.formDialog.data.email,
sats: self.formDialog.data.sats, sats: self.formDialog.data.sats,
duration: parseInt(self.formDialog.data.duration),
}) })
.then(function (response) { .then(function (response) {
self.paymentReq = response.data.payment_request self.paymentReq = response.data.payment_request
@ -131,6 +132,7 @@
axios axios
.get('/subdomains/api/v1/subdomains/' + self.paymentCheck) .get('/subdomains/api/v1/subdomains/' + self.paymentCheck)
.then(function (res) { .then(function (res) {
console.log(res.data)
if (res.data.paid) { if (res.data.paid) {
clearInterval(paymentChecker) clearInterval(paymentChecker)
self.receive = { self.receive = {
@ -140,25 +142,28 @@
} }
dismissMsg() dismissMsg()
console.log(self.formDialog)
this.formDialog.data.subdomain = '' self.formDialog.data.subdomain = ''
this.formDialog.data.email = '' self.formDialog.data.email = ''
this.formDialog.data.ip = '' self.formDialog.data.ip = ''
this.formDialog.data.duration = '' self.formDialog.data.duration = ''
self.$q.notify({ self.$q.notify({
type: 'positive', type: 'positive',
message: 'Sent, thank you!', message: 'Sent, thank you!',
icon: 'thumb_up', icon: 'thumb_up',
}) })
console.log("END")
} }
}) })
.catch(function (error) { .catch(function (error) {
console.log(error)
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, 2000) }, 2000)
}) })
.catch(function (error) { .catch(function (error) {
console.log(error)
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
} }

View File

@ -53,8 +53,52 @@
</q-table> </q-table>
</q-card-section> </q-card-section>
</q-card> </q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Subdomains</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportSubdomainsCSV">Export to CSV</q-btn>
</div>
</div>
<q-table dense flat :data="subdomains" row-key="id" :columns="subdomainsTable.columns"
:pagination.sync="subdomainsTable.pagination">
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" v-if="props.row.paid">
<q-td auto-width>
<q-btn unelevated dense size="xs" icon="email" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a" :href="'mailto:' + props.row.email"></q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
<q-td auto-width>
<q-btn flat dense size="xs" @click="deleteSubdomain(props.row.id)" icon="cancel" color="pink"></q-btn>
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
</div> </div>
<q-dialog v-model="domainDialog.show" position="top"> <q-dialog v-model="domainDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md"> <q-form @submit="sendFormData" class="q-gutter-md">
@ -68,7 +112,8 @@
</q-input> </q-input>
<q-input filled dense v-model.trim="domainDialog.data.webhook" type="text" label="Webhook (optional)" <q-input filled dense v-model.trim="domainDialog.data.webhook" type="text" label="Webhook (optional)"
hint="A URL to be called whenever this link receives a payment."></q-input> hint="A URL to be called whenever this link receives a payment."></q-input>
<q-input filled dense v-model.trim="domainDialog.data.description" type="textarea" label="Description "></q-input> <q-input filled dense v-model.trim="domainDialog.data.description" type="textarea" label="Description ">
</q-input>
<q-input filled dense v-model.number="domainDialog.data.cost" type="number" label="Amount per day"></q-input> <q-input filled dense v-model.number="domainDialog.data.cost" type="number" label="Amount per day"></q-input>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn v-if="domainDialog.data.id" unelevated color="deep-purple" type="submit">Update Form</q-btn> <q-btn v-if="domainDialog.data.id" unelevated color="deep-purple" type="submit">Update Form</q-btn>
@ -90,8 +135,8 @@
new Date(obj.time * 1000), new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm' 'YYYY-MM-DD HH:mm'
) )
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.displayUrl = ['/subdomains/', obj.id].join('') obj.displayUrl = ['/subdomains/', obj.id].join('')
console.log(obj)
return obj return obj
} }
@ -101,12 +146,13 @@
data: function () { data: function () {
return { return {
domains: [], domains: [],
subdomains: [],
domainsTable: { domainsTable: {
columns: [ columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'}, { name: 'id', align: 'left', label: 'ID', field: 'id' },
{name: 'domain', align: 'left', label: 'Domain name', field: 'domain'}, { name: 'domain', align: 'left', label: 'Domain name', field: 'domain' },
{name: 'wallet', align: 'left', label: 'Wallet', field: 'wallet'}, { name: 'wallet', align: 'left', label: 'Wallet', field: 'wallet' },
{name: 'webhook', align: 'left', label: 'Webhook', field: 'webhook'}, { name: 'webhook', align: 'left', label: 'Webhook', field: 'webhook' },
{ {
name: 'description', name: 'description',
align: 'left', align: 'left',
@ -124,6 +170,40 @@
rowsPerPage: 10 rowsPerPage: 10
}, },
}, },
subdomainsTable: {
columns: [
{ name: 'subdomain', align: 'left', label: 'Subdomain name', field: 'subdomain' },
{ name: 'domain', align: 'left', label: 'Domain name', field: 'domain_name' },
{
name: 'email',
align: 'left',
label: 'Email',
field: 'email'
},
{
name: 'ip',
align: 'left',
label: 'IP address',
field: 'ip'
},
{
name: 'sats',
align: 'left',
label: 'Sats paid',
field: 'sats'
},
{
name: 'duration',
align: 'left',
label: 'Duration in days',
field: 'duration'
},
{ name: 'id', align: 'left', label: 'ID', field: 'id' }
],
pagination: {
rowsPerPage: 10
},
},
domainDialog: { domainDialog: {
show: false, show: false,
data: {} data: {}
@ -133,51 +213,51 @@
methods: { methods: {
getSubdomains: function () { getSubdomains: function () {
var self = this var self = this
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/subdomains/api/v1/subdomains?all_wallets', '/subdomains/api/v1/subdomains?all_wallets',
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function (response) { .then(function (response) {
self.tickets = response.data.map(function (obj) { self.subdomains = response.data.map(function (obj) {
return mapLNSubdomain(obj) return mapLNDomain(obj)
}) })
}) })
}, },
deleteSubdomain: function (subdomainId) { deleteSubdomain: function (subdomainId) {
var self = this var self = this
var tickets = _.findWhere(this.tickets, {id: ticketId}) var subdomains = _.findWhere(this.subdomains, { id: subdomainId })
LNbits.utils LNbits.utils
.confirmDialog('Are you sure you want to delete this ticket') .confirmDialog('Are you sure you want to delete this subdomain')
.onOk(function () { .onOk(function () {
LNbits.api LNbits.api
.request( .request(
'DELETE', 'DELETE',
'/subdomain/api/v1/subdomains/' + subdomainId, '/subdomain/api/v1/subdomains/' + subdomainId,
_.findWhere(self.g.user.wallets, {id: subdomains.wallet}).inkey _.findWhere(self.g.user.wallets, { id: subdomains.wallet }).inkey
) )
.then(function (response) { .then(function (response) {
self.tickets = _.reject(self.tickets, function (obj) { self.subdomains = _.reject(self.subdomains, function (obj) {
return obj.id == ticketId return obj.id == subdomainId
}) })
}) })
.catch(function (error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}) })
}, },
exportSubdomainsCSV: function () { exportSubdomainsCSV: function () {
LNbits.utils.exportCSV(this.domainsTable.columns, this.tickets) LNbits.utils.exportCSV(this.subdomainsTable.columns, this.subdomains)
}, },
getDomains: function () { getDomains: function () {
var self = this var self = this
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
@ -189,7 +269,7 @@
return mapLNDomain(obj) return mapLNDomain(obj)
}) })
}) })
}, },
sendFormData: function () { sendFormData: function () {
var wallet = _.findWhere(this.g.user.wallets, { var wallet = _.findWhere(this.g.user.wallets, {
@ -206,7 +286,7 @@
createDomain: function (wallet, data) { createDomain: function (wallet, data) {
var self = this var self = this
LNbits.api LNbits.api
.request('POST', '/subdomains/api/v1/domains', wallet.inkey, data) .request('POST', '/subdomains/api/v1/domains', wallet.inkey, data)
.then(function (response) { .then(function (response) {
@ -217,10 +297,10 @@
.catch(function (error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
updateDomainDialog: function (formId) { updateDomainDialog: function (formId) {
var link = _.findWhere(this.domains, {id: formId}) var link = _.findWhere(this.domains, { id: formId })
console.log(link.id) console.log(link.id)
this.domainDialog.data.id = link.id this.domainDialog.data.id = link.id
this.domainDialog.data.wallet = link.wallet this.domainDialog.data.wallet = link.wallet
@ -235,7 +315,7 @@
updateDomain: function (wallet, data) { updateDomain: function (wallet, data) {
var self = this var self = this
console.log(data) console.log(data)
LNbits.api LNbits.api
.request( .request(
@ -255,12 +335,12 @@
.catch(function (error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
deleteDomain: function (domainId) { deleteDomain: function (domainId) {
var self = this var self = this
var domains = _.findWhere(this.domains, {id: domainId}) var domains = _.findWhere(this.domains, { id: domainId })
LNbits.utils LNbits.utils
.confirmDialog('Are you sure you want to delete this domain link?') .confirmDialog('Are you sure you want to delete this domain link?')
.onOk(function () { .onOk(function () {
@ -268,7 +348,7 @@
.request( .request(
'DELETE', 'DELETE',
'/subdomains/api/v1/domains/' + domainId, '/subdomains/api/v1/domains/' + domainId,
_.findWhere(self.g.user.wallets, {id: domains.wallet}).inkey _.findWhere(self.g.user.wallets, { id: domains.wallet }).inkey
) )
.then(function (response) { .then(function (response) {
self.domains = _.reject(self.domains, function (obj) { self.domains = _.reject(self.domains, function (obj) {
@ -279,7 +359,7 @@
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}) })
}, },
exportDomainsCSV: function () { exportDomainsCSV: function () {
LNbits.utils.exportCSV(this.domainsTable.columns, this.domains) LNbits.utils.exportCSV(this.domainsTable.columns, this.domains)

View File

@ -22,6 +22,6 @@ async def display(domain_id):
"subdomains/display.html", "subdomains/display.html",
domain_id=domain.id, domain_id=domain.id,
domain_domain=domain.domain, domain_domain=domain.domain,
form_desc=domain.description, domain_desc=domain.description,
form_cost=domain.cost, domain_cost=domain.cost,
) )

View File

@ -103,6 +103,7 @@ async def api_subdomains():
"email": {"type": "string", "empty": True, "required": True}, "email": {"type": "string", "empty": True, "required": True},
"ip": {"type": "string", "empty": False, "required": True}, "ip": {"type": "string", "empty": False, "required": True},
"sats": {"type": "integer", "min": 0, "required": True}, "sats": {"type": "integer", "min": 0, "required": True},
"duration": {"type": "integer", "empty": False, "required": True},
} }
) )
async def api_subdomain_make_subdomain(domain_id): async def api_subdomain_make_subdomain(domain_id):
@ -110,12 +111,13 @@ async def api_subdomain_make_subdomain(domain_id):
if not domain: if not domain:
return jsonify({"message": "LNsubdomain does not exist."}), HTTPStatus.NOT_FOUND return jsonify({"message": "LNsubdomain does not exist."}), HTTPStatus.NOT_FOUND
subdomain = len(re.split(r"\s+", g.data["subdomain"])) subdomain = g.data["subdomain"]
duration = g.data["duration"]
sats = g.data["sats"] sats = g.data["sats"]
payment_hash, payment_request = await create_invoice( payment_hash, payment_request = await create_invoice(
wallet_id=domain.wallet, wallet_id=domain.wallet,
amount=sats, amount=sats,
memo=f"subdomain with {subdomain} words on {domain_id}", memo=f"subdomain {subdomain}.{domain.domain} for {sats} sats for {duration} days",
extra={"tag": "lnsubdomain"}, extra={"tag": "lnsubdomain"},
) )