Added some unique checks so only 1 record can be made per card
This commit is contained in:
parent
7ea3830fed
commit
33f9ae9f66
3 changed files with 59 additions and 227 deletions
|
|
@ -5,10 +5,10 @@ async def m001_initial(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE boltcards.cards (
|
CREATE TABLE boltcards.cards (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY UNIQUE,
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
card_name TEXT NOT NULL,
|
card_name TEXT NOT NULL,
|
||||||
uid TEXT NOT NULL,
|
uid TEXT NOT NULL UNIQUE,
|
||||||
counter INT NOT NULL DEFAULT 0,
|
counter INT NOT NULL DEFAULT 0,
|
||||||
tx_limit TEXT NOT NULL,
|
tx_limit TEXT NOT NULL,
|
||||||
daily_limit TEXT NOT NULL,
|
daily_limit TEXT NOT NULL,
|
||||||
|
|
@ -30,7 +30,7 @@ async def m001_initial(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE boltcards.hits (
|
CREATE TABLE boltcards.hits (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY UNIQUE,
|
||||||
card_id TEXT NOT NULL,
|
card_id TEXT NOT NULL,
|
||||||
ip TEXT NOT NULL,
|
ip TEXT NOT NULL,
|
||||||
spent BOOL NOT NULL DEFAULT True,
|
spent BOOL NOT NULL DEFAULT True,
|
||||||
|
|
@ -48,7 +48,7 @@ async def m001_initial(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE boltcards.refunds (
|
CREATE TABLE boltcards.refunds (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY UNIQUE,
|
||||||
hit_id TEXT NOT NULL,
|
hit_id TEXT NOT NULL,
|
||||||
refund_amount INT NOT NULL,
|
refund_amount INT NOT NULL,
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
time TIMESTAMP NOT NULL DEFAULT """
|
||||||
|
|
|
||||||
|
|
@ -12,33 +12,18 @@
|
||||||
<h5 class="text-subtitle1 q-my-none">Cards</h5>
|
<h5 class="text-subtitle1 q-my-none">Cards</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-btn
|
<q-btn round size="sm" icon="add" unelevated color="primary" @click="addCardOpen">
|
||||||
round
|
|
||||||
size="sm"
|
|
||||||
icon="add"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
@click="addCardOpen"
|
|
||||||
>
|
|
||||||
<q-tooltip>Add card</q-tooltip>
|
<q-tooltip>Add card</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<q-btn flat color="grey" @click="exportCardsCSV"
|
<q-btn flat color="grey" @click="exportCardsCSV">Export to CSV</q-btn>
|
||||||
>Export to CSV</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-table
|
<q-table dense flat :data="cards" row-key="id" :columns="cardsTable.columns"
|
||||||
dense
|
:pagination.sync="cardsTable.pagination">
|
||||||
flat
|
|
||||||
:data="cards"
|
|
||||||
row-key="id"
|
|
||||||
:columns="cardsTable.columns"
|
|
||||||
:pagination.sync="cardsTable.pagination"
|
|
||||||
>
|
|
||||||
{% raw %}
|
{% raw %}
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
|
|
@ -53,13 +38,8 @@
|
||||||
<template v-slot:body="props">
|
<template v-slot:body="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
<q-btn
|
<q-btn unelevated dense icon="qr_code" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
unelevated
|
@click="openQrCodeDialog(props.row.id)">
|
||||||
dense
|
|
||||||
icon="qr_code"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openQrCodeDialog(props.row.id)"
|
|
||||||
>
|
|
||||||
<q-tooltip>Card key credentials</q-tooltip>
|
<q-tooltip>Card key credentials</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
@ -67,45 +47,19 @@
|
||||||
{{ col.value }}
|
{{ col.value }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
<q-btn
|
<q-btn v-if="props.row.enable" dense @click="enableCard(props.row.wallet, props.row.id, false)"
|
||||||
v-if="props.row.enable"
|
color="pink">DISABLE</q-btn>
|
||||||
dense
|
<q-btn v-else dense @click="enableCard(props.row.wallet, props.row.id, true)" color="green">ENABLE
|
||||||
@click="enableCard(props.row.wallet, props.row.id, false)"
|
|
||||||
color="pink"
|
|
||||||
>DISABLE</q-btn
|
|
||||||
>
|
|
||||||
<q-btn
|
|
||||||
v-else
|
|
||||||
dense
|
|
||||||
@click="enableCard(props.row.wallet, props.row.id, true)"
|
|
||||||
color="green"
|
|
||||||
>ENABLE
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
<q-btn
|
<q-btn flat dense size="xs" @click="updateCardDialog(props.row.id)" icon="edit" color="light-blue">
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
@click="updateCardDialog(props.row.id)"
|
|
||||||
icon="edit"
|
|
||||||
color="light-blue"
|
|
||||||
>
|
|
||||||
<q-tooltip>Edit card</q-tooltip>
|
<q-tooltip>Edit card</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
<q-btn
|
<q-btn flat dense size="xs" @click="deleteCard(props.row.id)" icon="cancel" color="pink">
|
||||||
flat
|
<q-tooltip>Deleting card will also delete all records</q-tooltip>
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
@click="deleteCard(props.row.id)"
|
|
||||||
icon="cancel"
|
|
||||||
color="pink"
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
>Deleting card will also delete all records</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
|
|
@ -121,19 +75,11 @@
|
||||||
<h5 class="text-subtitle1 q-my-none">Hits</h5>
|
<h5 class="text-subtitle1 q-my-none">Hits</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<q-btn flat color="grey" @click="exportCardsCSV"
|
<q-btn flat color="grey" @click="exportCardsCSV">Export to CSV</q-btn>
|
||||||
>Export to CSV</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-table
|
<q-table dense flat :data="hits" row-key="id" :columns="hitsTable.columns"
|
||||||
dense
|
:pagination.sync="hitsTable.pagination">
|
||||||
flat
|
|
||||||
:data="hits"
|
|
||||||
row-key="id"
|
|
||||||
:columns="hitsTable.columns"
|
|
||||||
:pagination.sync="hitsTable.pagination"
|
|
||||||
>
|
|
||||||
{% raw %}
|
{% raw %}
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
|
|
@ -160,19 +106,11 @@
|
||||||
<h5 class="text-subtitle1 q-my-none">Refunds</h5>
|
<h5 class="text-subtitle1 q-my-none">Refunds</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<q-btn flat color="grey" @click="exportRefundsCSV"
|
<q-btn flat color="grey" @click="exportRefundsCSV">Export to CSV</q-btn>
|
||||||
>Export to CSV</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-table
|
<q-table dense flat :data="refunds" row-key="id" :columns="refundsTable.columns"
|
||||||
dense
|
:pagination.sync="refundsTable.pagination">
|
||||||
flat
|
|
||||||
:data="refunds"
|
|
||||||
row-key="id"
|
|
||||||
:columns="refundsTable.columns"
|
|
||||||
:pagination.sync="refundsTable.pagination"
|
|
||||||
>
|
|
||||||
{% raw %}
|
{% raw %}
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
|
|
@ -209,148 +147,55 @@
|
||||||
<q-dialog v-model="cardDialog.show" position="top" @hide="closeFormDialog">
|
<q-dialog v-model="cardDialog.show" position="top" @hide="closeFormDialog">
|
||||||
<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">
|
||||||
<q-select
|
<q-select filled dense emit-value v-model="cardDialog.data.wallet" :options="g.user.walletOptions"
|
||||||
filled
|
label="Wallet *">
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model="cardDialog.data.wallet"
|
|
||||||
:options="g.user.walletOptions"
|
|
||||||
label="Wallet *"
|
|
||||||
>
|
|
||||||
</q-select>
|
</q-select>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-input
|
<q-input filled dense emit-value v-model.trim="cardDialog.data.tx_limit" type="number"
|
||||||
filled
|
label="Max transaction (sats)" class="q-pr-sm"></q-input>
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model.trim="cardDialog.data.tx_limit"
|
|
||||||
type="number"
|
|
||||||
label="Max transaction (sats)"
|
|
||||||
class="q-pr-sm"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-input
|
<q-input filled dense emit-value v-model.trim="cardDialog.data.daily_limit" type="number"
|
||||||
filled
|
label="Daily limit (sats)"></q-input>
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model.trim="cardDialog.data.daily_limit"
|
|
||||||
type="number"
|
|
||||||
label="Daily limit (sats)"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-input
|
<q-input filled dense emit-value v-model.trim="cardDialog.data.card_name" type="text" label="Card name ">
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model.trim="cardDialog.data.card_name"
|
|
||||||
type="text"
|
|
||||||
label="Card name "
|
|
||||||
>
|
|
||||||
</q-input>
|
</q-input>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<q-input
|
<q-input filled dense emit-value v-model.trim="cardDialog.data.uid" type="text" label="Card UID ">
|
||||||
filled
|
<q-tooltip>Get from the card you'll use, using an NFC app</q-tooltip>
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model.trim="cardDialog.data.uid"
|
|
||||||
type="text"
|
|
||||||
label="Card UID "
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
>Get from the card you'll use, using an NFC app</q-tooltip
|
|
||||||
>
|
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2 q-pl-sm">
|
<div class="col-2 q-pl-sm">
|
||||||
<q-btn
|
<q-btn outline disable color="grey" icon="nfc" :disable="nfcTagReading" @click="readNfcTag()">
|
||||||
outline
|
|
||||||
disable
|
|
||||||
color="grey"
|
|
||||||
icon="nfc"
|
|
||||||
:disable="nfcTagReading"
|
|
||||||
@click="readNfcTag()"
|
|
||||||
>
|
|
||||||
<q-tooltip>Tap card to scan UID</q-tooltip>
|
<q-tooltip>Tap card to scan UID</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-toggle
|
<q-toggle v-model="toggleAdvanced" label="Show advanced options"></q-toggle>
|
||||||
v-model="toggleAdvanced"
|
|
||||||
label="Show advanced options"
|
|
||||||
></q-toggle>
|
|
||||||
<div v-show="toggleAdvanced">
|
<div v-show="toggleAdvanced">
|
||||||
<q-input
|
<q-input filled dense v-model.trim="cardDialog.data.k0" type="text" label="Card Auth key (K0)"
|
||||||
filled
|
hint="Used to authentificate with the card (16 bytes in HEX). " @randomkey>
|
||||||
dense
|
|
||||||
v-model.trim="cardDialog.data.k0"
|
|
||||||
type="text"
|
|
||||||
label="Card Auth key (K0)"
|
|
||||||
hint="Used to authentificate with the card (16 bytes in HEX). "
|
|
||||||
@randomkey
|
|
||||||
>
|
|
||||||
</q-input>
|
</q-input>
|
||||||
<q-input
|
<q-input filled dense v-model.trim="cardDialog.data.k1" type="text" label="Card Meta key (K1)"
|
||||||
filled
|
hint="Used for encypting of the message (16 bytes in HEX)."></q-input>
|
||||||
dense
|
<q-input filled dense v-model.trim="cardDialog.data.k2" type="text" label="Card File key (K2)"
|
||||||
v-model.trim="cardDialog.data.k1"
|
hint="Used for CMAC of the message (16 bytes in HEX).">
|
||||||
type="text"
|
|
||||||
label="Card Meta key (K1)"
|
|
||||||
hint="Used for encypting of the message (16 bytes in HEX)."
|
|
||||||
></q-input>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="cardDialog.data.k2"
|
|
||||||
type="text"
|
|
||||||
label="Card File key (K2)"
|
|
||||||
hint="Used for CMAC of the message (16 bytes in HEX)."
|
|
||||||
>
|
|
||||||
</q-input>
|
</q-input>
|
||||||
<q-input
|
<q-input filled dense v-model.number="cardDialog.data.counter" type="number" label="Initial counter">
|
||||||
filled
|
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">Zero if you don't know.</q-tooltip>
|
||||||
dense
|
|
||||||
v-model.number="cardDialog.data.counter"
|
|
||||||
type="number"
|
|
||||||
label="Initial counter"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>Zero if you don't know.</q-tooltip
|
|
||||||
>
|
|
||||||
</q-input>
|
</q-input>
|
||||||
<q-btn
|
<q-btn unelevated color="primary" class="q-ml-auto" v-on:click="generateKeys" v-on:click.right="debugKeys"
|
||||||
unelevated
|
v-el:keybtn>Generate keys</q-btn>
|
||||||
color="primary"
|
|
||||||
class="q-ml-auto"
|
|
||||||
v-on:click="generateKeys"
|
|
||||||
v-on:click.right="debugKeys"
|
|
||||||
v-el:keybtn
|
|
||||||
>Generate keys</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn v-if="cardDialog.data.id" unelevated color="primary" type="submit">Update Card</q-btn>
|
||||||
v-if="cardDialog.data.id"
|
<q-btn v-else unelevated color="primary" :disable="cardDialog.data.uid == null" type="submit">Create Card
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
type="submit"
|
|
||||||
>Update Card</q-btn
|
|
||||||
>
|
|
||||||
<q-btn
|
|
||||||
v-else
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="cardDialog.data.uid == null"
|
|
||||||
type="submit"
|
|
||||||
>Create Card
|
|
||||||
</q-btn>
|
</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>
|
||||||
|
|
@ -360,11 +205,7 @@
|
||||||
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
|
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
|
||||||
{% raw %}
|
{% raw %}
|
||||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||||
<qrcode
|
<qrcode :value="qrCodeDialog.data.link" :options="{width: 800}" class="rounded-borders"></qrcode>
|
||||||
:value="qrCodeDialog.data.link"
|
|
||||||
:options="{width: 800}"
|
|
||||||
class="rounded-borders"
|
|
||||||
></qrcode>
|
|
||||||
</q-responsive>
|
</q-responsive>
|
||||||
<p style="word-break: break-all" class="text-center">
|
<p style="word-break: break-all" class="text-center">
|
||||||
(Keys for bolt-nfc-android-app)
|
(Keys for bolt-nfc-android-app)
|
||||||
|
|
@ -375,27 +216,14 @@
|
||||||
<strong>Lock key:</strong> {{ qrCodeDialog.data.k0 }}<br />
|
<strong>Lock key:</strong> {{ qrCodeDialog.data.k0 }}<br />
|
||||||
<strong>Meta key:</strong> {{ qrCodeDialog.data.k1 }}<br />
|
<strong>Meta key:</strong> {{ qrCodeDialog.data.k1 }}<br />
|
||||||
<strong>File key:</strong> {{ qrCodeDialog.data.k2 }}<br />
|
<strong>File key:</strong> {{ qrCodeDialog.data.k2 }}<br />
|
||||||
</p>
|
</p><br />
|
||||||
<br />
|
|
||||||
|
<q-btn unelevated outline color="grey" @click="copyText(lnurlLink + qrCodeDialog.data.uid)" label="Base url (LNURL://)">
|
||||||
<q-btn
|
</q-btn>
|
||||||
unelevated
|
<q-btn unelevated outline color="grey" @click="copyText(qrCodeDialog.data.link)" label="Keys/Auth link">
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="copyText(lnurlLink + qrCodeDialog.data.uid)"
|
|
||||||
label="Base url (LNURL://)"
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="copyText(qrCodeDialog.data.link)"
|
|
||||||
label="Keys/Auth link"
|
|
||||||
>
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-tooltip>Click to copy, then add to NFC card</q-tooltip>
|
<q-tooltip>Click to copy, then add to NFC card</q-tooltip>
|
||||||
|
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
<div class="row q-mt-lg q-gutter-sm">
|
<div class="row q-mt-lg q-gutter-sm">
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
||||||
|
|
@ -406,4 +234,4 @@
|
||||||
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<script src="/boltcards/static/js/index.js"></script>
|
<script src="/boltcards/static/js/index.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -74,7 +74,11 @@ async def api_card_create_or_update(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
detail="Invalid byte data provided.", status_code=HTTPStatus.BAD_REQUEST
|
detail="Invalid byte data provided.", status_code=HTTPStatus.BAD_REQUEST
|
||||||
)
|
)
|
||||||
|
checkUid = await get_card_by_uid(data.uid)
|
||||||
|
if checkUid:
|
||||||
|
raise HTTPException(
|
||||||
|
detail="UID already registered. Delete registered card and try again.", status_code=HTTPStatus.BAD_REQUEST
|
||||||
|
)
|
||||||
if card_id:
|
if card_id:
|
||||||
card = await get_card(card_id)
|
card = await get_card(card_id)
|
||||||
if not card:
|
if not card:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue