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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
	if _, err := db.Exec("UPDATE invoices SET confirmed_at = $2, msats_received = $3 WHERE hash = $1", hash, confirmedAt, msatsReceived); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,7 @@ type (
 | 
			
		||||
		ConfirmedAt    null.Time
 | 
			
		||||
		HeldSince      null.Time
 | 
			
		||||
		Description    string
 | 
			
		||||
		Status         string
 | 
			
		||||
	}
 | 
			
		||||
	Order struct {
 | 
			
		||||
		Id        UUID
 | 
			
		||||
 | 
			
		||||
@ -93,3 +93,18 @@ func HandleInvoice(sc context.ServerContext) echo.HandlerFunc {
 | 
			
		||||
		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",
 | 
			
		||||
		handler.HandleInvoiceStatus,
 | 
			
		||||
		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 {
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@
 | 
			
		||||
    "core-js": "^3.8.3",
 | 
			
		||||
    "pinia": "^2.1.7",
 | 
			
		||||
    "register-service-worker": "^1.7.2",
 | 
			
		||||
    "s-ago": "^2.2.0",
 | 
			
		||||
    "vite": "^4.5.0",
 | 
			
		||||
    "vue": "^3.2.13",
 | 
			
		||||
    "vue-router": "4"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								vue/src/components/UserHome.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vue/src/components/UserHome.vue
									
									
									
									
									
										Normal file
									
								
							@ -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>
 | 
			
		||||
							
								
								
									
										56
									
								
								vue/src/components/UserInvoices.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								vue/src/components/UserInvoices.vue
									
									
									
									
									
										Normal file
									
								
							@ -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 MarketView from '@/views/MarketView'
 | 
			
		||||
import InvoiceView from '@/views/InvoiceView'
 | 
			
		||||
import UserHome from '@/components/UserHome'
 | 
			
		||||
import UserInvoices from '@/components/UserInvoices'
 | 
			
		||||
 | 
			
		||||
const routes = [
 | 
			
		||||
  {
 | 
			
		||||
@ -19,7 +21,12 @@ const routes = [
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
@ -9,21 +9,24 @@
 | 
			
		||||
 \__,_|___/\___|_|   </pre>
 | 
			
		||||
  </div>
 | 
			
		||||
  <!-- eslint-enable -->
 | 
			
		||||
  <div v-if="session.pubkey">
 | 
			
		||||
    <div>authenticated as {{ session.pubkey.slice(0, 8) }}</div>
 | 
			
		||||
    <button class="my-3" @click="logout">logout</button>
 | 
			
		||||
  </div>
 | 
			
		||||
  <header class="flex flex-row text-center justify-center pt-1">
 | 
			
		||||
    <nav>
 | 
			
		||||
      <router-link to="/user">settings</router-link>
 | 
			
		||||
      <router-link to="/user/invoices">invoices</router-link>
 | 
			
		||||
    </nav>
 | 
			
		||||
  </header>
 | 
			
		||||
  <Suspense>
 | 
			
		||||
    <router-view />
 | 
			
		||||
  </Suspense>
 | 
			
		||||
</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('/')
 | 
			
		||||
<style scoped>
 | 
			
		||||
nav {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
nav>a {
 | 
			
		||||
  margin: 0 3px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -4012,6 +4012,11 @@ run-parallel@^1.1.9:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    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:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user