formatting

This commit is contained in:
Lee Salminen 2022-12-20 07:15:54 -06:00
parent 9e857e6329
commit 048ccd6d65
8 changed files with 123 additions and 89 deletions

View file

@ -3,12 +3,7 @@ from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from . import db from . import db
from .models import ( from .models import Address, CreateAddressData, CreateDomainData, Domain
CreateDomainData,
Domain,
Address,
CreateAddressData,
)
async def get_domain(domain_id: str) -> Optional[Domain]: async def get_domain(domain_id: str) -> Optional[Domain]:
@ -17,12 +12,14 @@ async def get_domain(domain_id: str) -> Optional[Domain]:
) )
return Domain.from_row(row) if row else None return Domain.from_row(row) if row else None
async def get_domain_by_name(domain: str) -> Optional[Domain]: async def get_domain_by_name(domain: str) -> Optional[Domain]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM nostrnip5.domains WHERE domain = ?", (domain,) "SELECT * FROM nostrnip5.domains WHERE domain = ?", (domain,)
) )
return Domain.from_row(row) if row else None return Domain.from_row(row) if row else None
async def get_domains(wallet_ids: Union[str, List[str]]) -> List[Domain]: async def get_domains(wallet_ids: Union[str, List[str]]) -> List[Domain]:
if isinstance(wallet_ids, str): if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
@ -34,18 +31,31 @@ async def get_domains(wallet_ids: Union[str, List[str]]) -> List[Domain]:
return [Domain.from_row(row) for row in rows] return [Domain.from_row(row) for row in rows]
async def get_address(domain_id: str, address_id: str) -> Optional[Address]: async def get_address(domain_id: str, address_id: str) -> Optional[Address]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM nostrnip5.addresses WHERE domain_id = ? AND id = ?", (domain_id,address_id,) "SELECT * FROM nostrnip5.addresses WHERE domain_id = ? AND id = ?",
(
domain_id,
address_id,
),
) )
return Address.from_row(row) if row else None return Address.from_row(row) if row else None
async def get_address_by_local_part(domain_id: str, local_part: str) -> Optional[Address]:
async def get_address_by_local_part(
domain_id: str, local_part: str
) -> Optional[Address]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM nostrnip5.addresses WHERE domain_id = ? AND local_part = ?", (domain_id,local_part,) "SELECT * FROM nostrnip5.addresses WHERE domain_id = ? AND local_part = ?",
(
domain_id,
local_part,
),
) )
return Address.from_row(row) if row else None return Address.from_row(row) if row else None
async def get_addresses(domain_id: str) -> List[Address]: async def get_addresses(domain_id: str) -> List[Address]:
rows = await db.fetchall( rows = await db.fetchall(
f"SELECT * FROM nostrnip5.addresses WHERE domain_id = ?", (domain_id,) f"SELECT * FROM nostrnip5.addresses WHERE domain_id = ?", (domain_id,)
@ -53,6 +63,7 @@ async def get_addresses(domain_id: str) -> List[Address]:
return [Address.from_row(row) for row in rows] return [Address.from_row(row) for row in rows]
async def get_all_addresses(wallet_ids: Union[str, List[str]]) -> List[Address]: async def get_all_addresses(wallet_ids: Union[str, List[str]]) -> List[Address]:
if isinstance(wallet_ids, str): if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
@ -65,11 +76,12 @@ async def get_all_addresses(wallet_ids: Union[str, List[str]]) -> List[Address]:
JOIN nostrnip5.domains d ON d.id = a.domain_id JOIN nostrnip5.domains d ON d.id = a.domain_id
WHERE d.wallet IN ({q}) WHERE d.wallet IN ({q})
""", """,
(*wallet_ids,) (*wallet_ids,),
) )
return [Address.from_row(row) for row in rows] return [Address.from_row(row) for row in rows]
async def activate_domain(domain_id: str, address_id: str) -> Address: async def activate_domain(domain_id: str, address_id: str) -> Address:
await db.execute( await db.execute(
""" """
@ -88,37 +100,34 @@ async def activate_domain(domain_id: str, address_id: str) -> Address:
assert address, "Newly updated address couldn't be retrieved" assert address, "Newly updated address couldn't be retrieved"
return address return address
async def delete_domain(domain_id) -> bool: async def delete_domain(domain_id) -> bool:
await db.execute( await db.execute(
""" """
DELETE FROM nostrnip5.addresses WHERE domain_id = ? DELETE FROM nostrnip5.addresses WHERE domain_id = ?
""", """,
( (domain_id,),
domain_id,
),
) )
await db.execute( await db.execute(
""" """
DELETE FROM nostrnip5.domains WHERE id = ? DELETE FROM nostrnip5.domains WHERE id = ?
""", """,
( (domain_id,),
domain_id,
),
) )
return True return True
async def delete_address(address_id) -> bool: async def delete_address(address_id) -> bool:
await db.execute( await db.execute(
""" """
DELETE FROM nostrnip5.addresses WHERE id = ? DELETE FROM nostrnip5.addresses WHERE id = ?
""", """,
( (address_id,),
address_id,
),
) )
async def create_address_internal(domain_id: str, data: CreateAddressData) -> Address: async def create_address_internal(domain_id: str, data: CreateAddressData) -> Address:
address_id = urlsafe_short_hash() address_id = urlsafe_short_hash()
@ -140,6 +149,7 @@ async def create_address_internal(domain_id: str, data: CreateAddressData) -> Ad
assert address, "Newly created address couldn't be retrieved" assert address, "Newly created address couldn't be retrieved"
return address return address
async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Domain: async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Domain:
domain_id = urlsafe_short_hash() domain_id = urlsafe_short_hash()
@ -148,13 +158,7 @@ async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Doma
INSERT INTO nostrnip5.domains (id, wallet, currency, amount, domain) INSERT INTO nostrnip5.domains (id, wallet, currency, amount, domain)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
""", """,
( (domain_id, wallet_id, data.currency, int(data.amount * 100), data.domain),
domain_id,
wallet_id,
data.currency,
int(data.amount * 100),
data.domain
),
) )
domain = await get_domain(domain_id) domain = await get_domain(domain_id)

View file

@ -5,18 +5,21 @@ from typing import List, Optional
from fastapi.param_functions import Query from fastapi.param_functions import Query
from pydantic import BaseModel from pydantic import BaseModel
class CreateAddressData(BaseModel): class CreateAddressData(BaseModel):
domain_id: str domain_id: str
local_part: str local_part: str
pubkey: str pubkey: str
active: bool = False active: bool = False
class CreateDomainData(BaseModel): class CreateDomainData(BaseModel):
wallet: str wallet: str
currency: str currency: str
amount: float = Query(..., ge=0.01) amount: float = Query(..., ge=0.01)
domain: str domain: str
class Domain(BaseModel): class Domain(BaseModel):
id: str id: str
wallet: str wallet: str
@ -29,6 +32,7 @@ class Domain(BaseModel):
def from_row(cls, row: Row) -> "Domain": def from_row(cls, row: Row) -> "Domain":
return cls(**dict(row)) return cls(**dict(row))
class Address(BaseModel): class Address(BaseModel):
id: str id: str
domain_id: str domain_id: str

View file

@ -5,10 +5,7 @@ from lnbits.core.models import Payment
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from lnbits.tasks import internal_invoice_queue, register_invoice_listener from lnbits.tasks import internal_invoice_queue, register_invoice_listener
from .crud import ( from .crud import activate_domain, get_domain
get_domain,
activate_domain,
)
async def wait_for_paid_invoices(): async def wait_for_paid_invoices():

View file

@ -78,7 +78,9 @@
<h5 class="text-subtitle1 q-my-none">Addresses</h5> <h5 class="text-subtitle1 q-my-none">Addresses</h5>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<q-btn flat color="grey" @click="exportAddressesCSV">Export to CSV</q-btn> <q-btn flat color="grey" @click="exportAddressesCSV"
>Export to CSV</q-btn
>
</div> </div>
</div> </div>
<q-table <q-table
@ -140,7 +142,6 @@
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog"> <q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px"> <q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-form @submit="saveDomain" class="q-gutter-md"> <q-form @submit="saveDomain" class="q-gutter-md">
<q-select <q-select
filled filled
dense dense
@ -387,9 +388,14 @@
columns: [ columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'}, {name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'domain', align: 'left', label: 'Domain', field: 'domain'}, {name: 'domain', align: 'left', label: 'Domain', field: 'domain'},
{name: 'currency', align: 'left', label: 'Currency', field: 'currency'}, {
name: 'currency',
align: 'left',
label: 'Currency',
field: 'currency'
},
{name: 'amount', align: 'left', label: 'Amount', field: 'amount'}, {name: 'amount', align: 'left', label: 'Amount', field: 'amount'},
{name: 'time', align: 'left', label: "Created At", field: 'time'}, {name: 'time', align: 'left', label: 'Created At', field: 'time'}
], ],
pagination: { pagination: {
rowsPerPage: 10 rowsPerPage: 10
@ -398,11 +404,21 @@
addressesTable: { addressesTable: {
columns: [ columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'}, {name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'active', align: 'left', label: "Active", field: 'active'}, {name: 'active', align: 'left', label: 'Active', field: 'active'},
{name: 'domain_id', align: 'left', label: 'Domain', field: 'domain_id'}, {
{name: 'local_part', align: 'left', label: 'Local Part', field: 'local_part'}, name: 'domain_id',
align: 'left',
label: 'Domain',
field: 'domain_id'
},
{
name: 'local_part',
align: 'left',
label: 'Local Part',
field: 'local_part'
},
{name: 'pubkey', align: 'left', label: 'Pubkey', field: 'pubkey'}, {name: 'pubkey', align: 'left', label: 'Pubkey', field: 'pubkey'},
{name: 'time', align: 'left', label: "Created At", field: 'time'}, {name: 'time', align: 'left', label: 'Created At', field: 'time'}
], ],
pagination: { pagination: {
rowsPerPage: 10 rowsPerPage: 10
@ -410,7 +426,7 @@
}, },
formDialog: { formDialog: {
show: false, show: false,
data: {}, data: {}
} }
} }
}, },

View file

@ -1,13 +1,21 @@
{% extends "public.html" %} {% block toolbar_title %} Verify NIP-5 For {{ domain.domain }} {% extends "public.html" %} {% block toolbar_title %} Verify NIP-5 For {{
{% endblock %} {% from "macros.jinja" import window_vars with context %} {% domain.domain }} {% endblock %} {% from "macros.jinja" import window_vars with
block page %} context %} {% block page %}
<link rel="stylesheet" href="/nostrnip5/static/css/signup.css" /> <link rel="stylesheet" href="/nostrnip5/static/css/signup.css" />
<div> <div>
<q-card class="q-pa-lg q-pt-lg"> <q-card class="q-pa-lg q-pt-lg">
<q-form @submit="createAddress" class="q-gutter-md"> <q-form @submit="createAddress" class="q-gutter-md">
<p>You can use this page to get NIP-5 verified on the nostr protocol under the {{ domain.domain }} domain.</p> <p>
<p>The current price is <b>{{ "{:0,.2f}".format(domain.amount / 100) }} {{ domain.currency }}</b> for a <b>lifetime</b> account.</p> You can use this page to get NIP-5 verified on the nostr protocol under
the {{ domain.domain }} domain.
</p>
<p>
The current price is
<b
>{{ "{:0,.2f}".format(domain.amount / 100) }} {{ domain.currency }}</b
>
for a <b>lifetime</b> account.
</p>
<p>After submitting payment, your address will be</p> <p>After submitting payment, your address will be</p>
@ -43,9 +51,7 @@ block page %}
type="submit" type="submit"
>Create Address</q-btn >Create Address</q-btn
> >
<q-btn v-close-popup flat color="grey" class="q-ml-auto" <q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
>Cancel</q-btn
>
</div> </div>
</q-form> </q-form>
</q-card> </q-card>
@ -91,19 +97,19 @@ block page %}
amount: '{{ domain.amount }}', amount: '{{ domain.amount }}',
qrCodeDialog: { qrCodeDialog: {
data: { data: {
payment_request: null, payment_request: null
}, },
show: false, show: false
}, },
formDialog: { formDialog: {
data: { data: {
local_part: null, local_part: null,
pubkey: null, pubkey: null
}, }
}, },
urlDialog: { urlDialog: {
show: false, show: false
}, }
} }
}, },
methods: { methods: {
@ -118,7 +124,10 @@ block page %}
var localPart = formDialog.data.local_part var localPart = formDialog.data.local_part
axios axios
.post('/nostrnip5/api/v1/domain/' + this.domain_id + '/address', formDialog.data) .post(
'/nostrnip5/api/v1/domain/' + this.domain_id + '/address',
formDialog.data
)
.then(function (response) { .then(function (response) {
formDialog.data = {} formDialog.data = {}
@ -147,7 +156,9 @@ block page %}
qrCodeDialog.show = false qrCodeDialog.show = false
setTimeout(function () { setTimeout(function () {
alert(`Success! Your username is now active at ${localPart}@${self.domain}. Please add this to your nostr profile accordingly.`) alert(
`Success! Your username is now active at ${localPart}@${self.domain}. Please add this to your nostr profile accordingly.`
)
}, 500) }, 500)
} }
}) })
@ -156,14 +167,10 @@ block page %}
.catch(function (error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
},
},
computed: {
},
created: function () {
} }
},
computed: {},
created: function () {}
}) })
</script> </script>
{% endblock %} {% endblock %}

