feat: add transaction search

This commit is contained in:
René Aaron 2026-06-13 00:13:44 +02:00
parent bf9c346a98
commit bea8daac9f
12 changed files with 163 additions and 41 deletions

View file

@ -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)

View file

@ -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
}

View file

@ -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>
);

View file

@ -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) => {

View file

@ -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

View file

@ -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,
});
}

View file

@ -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{

View file

@ -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,

View file

@ -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)

View file

@ -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)

View file

@ -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))
}

View file

@ -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()}
}