Show user invoices
This commit is contained in:
parent
9026f56359
commit
6d7cd2573d
|
@ -70,6 +70,33 @@ func (db *DB) FetchInvoices(where *FetchInvoicesWhere, invoices *[]Invoice) erro
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) FetchUserInvoices(pubkey string, invoices *[]Invoice) error {
|
||||||
|
var (
|
||||||
|
rows *sql.Rows
|
||||||
|
invoice Invoice
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
query = "" +
|
||||||
|
"SELECT id, pubkey, msats, preimage, hash, bolt11, created_at, expires_at, confirmed_at, held_since, COALESCE(description, ''), " +
|
||||||
|
"CASE WHEN confirmed_at IS NOT NULL THEN 'PAID' WHEN expires_at < CURRENT_TIMESTAMP THEN 'EXPIRED' ELSE 'WAITING' END AS status " +
|
||||||
|
"FROM invoices " +
|
||||||
|
"WHERE pubkey = $1 " +
|
||||||
|
"ORDER BY created_at DESC"
|
||||||
|
)
|
||||||
|
if rows, err = db.Query(query, pubkey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
rows.Scan(
|
||||||
|
&invoice.Id, &invoice.Pubkey, &invoice.Msats, &invoice.Preimage, &invoice.Hash,
|
||||||
|
&invoice.PaymentRequest, &invoice.CreatedAt, &invoice.ExpiresAt, &invoice.ConfirmedAt, &invoice.HeldSince, &invoice.Description, &invoice.Status)
|
||||||
|
*invoices = append(*invoices, invoice)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) ConfirmInvoice(hash string, confirmedAt time.Time, msatsReceived int) error {
|
func (db *DB) ConfirmInvoice(hash string, confirmedAt time.Time, msatsReceived int) error {
|
||||||
if _, err := db.Exec("UPDATE invoices SET confirmed_at = $2, msats_received = $3 WHERE hash = $1", hash, confirmedAt, msatsReceived); err != nil {
|
if _, err := db.Exec("UPDATE invoices SET confirmed_at = $2, msats_received = $3 WHERE hash = $1", hash, confirmedAt, msatsReceived); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -47,6 +47,7 @@ type (
|
||||||
ConfirmedAt null.Time
|
ConfirmedAt null.Time
|
||||||
HeldSince null.Time
|
HeldSince null.Time
|
||||||
Description string
|
Description string
|
||||||
|
Status string
|
||||||
}
|
}
|
||||||
Order struct {
|
Order struct {
|
||||||
Id UUID
|
Id UUID
|
||||||
|
|
|
@ -93,3 +93,18 @@ func HandleInvoice(sc context.ServerContext) echo.HandlerFunc {
|
||||||
return sc.Render(c, http.StatusOK, "invoice.html", data)
|
return sc.Render(c, http.StatusOK, "invoice.html", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HandleInvoices(sc context.ServerContext) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
u db.User
|
||||||
|
invoices []db.Invoice
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
u = c.Get("session").(db.User)
|
||||||
|
if err = sc.Db.FetchUserInvoices(u.Pubkey, &invoices); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, invoices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -57,6 +57,9 @@ func addBackendRoutes(e *echo.Echo, sc ServerContext) {
|
||||||
GET(e, sc, "/api/invoice/:id",
|
GET(e, sc, "/api/invoice/:id",
|
||||||
handler.HandleInvoiceStatus,
|
handler.HandleInvoiceStatus,
|
||||||
middleware.SessionGuard)
|
middleware.SessionGuard)
|
||||||
|
GET(e, sc, "/api/invoices",
|
||||||
|
handler.HandleInvoices,
|
||||||
|
middleware.SessionGuard)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GET(e *echo.Echo, sc ServerContext, path string, scF HandlerFunc, scM ...MiddlewareFunc) *echo.Route {
|
func GET(e *echo.Echo, sc ServerContext, path string, scF HandlerFunc, scM ...MiddlewareFunc) *echo.Route {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
|
"s-ago": "^2.2.0",
|
||||||
"vite": "^4.5.0",
|
"vite": "^4.5.0",
|
||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-router": "4"
|
"vue-router": "4"
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="session.pubkey" class="flex flex-row items-center">
|
||||||
|
<div>authenticated as {{ session.pubkey.slice(0, 8) }}</div>
|
||||||
|
<button class="ms-2 my-3" @click="logout">logout</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useSession } from '@/stores/session'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
const session = useSession()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
await session.logout()
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,56 @@
|
||||||
|
<template>
|
||||||
|
<div class="text w-auto">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<th>sats</th>
|
||||||
|
<th>created at</th>
|
||||||
|
<th class="hidden-sm">expires at</th>
|
||||||
|
<th>status</th>
|
||||||
|
<th>details</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="i in invoices " :key="i.id">
|
||||||
|
<td>{{ i.Msats / 1000 }}</td>
|
||||||
|
<td :title="i.CreatedAt">{{ ago(new Date(i.CreatedAt)) }}</td>
|
||||||
|
<td :title="i.ExpiresAt" class="hidden-sm">{{ ago(new Date(i.ExpiresAt)) }}</td>
|
||||||
|
<td>{{ i.Status }}</td>
|
||||||
|
<td>
|
||||||
|
<router-link :to="/invoice/ + i.Id">open</router-link>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import ago from 's-ago'
|
||||||
|
|
||||||
|
const invoices = ref(null)
|
||||||
|
|
||||||
|
const url = '/api/invoices'
|
||||||
|
await fetch(url)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(body => {
|
||||||
|
invoices.value = body
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
table {
|
||||||
|
width: fit-content;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.hidden-sm {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -10,6 +10,8 @@ import LoginView from '@/views/LoginView'
|
||||||
import UserView from '@/views/UserView'
|
import UserView from '@/views/UserView'
|
||||||
import MarketView from '@/views/MarketView'
|
import MarketView from '@/views/MarketView'
|
||||||
import InvoiceView from '@/views/InvoiceView'
|
import InvoiceView from '@/views/InvoiceView'
|
||||||
|
import UserHome from '@/components/UserHome'
|
||||||
|
import UserInvoices from '@/components/UserInvoices'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
@ -19,7 +21,12 @@ const routes = [
|
||||||
path: '/login', component: LoginView
|
path: '/login', component: LoginView
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/user', component: UserView
|
path: '/user',
|
||||||
|
component: UserView,
|
||||||
|
children: [
|
||||||
|
{ path: '', name: 'user', component: UserHome },
|
||||||
|
{ path: 'invoices', name: 'invoices', component: UserInvoices }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/market/:id', component: MarketView
|
path: '/market/:id', component: MarketView
|
||||||
|
|
|
@ -9,21 +9,24 @@
|
||||||
\__,_|___/\___|_| </pre>
|
\__,_|___/\___|_| </pre>
|
||||||
</div>
|
</div>
|
||||||
<!-- eslint-enable -->
|
<!-- eslint-enable -->
|
||||||
<div v-if="session.pubkey">
|
<header class="flex flex-row text-center justify-center pt-1">
|
||||||
<div>authenticated as {{ session.pubkey.slice(0, 8) }}</div>
|
<nav>
|
||||||
<button class="my-3" @click="logout">logout</button>
|
<router-link to="/user">settings</router-link>
|
||||||
</div>
|
<router-link to="/user/invoices">invoices</router-link>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<Suspense>
|
||||||
|
<router-view />
|
||||||
|
</Suspense>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<style scoped>
|
||||||
import { useSession } from '@/stores/session'
|
nav {
|
||||||
import { useRouter } from 'vue-router'
|
display: flex;
|
||||||
const session = useSession()
|
justify-content: center;
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const logout = async () => {
|
|
||||||
await session.logout()
|
|
||||||
router.push('/')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
nav>a {
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -4012,6 +4012,11 @@ run-parallel@^1.1.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
|
s-ago@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/s-ago/-/s-ago-2.2.0.tgz#4143a9d0176b3100dcf649c78e8a1ec8a59b1312"
|
||||||
|
integrity sha512-t6Q/aFCCJSBf5UUkR/WH0mDHX8EGm2IBQ7nQLobVLsdxOlkryYMbOlwu2D4Cf7jPUp0v1LhfPgvIZNoi9k8lUA==
|
||||||
|
|
||||||
safe-array-concat@^1.0.1:
|
safe-array-concat@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c"
|
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c"
|
||||||
|
|
Loading…
Reference in New Issue