View file

@ -11,9 +11,7 @@ from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
from . import nostrnip5_ext, nostrnip5_renderer from . import nostrnip5_ext, nostrnip5_renderer
from .crud import ( from .crud import get_domain
get_domain,
)
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")

View file

@ -1,10 +1,10 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Optional
from fastapi import Query, Request, Response from fastapi import Query, Request, Response
from fastapi.params import Depends from fastapi.params import Depends
from loguru import logger from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from typing import Optional
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
@ -14,18 +14,18 @@ from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
from . import nostrnip5_ext from . import nostrnip5_ext
from .crud import ( from .crud import (
get_domains,
get_domain,
create_domain_internal,
create_address_internal, create_address_internal,
create_domain_internal,
delete_address,
delete_domain, delete_domain,
get_domain_by_name,
get_address_by_local_part, get_address_by_local_part,
get_addresses, get_addresses,
get_all_addresses, get_all_addresses,
delete_address, get_domain,
get_domain_by_name,
get_domains,
) )
from .models import CreateDomainData, CreateAddressData from .models import CreateAddressData, CreateDomainData
@nostrnip5_ext.get("/api/v1/domains", status_code=HTTPStatus.OK) @nostrnip5_ext.get("/api/v1/domains", status_code=HTTPStatus.OK)
@ -38,6 +38,7 @@ async def api_domains(
return [domain.dict() for domain in await get_domains(wallet_ids)] return [domain.dict() for domain in await get_domains(wallet_ids)]
@nostrnip5_ext.get("/api/v1/addresses", status_code=HTTPStatus.OK) @nostrnip5_ext.get("/api/v1/addresses", status_code=HTTPStatus.OK)
async def api_addresses( async def api_addresses(
all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)
@ -85,6 +86,7 @@ async def api_domain_delete(
return True return True
@nostrnip5_ext.delete("/api/v1/address/{address_id}", status_code=HTTPStatus.CREATED) @nostrnip5_ext.delete("/api/v1/address/{address_id}", status_code=HTTPStatus.CREATED)
async def api_address_delete( async def api_address_delete(
address_id: str, address_id: str,
@ -95,7 +97,9 @@ async def api_address_delete(
return True return True
@nostrnip5_ext.post("/api/v1/domain/{domain_id}/address", status_code=HTTPStatus.CREATED) @nostrnip5_ext.post(
"/api/v1/domain/{domain_id}/address", status_code=HTTPStatus.CREATED
)
async def api_address_create( async def api_address_create(
data: CreateAddressData, data: CreateAddressData,
domain_id: str, domain_id: str,
@ -127,7 +131,11 @@ async def api_address_create(
wallet_id=domain.wallet, wallet_id=domain.wallet,
amount=price_in_sats, amount=price_in_sats,
memo=f"Payment for domain {domain_id}", memo=f"Payment for domain {domain_id}",
extra={"tag": "nostrnip5", "domain_id": domain_id, "address_id": address.id,}, extra={
"tag": "nostrnip5",
"domain_id": domain_id,
"address_id": address.id,
},
) )
except Exception as e: except Exception as e:
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
@ -154,7 +162,9 @@ async def api_nostrnip5_check_payment(domain_id: str, payment_hash: str):
@nostrnip5_ext.get("/api/v1/domain/{domain_id}/nostr.json", status_code=HTTPStatus.OK) @nostrnip5_ext.get("/api/v1/domain/{domain_id}/nostr.json", status_code=HTTPStatus.OK)
async def api_get_nostr_json(response: Response, domain_id: str, name: str = Query(None)): async def api_get_nostr_json(
response: Response, domain_id: str, name: str = Query(None)
):
addresses = [address.dict() for address in await get_addresses(domain_id)] addresses = [address.dict() for address in await get_addresses(domain_id)]
output = {} output = {}
@ -170,6 +180,4 @@ async def api_get_nostr_json(response: Response, domain_id: str, name: str = Que
response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS" response.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS"
return { return {"names": output}
"names": output
}