mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
feat: add transaction search
This commit is contained in:
parent
bf9c346a98
commit
bea8daac9f
12 changed files with 163 additions and 41 deletions
|
|
@ -43,7 +43,7 @@ type API interface {
|
|||
SignMessage(ctx context.Context, message string) (*SignMessageResponse, error)
|
||||
RedeemOnchainFunds(ctx context.Context, toAddress string, amountSat uint64, feeRate *uint64, sendAll bool) (*RedeemOnchainFundsResponse, error)
|
||||
GetBalances(ctx context.Context) (*BalancesResponse, error)
|
||||
ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64) (*ListTransactionsResponse, error)
|
||||
ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64, searchTerm string) (*ListTransactionsResponse, error)
|
||||
ListOnchainTransactions(ctx context.Context) ([]OnchainTransaction, error)
|
||||
SendPayment(ctx context.Context, invoice string, amountMsat *uint64, metadata map[string]interface{}, fromAppId *uint) (*SendPaymentResponse, error)
|
||||
CreateInvoice(ctx context.Context, amountMsat uint64, description string) (*MakeInvoiceResponse, error)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func (api *api) SetTransactionUserLabels(ctx context.Context, id uint, labels ma
|
|||
return api.svc.GetTransactionsService().SetTransactionUserLabels(ctx, id, labels)
|
||||
}
|
||||
|
||||
func (api *api) ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64) (*ListTransactionsResponse, error) {
|
||||
func (api *api) ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64, searchTerm string) (*ListTransactionsResponse, error) {
|
||||
lnClient := api.svc.GetLNClient()
|
||||
if lnClient == nil {
|
||||
return nil, ErrLNClientNotStarted
|
||||
|
|
@ -51,7 +51,7 @@ func (api *api) ListTransactions(ctx context.Context, appId *uint, limit uint64,
|
|||
forceFilterByAppId = true
|
||||
}
|
||||
|
||||
transactions, totalCount, err := api.svc.GetTransactionsService().ListTransactions(ctx, 0, 0, limit, offset, true, false, nil, lnClient, appId, forceFilterByAppId)
|
||||
transactions, totalCount, err := api.svc.GetTransactionsService().ListTransactions(ctx, 0, 0, limit, offset, true, false, nil, lnClient, appId, forceFilterByAppId, searchTerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
ArrowUpDownIcon,
|
||||
ArrowUpIcon,
|
||||
Code2Icon,
|
||||
CreditCardIcon,
|
||||
FileSignatureIcon,
|
||||
|
|
@ -23,7 +27,9 @@ import {
|
|||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import useSWR from "swr";
|
||||
import AppAvatar from "src/components/AppAvatar";
|
||||
import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
|
||||
|
||||
import {
|
||||
CommandDialog,
|
||||
|
|
@ -34,6 +40,11 @@ import {
|
|||
CommandSeparator,
|
||||
} from "src/components/ui/command";
|
||||
import { useApps } from "src/hooks/useApps";
|
||||
import { getTransactionsUrl } from "src/hooks/useTransactions";
|
||||
import { ListTransactionsResponse } from "src/types";
|
||||
import { swrFetcher } from "src/utils/swr";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
interface CommandPaletteProps {
|
||||
open?: boolean;
|
||||
|
|
@ -54,6 +65,12 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) {
|
|||
!!searchText?.length
|
||||
);
|
||||
|
||||
const { data: transactionsBySearch } = useSWR<ListTransactionsResponse>(
|
||||
searchText ? getTransactionsUrl(undefined, 5, 1, searchText) : null,
|
||||
swrFetcher,
|
||||
{ keepPreviousData: true }
|
||||
);
|
||||
|
||||
const runCommand = React.useCallback(
|
||||
(command: () => void) => {
|
||||
onOpenChange?.(false);
|
||||
|
|
@ -244,6 +261,40 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) {
|
|||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
{!!searchText && !!transactionsBySearch?.transactions.length && (
|
||||
<CommandGroup heading="Transactions" forceMount>
|
||||
{transactionsBySearch.transactions.map((tx) => (
|
||||
<CommandItem
|
||||
key={tx.paymentHash}
|
||||
value={`tx-${tx.paymentHash}`}
|
||||
onSelect={() =>
|
||||
runCommand(() => navigate(`/wallet?q=${tx.paymentHash}`))
|
||||
}
|
||||
>
|
||||
{tx.type === "incoming" ? <ArrowDownIcon /> : <ArrowUpIcon />}
|
||||
<span className="truncate sensitive">
|
||||
{tx.description ||
|
||||
(tx.type === "incoming"
|
||||
? tx.metadata?.payer_data?.name
|
||||
? `Received from ${tx.metadata.payer_data.name}`
|
||||
: "Received"
|
||||
: tx.metadata?.recipient_data?.identifier
|
||||
? `Sent to ${tx.metadata.recipient_data.identifier}`
|
||||
: "Sent")}
|
||||
</span>
|
||||
<span className="ml-auto flex shrink-0 items-center gap-2 slashed-zero">
|
||||
<span className="sensitive">
|
||||
{tx.type === "outgoing" ? "-" : "+"}
|
||||
<FormattedBitcoinAmount amountMsat={tx.amountMsat} />
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{dayjs(tx.updatedAt).fromNow()}
|
||||
</span>
|
||||
</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { LucideIcon, ZapIcon } from "lucide-react";
|
||||
import { LucideIcon, SearchIcon, XIcon, ZapIcon } from "lucide-react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useSearchParams } from "react-router";
|
||||
import { CustomPagination } from "src/components/CustomPagination";
|
||||
import EmptyState from "src/components/EmptyState";
|
||||
import Loading from "src/components/Loading";
|
||||
import TransactionItem from "src/components/TransactionItem";
|
||||
import { Badge } from "src/components/ui/badge";
|
||||
import { LIST_TRANSACTIONS_LIMIT } from "src/constants";
|
||||
import { getTransactionsUrl, useTransactions } from "src/hooks/useTransactions";
|
||||
|
||||
|
|
@ -23,21 +25,37 @@ function TransactionsList({
|
|||
emptyVariant,
|
||||
}: TransactionsListProps) {
|
||||
const [page, setPage] = useState(1);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const searchTerm = searchParams.get("q") ?? "";
|
||||
const transactionListRef = useRef<HTMLDivElement>(null);
|
||||
const transactionListKey = getTransactionsUrl(
|
||||
appId,
|
||||
LIST_TRANSACTIONS_LIMIT,
|
||||
page
|
||||
page,
|
||||
searchTerm
|
||||
);
|
||||
const { data: transactionData, isLoading } = useTransactions(
|
||||
const { data: transactionData } = useTransactions(
|
||||
appId,
|
||||
false,
|
||||
LIST_TRANSACTIONS_LIMIT,
|
||||
page
|
||||
page,
|
||||
searchTerm
|
||||
);
|
||||
const transactions = transactionData?.transactions || [];
|
||||
const totalCount = transactionData?.totalCount || 0;
|
||||
|
||||
const clearSearch = () => {
|
||||
setSearchParams(
|
||||
(prev) => {
|
||||
const next = new URLSearchParams(prev);
|
||||
next.delete("q");
|
||||
return next;
|
||||
},
|
||||
{ replace: true }
|
||||
);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setPage(page);
|
||||
transactionListRef.current?.scrollIntoView({
|
||||
|
|
@ -46,19 +64,43 @@ function TransactionsList({
|
|||
});
|
||||
};
|
||||
|
||||
if (isLoading || !transactionData) {
|
||||
if (!transactionData) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={transactionListRef} className="flex flex-col flex-1">
|
||||
{!!searchTerm && (
|
||||
<div className="flex justify-end mb-2">
|
||||
<Badge variant="secondary" className="max-w-56 gap-1">
|
||||
<SearchIcon className="size-3 shrink-0" />
|
||||
<span className="truncate sensitive">{searchTerm}</span>
|
||||
<button
|
||||
onClick={clearSearch}
|
||||
className="cursor-pointer shrink-0"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<XIcon className="size-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
{!transactions.length ? (
|
||||
<EmptyState
|
||||
icon={emptyIcon}
|
||||
title={emptyTitle}
|
||||
description={emptyDescription}
|
||||
variant={emptyVariant}
|
||||
/>
|
||||
searchTerm ? (
|
||||
<EmptyState
|
||||
icon={SearchIcon}
|
||||
title="No transactions found"
|
||||
description="Try a different search term."
|
||||
variant={emptyVariant}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState
|
||||
icon={emptyIcon}
|
||||
title={emptyTitle}
|
||||
description={emptyDescription}
|
||||
variant={emptyVariant}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{transactions?.map((tx) => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
CreditCardIcon,
|
||||
DownloadIcon,
|
||||
EllipsisVerticalIcon,
|
||||
SearchIcon,
|
||||
} from "lucide-react";
|
||||
import { Link } from "react-router";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
|
|
@ -16,6 +17,7 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "src/components/ui/dropdown-menu";
|
||||
import { ProDropdownMenuItem } from "src/components/UpgradeDialog";
|
||||
import { useCommandPaletteContext } from "src/contexts/CommandPaletteContext";
|
||||
import { handleExportTransactions } from "./transactions-utils";
|
||||
|
||||
export function WalletActionsMenu({
|
||||
|
|
@ -23,6 +25,8 @@ export function WalletActionsMenu({
|
|||
}: {
|
||||
hasChannelManagement: boolean;
|
||||
}) {
|
||||
const { setOpen: setCommandPaletteOpen } = useCommandPaletteContext();
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<Button asChild size="icon" variant="ghost">
|
||||
|
|
@ -60,6 +64,10 @@ export function WalletActionsMenu({
|
|||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
</div>
|
||||
<DropdownMenuItem onClick={() => setCommandPaletteOpen(true)}>
|
||||
<SearchIcon className="h-4 w-4" />
|
||||
Search Transactions
|
||||
</DropdownMenuItem>
|
||||
<ProDropdownMenuItem onClick={() => handleExportTransactions()}>
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
Export Transactions
|
||||
|
|
|
|||
|
|
@ -7,12 +7,20 @@ const pollConfiguration: SWRConfiguration = {
|
|||
refreshInterval: 10000,
|
||||
};
|
||||
|
||||
export function getTransactionsUrl(appId?: number, limit = 100, page = 1) {
|
||||
export function getTransactionsUrl(
|
||||
appId?: number,
|
||||
limit = 100,
|
||||
page = 1,
|
||||
searchTerm = ""
|
||||
) {
|
||||
const offset = (page - 1) * limit;
|
||||
let url = `/api/transactions?limit=${limit}&offset=${offset}`;
|
||||
if (appId) {
|
||||
url += `&appId=${appId}`;
|
||||
}
|
||||
if (searchTerm) {
|
||||
url += `&q=${encodeURIComponent(searchTerm)}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
|
@ -21,13 +29,13 @@ export function useTransactions(
|
|||
appId?: number,
|
||||
poll = false,
|
||||
limit = 100,
|
||||
page = 1
|
||||
page = 1,
|
||||
searchTerm = ""
|
||||
) {
|
||||
const url = getTransactionsUrl(appId, limit, page);
|
||||
const url = getTransactionsUrl(appId, limit, page, searchTerm);
|
||||
|
||||
return useSWR<ListTransactionsResponse>(
|
||||
url,
|
||||
swrFetcher,
|
||||
poll ? pollConfiguration : undefined
|
||||
);
|
||||
return useSWR<ListTransactionsResponse>(url, swrFetcher, {
|
||||
...(poll ? pollConfiguration : {}),
|
||||
keepPreviousData: true,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -765,7 +765,9 @@ func (httpSvc *HttpService) listTransactionsHandler(c echo.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
transactions, err := httpSvc.api.ListTransactions(ctx, appId, limit, offset)
|
||||
searchTerm := c.QueryParam("q")
|
||||
|
||||
transactions, err := httpSvc.api.ListTransactions(ctx, appId, limit, offset, searchTerm)
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func (controller *nip47Controller) HandleListTransactionsEvent(ctx context.Conte
|
|||
transactionType = &listParams.Type
|
||||
}
|
||||
|
||||
dbTransactions, totalCount, err := controller.transactionsService.ListTransactions(ctx, listParams.From, listParams.Until, limit, listParams.Offset, listParams.Unpaid || listParams.UnpaidOutgoing, listParams.Unpaid || listParams.UnpaidIncoming, transactionType, controller.lnClient, &appId, false)
|
||||
dbTransactions, totalCount, err := controller.transactionsService.ListTransactions(ctx, listParams.From, listParams.Until, limit, listParams.Offset, listParams.Unpaid || listParams.UnpaidOutgoing, listParams.Unpaid || listParams.UnpaidIncoming, transactionType, controller.lnClient, &appId, false, "")
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"params": listParams,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ func TestListTransactions_Paid(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, false, false, nil, svc.LNClient, nil, false)
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, false, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(1), totalCount)
|
||||
assert.Equal(t, 1, len(incomingTransactions))
|
||||
|
|
@ -114,7 +114,7 @@ func TestListTransactions_UnpaidIncoming(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, false, true, nil, svc.LNClient, nil, false)
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, false, true, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(3), totalCount)
|
||||
assert.Equal(t, 3, len(incomingTransactions))
|
||||
|
|
@ -182,7 +182,7 @@ func TestListTransactions_UnpaidOutgoing(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
outgoingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, true, false, nil, svc.LNClient, nil, false)
|
||||
outgoingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, true, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(3), totalCount)
|
||||
assert.Equal(t, 3, len(outgoingTransactions))
|
||||
|
|
@ -250,7 +250,7 @@ func TestListTransactions_Unpaid(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
outgoingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, true, true, nil, svc.LNClient, nil, false)
|
||||
outgoingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, true, true, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(5), totalCount)
|
||||
assert.Equal(t, 5, len(outgoingTransactions))
|
||||
|
|
@ -286,7 +286,7 @@ func TestListTransactions_Limit(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 1, 0, false, false, nil, svc.LNClient, nil, false)
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 1, 0, false, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(2), totalCount)
|
||||
assert.Equal(t, 1, len(incomingTransactions))
|
||||
|
|
@ -343,7 +343,7 @@ func TestListTransactions_Offset(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 1, 2, false, false, nil, svc.LNClient, nil, false)
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, 0, 0, 1, 2, false, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(4), totalCount)
|
||||
assert.Equal(t, 1, len(incomingTransactions))
|
||||
|
|
@ -394,7 +394,7 @@ func TestListTransactions_FromUntil(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, false, false, nil, svc.LNClient, nil, false)
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, false, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(1), totalCount)
|
||||
assert.Equal(t, 1, len(incomingTransactions))
|
||||
|
|
@ -456,7 +456,7 @@ func TestListTransactions_FromUntilUnpaidOutgoing(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, true, false, nil, svc.LNClient, nil, false)
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, true, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(1), totalCount)
|
||||
assert.Equal(t, "second", incomingTransactions[0].Description)
|
||||
|
|
@ -518,7 +518,7 @@ func TestListTransactions_FromUntilUnpaidIncoming(t *testing.T) {
|
|||
|
||||
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
||||
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, false, true, nil, svc.LNClient, nil, false)
|
||||
incomingTransactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, false, true, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(1), totalCount)
|
||||
assert.Equal(t, "second", incomingTransactions[0].Description)
|
||||
|
|
|
|||
|
|
@ -314,7 +314,7 @@ func TestNotifications_FailedKnownPendingAndExistingFailedPayment(t *testing.T)
|
|||
},
|
||||
}, map[string]interface{}{})
|
||||
|
||||
transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, nil, svc.LNClient, nil, false)
|
||||
transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(2), totalCount)
|
||||
for _, transaction := range transactions {
|
||||
|
|
@ -348,7 +348,7 @@ func TestNotifications_SentAfterMarkedPaymentFailed(t *testing.T) {
|
|||
Properties: tests.MockLNClientTransaction,
|
||||
}, map[string]interface{}{})
|
||||
|
||||
transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, nil, svc.LNClient, nil, false)
|
||||
transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(1), totalCount)
|
||||
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transactions[0].State)
|
||||
|
|
@ -392,7 +392,7 @@ func TestNotifications_SentAfterMarkedTwoPaymentsFailed(t *testing.T) {
|
|||
Properties: tests.MockLNClientTransaction,
|
||||
}, map[string]interface{}{})
|
||||
|
||||
transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, nil, svc.LNClient, nil, false)
|
||||
transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(2), totalCount)
|
||||
assert.Equal(t, latestFailedTransaction.ID, transactions[0].ID)
|
||||
|
|
@ -436,7 +436,7 @@ func TestNotifications_SentWithFailedAndPendingPayment(t *testing.T) {
|
|||
Properties: tests.MockLNClientTransaction,
|
||||
}, map[string]interface{}{})
|
||||
|
||||
transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, nil, svc.LNClient, nil, false)
|
||||
transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, nil, svc.LNClient, nil, false, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(2), totalCount)
|
||||
assert.Equal(t, pendingTransaction.ID, transactions[0].ID)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ type TransactionsService interface {
|
|||
events.EventSubscriber
|
||||
MakeInvoice(ctx context.Context, amountMsat uint64, description string, descriptionHash string, expiry uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint, throughNodePubkey *string) (*Transaction, error)
|
||||
LookupTransaction(ctx context.Context, paymentHash string, transactionType *string, lnClient lnclient.LNClient, appId *uint) (*Transaction, error)
|
||||
ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint, forceFilterByAppId bool) (transactions []Transaction, totalCount uint64, err error)
|
||||
ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint, forceFilterByAppId bool, searchTerm string) (transactions []Transaction, totalCount uint64, err error)
|
||||
SendPaymentSync(payReq string, amountMsat *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
SendKeysend(amountMsat uint64, destination string, customRecords []lnclient.TLVRecord, preimage string, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
MakeHoldInvoice(ctx context.Context, amountMsat uint64, description string, descriptionHash string, expiry uint64, paymentHash string, minCltvExpiryDelta *uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
|
|
@ -628,7 +628,7 @@ func (svc *transactionsService) LookupTransaction(ctx context.Context, paymentHa
|
|||
return &transaction, nil
|
||||
}
|
||||
|
||||
func (svc *transactionsService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint, forceFilterByAppId bool) (transactions []Transaction, totalCount uint64, err error) {
|
||||
func (svc *transactionsService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint, forceFilterByAppId bool, searchTerm string) (transactions []Transaction, totalCount uint64, err error) {
|
||||
svc.checkUnsettledTransactions(ctx, lnClient)
|
||||
|
||||
var isIsolatedApp bool
|
||||
|
|
@ -664,6 +664,12 @@ func (svc *transactionsService) ListTransactions(ctx context.Context, from, unti
|
|||
tx = tx.Where("type = ?", *transactionType)
|
||||
}
|
||||
|
||||
if searchTerm != "" {
|
||||
likeTerm := "%" + strings.ToLower(searchTerm) + "%"
|
||||
// CAST so the metadata JSON column is searchable on both SQLite and PostgreSQL
|
||||
tx = tx.Where("LOWER(description) LIKE ? OR payment_hash = ? OR LOWER(CAST(metadata AS TEXT)) LIKE ?", likeTerm, strings.ToLower(searchTerm), likeTerm)
|
||||
}
|
||||
|
||||
if from > 0 {
|
||||
tx = tx.Where("updated_at >= ?", time.Unix(int64(from), 0))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,9 +326,10 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
limit := uint64(20)
|
||||
offset := uint64(0)
|
||||
var appId *uint
|
||||
searchTerm := ""
|
||||
|
||||
// Extract limit and offset parameters
|
||||
paramRegex := regexp.MustCompile(`[?&](limit|offset|appId)=([^&]+)`)
|
||||
paramRegex := regexp.MustCompile(`[?&](limit|offset|appId|q)=([^&]+)`)
|
||||
paramMatches := paramRegex.FindAllStringSubmatch(route, -1)
|
||||
for _, match := range paramMatches {
|
||||
switch match[1] {
|
||||
|
|
@ -345,10 +346,14 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
var unsignedAppId = uint(parsedAppId)
|
||||
appId = &unsignedAppId
|
||||
}
|
||||
case "q":
|
||||
if unescaped, err := url.QueryUnescape(match[2]); err == nil {
|
||||
searchTerm = unescaped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transactions, err := app.api.ListTransactions(ctx, appId, limit, offset)
|
||||
transactions, err := app.api.ListTransactions(ctx, appId, limit, offset, searchTerm)
|
||||
if err != nil {
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue