feat: create invoice to join
This commit is contained in:
parent
db3ad2e32f
commit
8678090e7b
5 changed files with 176 additions and 8 deletions
13
crud.py
13
crud.py
|
|
@ -61,6 +61,17 @@ async def get_relay(user_id: str, relay_id: str) -> Optional[NostrRelay]:
|
||||||
|
|
||||||
return NostrRelay.from_row(row) if row else None
|
return NostrRelay.from_row(row) if row else None
|
||||||
|
|
||||||
|
async def get_relay_by_id(relay_id: str) -> Optional[NostrRelay]:
|
||||||
|
"""Note: it does not require `user_id`. Can read any relay. Use it with care."""
|
||||||
|
row = await db.fetchone(
|
||||||
|
"""SELECT * FROM nostrrelay.relays WHERE id = ?""",
|
||||||
|
(
|
||||||
|
relay_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return NostrRelay.from_row(row) if row else None
|
||||||
|
|
||||||
|
|
||||||
async def get_relays(user_id: str) -> List[NostrRelay]:
|
async def get_relays(user_id: str) -> List[NostrRelay]:
|
||||||
rows = await db.fetchall(
|
rows = await db.fetchall(
|
||||||
|
|
@ -100,7 +111,7 @@ async def get_public_relay(relay_id: str) -> Optional[dict]:
|
||||||
"description": relay.description,
|
"description": relay.description,
|
||||||
"pubkey": relay.pubkey,
|
"pubkey": relay.pubkey,
|
||||||
"contact": relay.contact,
|
"contact": relay.contact,
|
||||||
"config": dict(RelayPublicSpec(**dict(relay.config)))
|
"config": RelayPublicSpec(**dict(relay.config)).dict(by_alias=True)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
19
helpers.py
Normal file
19
helpers.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
from bech32 import bech32_decode, convertbits
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_public_key(pubkey: str) -> str:
|
||||||
|
if pubkey.startswith('npub1'):
|
||||||
|
_, decoded_data = bech32_decode(pubkey)
|
||||||
|
if not decoded_data:
|
||||||
|
raise ValueError("Public Key is not valid npub")
|
||||||
|
|
||||||
|
decoded_data_bits = convertbits(decoded_data, 5, 8, False)
|
||||||
|
if not decoded_data_bits:
|
||||||
|
raise ValueError("Public Key is not valid npub")
|
||||||
|
return bytes(decoded_data_bits).hex()
|
||||||
|
|
||||||
|
#check if valid hex
|
||||||
|
if len(pubkey) != 64:
|
||||||
|
raise ValueError("Public Key is not valid hex")
|
||||||
|
int(pubkey, 16)
|
||||||
|
return pubkey
|
||||||
|
|
@ -97,6 +97,10 @@ class NostrRelay(BaseModel):
|
||||||
|
|
||||||
config: "RelaySpec" = RelaySpec()
|
config: "RelaySpec" = RelaySpec()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_free_to_join(self):
|
||||||
|
return not self.config.is_paid_relay or self.config.cost_to_join == 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row) -> "NostrRelay":
|
def from_row(cls, row: Row) -> "NostrRelay":
|
||||||
relay = cls(**dict(row))
|
relay = cls(**dict(row))
|
||||||
|
|
@ -290,3 +294,8 @@ class NostrFilter(BaseModel):
|
||||||
values += [self.until]
|
values += [self.until]
|
||||||
|
|
||||||
return inner_joins, where, values
|
return inner_joins, where, values
|
||||||
|
|
||||||
|
|
||||||
|
class RelayJoin(BaseModel):
|
||||||
|
relay_id: str
|
||||||
|
pubkey: str
|
||||||
|
|
@ -8,7 +8,55 @@
|
||||||
<div class="col-12 col-md-2 q-gutter-y-md"></div>
|
<div class="col-12 col-md-2 q-gutter-y-md"></div>
|
||||||
<div class="col-12 col-md-6 q-gutter-y-md q-pa-xl">
|
<div class="col-12 col-md-6 q-gutter-y-md q-pa-xl">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section> </q-card-section>
|
<q-card-section>
|
||||||
|
<h4 v-text="relay.name" class="q-my-none"></h4>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<span class="text-bold">Public Key:</span>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="pubkey"
|
||||||
|
type="text"
|
||||||
|
label="User Public Key"
|
||||||
|
></q-input>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-if="relay.config.isPaidRelay">
|
||||||
|
<q-btn @click="payToJoin" unelevated color="primary float-right"
|
||||||
|
>Pay to join</q-btn
|
||||||
|
>
|
||||||
|
<span class="text-bold">Cost to join: </span>
|
||||||
|
|
||||||
|
<span v-text="relay.config.costToJoin"></span>
|
||||||
|
<q-badge color="orange">
|
||||||
|
<span>sats</span>
|
||||||
|
</q-badge>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-else>
|
||||||
|
<q-badge color="yellow" text-color="black">
|
||||||
|
This is a free relay
|
||||||
|
</q-badge>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-if="joinInvoice">
|
||||||
|
<q-expansion-item
|
||||||
|
group="join-invoice"
|
||||||
|
label="Join Invoice"
|
||||||
|
:content-inset-level="0.5"
|
||||||
|
default-opened
|
||||||
|
>
|
||||||
|
<div class="q-ma-xl">
|
||||||
|
<q-responsive :ratio="1" class="q-ma-xl q-mx-md">
|
||||||
|
<qrcode
|
||||||
|
:value="'lightning:'+joinInvoice"
|
||||||
|
:options="{width: 340}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
</div>
|
||||||
|
</q-expansion-item>
|
||||||
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4 q-gutter-y-md q-pa-xl">
|
<div class="col-12 col-md-4 q-gutter-y-md q-pa-xl">
|
||||||
|
|
@ -17,7 +65,7 @@
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
group="extras"
|
group="extras"
|
||||||
icon="sensors"
|
icon="sensors"
|
||||||
:label="relay.name"
|
label="Relay Specs"
|
||||||
:content-inset-level="0.5"
|
:content-inset-level="0.5"
|
||||||
>
|
>
|
||||||
<q-separator class="q-mt-md"></q-separator>
|
<q-separator class="q-mt-md"></q-separator>
|
||||||
|
|
@ -94,10 +142,40 @@
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
relay: JSON.parse('{{relay | tojson | safe}}')
|
relay: JSON.parse('{{relay | tojson | safe}}'),
|
||||||
|
pubkey: '',
|
||||||
|
joinInvoice: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {}
|
methods: {
|
||||||
|
payToJoin: async function () {
|
||||||
|
if (!this.pubkey) {
|
||||||
|
this.$q.notify({
|
||||||
|
timeout: 5000,
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Public key is missing'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'PUT',
|
||||||
|
'/nostrrelay/api/v1/join',
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
relay_id: this.relay.id,
|
||||||
|
pubkey: this.pubkey
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.joinInvoice = data.invoice
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
console.log('### created', this.relay)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
57
views_api.py
57
views_api.py
|
|
@ -3,10 +3,10 @@ from typing import List, Optional
|
||||||
|
|
||||||
from fastapi import Depends, WebSocket
|
from fastapi import Depends, WebSocket
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic.types import UUID4
|
from pydantic.types import UUID4
|
||||||
|
|
||||||
|
from lnbits.core.services import create_invoice
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
WalletTypeInfo,
|
WalletTypeInfo,
|
||||||
check_admin,
|
check_admin,
|
||||||
|
|
@ -21,12 +21,13 @@ from .crud import (
|
||||||
create_relay,
|
create_relay,
|
||||||
delete_all_events,
|
delete_all_events,
|
||||||
delete_relay,
|
delete_relay,
|
||||||
get_public_relay,
|
|
||||||
get_relay,
|
get_relay,
|
||||||
|
get_relay_by_id,
|
||||||
get_relays,
|
get_relays,
|
||||||
update_relay,
|
update_relay,
|
||||||
)
|
)
|
||||||
from .models import NostrRelay
|
from .helpers import normalize_public_key
|
||||||
|
from .models import NostrRelay, RelayJoin
|
||||||
|
|
||||||
client_manager = NostrClientManager()
|
client_manager = NostrClientManager()
|
||||||
|
|
||||||
|
|
@ -151,3 +152,53 @@ async def api_delete_relay(
|
||||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
detail="Cannot delete relay",
|
detail="Cannot delete relay",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@nostrrelay_ext.put("/api/v1/join")
|
||||||
|
async def api_pay_to_join(
|
||||||
|
data: RelayJoin
|
||||||
|
):
|
||||||
|
|
||||||
|
try:
|
||||||
|
pubkey = normalize_public_key(data.pubkey)
|
||||||
|
relay = await get_relay_by_id(data.relay_id)
|
||||||
|
if not relay:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
detail="Relay not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
if relay.is_free_to_join:
|
||||||
|
raise ValueError("Relay is free to join")
|
||||||
|
|
||||||
|
_, payment_request = await create_invoice(
|
||||||
|
wallet_id=relay.config.wallet,
|
||||||
|
amount=int(relay.config.cost_to_join),
|
||||||
|
memo=f"Pubkey '{data.pubkey}' wants to join {relay.id}",
|
||||||
|
extra={
|
||||||
|
"tag": "nostrrely",
|
||||||
|
"action": "join",
|
||||||
|
"relay": relay.id,
|
||||||
|
"pubkey": pubkey
|
||||||
|
},
|
||||||
|
)
|
||||||
|
print("### payment_request", payment_request)
|
||||||
|
return {
|
||||||
|
"invoice": payment_request
|
||||||
|
}
|
||||||
|
except ValueError as ex:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail=str(ex),
|
||||||
|
)
|
||||||
|
except HTTPException as ex:
|
||||||
|
raise ex
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning(ex)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
detail="Cannot create invoice for client to join",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue