mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
feat: increase isolated app balance (#710)
This commit is contained in:
parent
430c9fe9d2
commit
731d248129
15 changed files with 300 additions and 92 deletions
|
|
@ -20,6 +20,7 @@ import (
|
|||
"golang.org/x/oauth2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/getAlby/hub/apps"
|
||||
"github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/db"
|
||||
|
|
@ -368,9 +369,9 @@ func (svc *albyOAuthService) DrainSharedWallet(ctx context.Context, lnClient lnc
|
|||
10 // Alby fee reserve (10 sats)
|
||||
|
||||
if amountSat < 1 {
|
||||
return errors.New("Not enough balance remaining")
|
||||
return errors.New("not enough balance remaining")
|
||||
}
|
||||
amount := amountSat * 1000
|
||||
amount := uint64(amountSat * 1000)
|
||||
|
||||
logger.Logger.WithField("amount", amount).WithError(err).Error("Draining Alby shared wallet funds")
|
||||
|
||||
|
|
@ -526,7 +527,7 @@ func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.
|
|||
scopes = append(scopes, constants.NOTIFICATIONS_SCOPE)
|
||||
}
|
||||
|
||||
app, _, err := db.NewDBService(svc.db, svc.eventPublisher).CreateApp(
|
||||
app, _, err := apps.NewAppsService(svc.db, svc.eventPublisher).CreateApp(
|
||||
ALBY_ACCOUNT_APP_NAME,
|
||||
connectionPubkey,
|
||||
budget,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
|
||||
"github.com/getAlby/hub/alby"
|
||||
"github.com/getAlby/hub/apps"
|
||||
"github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/db"
|
||||
|
|
@ -33,7 +34,7 @@ import (
|
|||
|
||||
type api struct {
|
||||
db *gorm.DB
|
||||
dbSvc db.DBService
|
||||
appsSvc apps.AppsService
|
||||
cfg config.Config
|
||||
svc service.Service
|
||||
permissionsSvc permissions.PermissionsService
|
||||
|
|
@ -46,7 +47,7 @@ type api struct {
|
|||
func NewAPI(svc service.Service, gormDB *gorm.DB, config config.Config, keys keys.Keys, albyOAuthSvc alby.AlbyOAuthService, eventPublisher events.EventPublisher) *api {
|
||||
return &api{
|
||||
db: gormDB,
|
||||
dbSvc: db.NewDBService(gormDB, eventPublisher),
|
||||
appsSvc: apps.NewAppsService(gormDB, eventPublisher),
|
||||
cfg: config,
|
||||
svc: svc,
|
||||
permissionsSvc: permissions.NewPermissionsService(gormDB, eventPublisher),
|
||||
|
|
@ -71,7 +72,7 @@ func (api *api) CreateApp(createAppRequest *CreateAppRequest) (*CreateAppRespons
|
|||
}
|
||||
}
|
||||
|
||||
app, pairingSecretKey, err := api.dbSvc.CreateApp(
|
||||
app, pairingSecretKey, err := api.appsSvc.CreateApp(
|
||||
createAppRequest.Name,
|
||||
createAppRequest.Pubkey,
|
||||
createAppRequest.MaxAmountSat,
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ import (
|
|||
|
||||
type API interface {
|
||||
CreateApp(createAppRequest *CreateAppRequest) (*CreateAppResponse, error)
|
||||
UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) error
|
||||
DeleteApp(userApp *db.App) error
|
||||
GetApp(userApp *db.App) *App
|
||||
UpdateApp(app *db.App, updateAppRequest *UpdateAppRequest) error
|
||||
TopupIsolatedApp(ctx context.Context, app *db.App, amountMsat uint64) error
|
||||
DeleteApp(app *db.App) error
|
||||
GetApp(app *db.App) *App
|
||||
ListApps() ([]App, error)
|
||||
ListChannels(ctx context.Context) ([]Channel, error)
|
||||
GetChannelPeerSuggestions(ctx context.Context) ([]alby.ChannelPeerSuggestion, error)
|
||||
|
|
@ -36,7 +37,7 @@ type API interface {
|
|||
GetBalances(ctx context.Context) (*BalancesResponse, error)
|
||||
ListTransactions(ctx context.Context, limit uint64, offset uint64) (*ListTransactionsResponse, error)
|
||||
SendPayment(ctx context.Context, invoice string) (*SendPaymentResponse, error)
|
||||
CreateInvoice(ctx context.Context, amount int64, description string) (*MakeInvoiceResponse, error)
|
||||
CreateInvoice(ctx context.Context, amount uint64, description string) (*MakeInvoiceResponse, error)
|
||||
LookupInvoice(ctx context.Context, paymentHash string) (*LookupInvoiceResponse, error)
|
||||
RequestMempoolApi(endpoint string) (interface{}, error)
|
||||
GetInfo(ctx context.Context) (*InfoResponse, error)
|
||||
|
|
@ -86,6 +87,10 @@ type UpdateAppRequest struct {
|
|||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type TopupIsolatedAppRequest struct {
|
||||
AmountSat uint64 `json:"amountSat"`
|
||||
}
|
||||
|
||||
type CreateAppRequest struct {
|
||||
Name string `json:"name"`
|
||||
Pubkey string `json:"pubkey"`
|
||||
|
|
@ -283,7 +288,7 @@ type SignMessageResponse struct {
|
|||
}
|
||||
|
||||
type MakeInvoiceRequest struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Amount uint64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/transactions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (api *api) CreateInvoice(ctx context.Context, amount int64, description string) (*MakeInvoiceResponse, error) {
|
||||
func (api *api) CreateInvoice(ctx context.Context, amount uint64, description string) (*MakeInvoiceResponse, error) {
|
||||
if api.svc.GetLNClient() == nil {
|
||||
return nil, errors.New("LNClient not started")
|
||||
}
|
||||
|
|
@ -116,6 +117,24 @@ func toApiTransaction(transaction *transactions.Transaction) *Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
func (api *api) TopupIsolatedApp(ctx context.Context, userApp *db.App, amountMsat uint64) error {
|
||||
if api.svc.GetLNClient() == nil {
|
||||
return errors.New("LNClient not started")
|
||||
}
|
||||
if !userApp.Isolated {
|
||||
return errors.New("app is not isolated")
|
||||
}
|
||||
|
||||
transaction, err := api.svc.GetTransactionsService().MakeInvoice(ctx, amountMsat, "top up", "", 0, nil, api.svc.GetLNClient(), &userApp.ID, nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = api.svc.GetTransactionsService().SendPaymentSync(ctx, transaction.PaymentRequest, api.svc.GetLNClient(), nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func toApiBoostagram(boostagram *transactions.Boostagram) *Boostagram {
|
||||
return &Boostagram{
|
||||
AppName: boostagram.AppName,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package db
|
||||
package apps
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/getAlby/hub/constants"
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
|
|
@ -16,19 +17,24 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type dbService struct {
|
||||
type AppsService interface {
|
||||
CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool, metadata map[string]interface{}) (*db.App, string, error)
|
||||
GetAppByPubkey(pubkey string) *db.App
|
||||
}
|
||||
|
||||
type appsService struct {
|
||||
db *gorm.DB
|
||||
eventPublisher events.EventPublisher
|
||||
}
|
||||
|
||||
func NewDBService(db *gorm.DB, eventPublisher events.EventPublisher) *dbService {
|
||||
return &dbService{
|
||||
func NewAppsService(db *gorm.DB, eventPublisher events.EventPublisher) *appsService {
|
||||
return &appsService{
|
||||
db: db,
|
||||
eventPublisher: eventPublisher,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool, metadata map[string]interface{}) (*App, string, error) {
|
||||
func (svc *appsService) CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool, metadata map[string]interface{}) (*db.App, string, error) {
|
||||
if isolated && (slices.Contains(scopes, constants.SIGN_MESSAGE_SCOPE)) {
|
||||
// cannot sign messages because the isolated app is a custodial subaccount
|
||||
return nil, "", errors.New("isolated app cannot have sign_message scope")
|
||||
|
|
@ -59,7 +65,7 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64,
|
|||
}
|
||||
}
|
||||
|
||||
app := App{Name: name, NostrPubkey: pairingPublicKey, Isolated: isolated, Metadata: datatypes.JSON(metadataBytes)}
|
||||
app := db.App{Name: name, NostrPubkey: pairingPublicKey, Isolated: isolated, Metadata: datatypes.JSON(metadataBytes)}
|
||||
|
||||
err := svc.db.Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Save(&app).Error
|
||||
|
|
@ -68,7 +74,7 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64,
|
|||
}
|
||||
|
||||
for _, scope := range scopes {
|
||||
appPermission := AppPermission{
|
||||
appPermission := db.AppPermission{
|
||||
App: app,
|
||||
Scope: scope,
|
||||
ExpiresAt: expiresAt,
|
||||
|
|
@ -100,3 +106,12 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64,
|
|||
|
||||
return &app, pairingSecretKey, nil
|
||||
}
|
||||
|
||||
func (svc *appsService) GetAppByPubkey(pubkey string) *db.App {
|
||||
dbApp := db.App{}
|
||||
findResult := svc.db.Where("nostr_pubkey = ?", pubkey).First(&dbApp)
|
||||
if findResult.RowsAffected == 0 {
|
||||
return nil
|
||||
}
|
||||
return &dbApp
|
||||
}
|
||||
|
|
@ -86,10 +86,6 @@ type Transaction struct {
|
|||
FailureReason string
|
||||
}
|
||||
|
||||
type DBService interface {
|
||||
CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool, metadata map[string]interface{}) (*App, string, error)
|
||||
}
|
||||
|
||||
const (
|
||||
REQUEST_EVENT_STATE_HANDLER_EXECUTING = "executing"
|
||||
REQUEST_EVENT_STATE_HANDLER_EXECUTED = "executed"
|
||||
|
|
|
|||
90
frontend/src/components/IsolatedAppTopupDialog.tsx
Normal file
90
frontend/src/components/IsolatedAppTopupDialog.tsx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import React from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "src/components/ui/alert-dialog";
|
||||
import { Input } from "src/components/ui/input";
|
||||
import { LoadingButton } from "src/components/ui/loading-button";
|
||||
import { useToast } from "src/components/ui/use-toast";
|
||||
import { useApp } from "src/hooks/useApp";
|
||||
import { handleRequestError } from "src/utils/handleRequestError";
|
||||
import { request } from "src/utils/request";
|
||||
|
||||
type IsolatedAppTopupProps = {
|
||||
appPubkey: string;
|
||||
};
|
||||
|
||||
export function IsolatedAppTopupDialog({
|
||||
appPubkey,
|
||||
children,
|
||||
}: React.PropsWithChildren<IsolatedAppTopupProps>) {
|
||||
const { mutate: reloadApp } = useApp(appPubkey);
|
||||
const [amountSat, setAmountSat] = React.useState("");
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const { toast } = useToast();
|
||||
async function onSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
try {
|
||||
await request(`/api/apps/${appPubkey}/topup`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
amountSat: +amountSat,
|
||||
}),
|
||||
});
|
||||
await reloadApp();
|
||||
toast({
|
||||
title: "Successfully increased isolated app balance",
|
||||
});
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
handleRequestError(
|
||||
toast,
|
||||
"Failed to increase isolated app balance",
|
||||
error
|
||||
);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<form onSubmit={onSubmit}>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Increase Isolated App Balance</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
As the owner of your Alby Hub, you must make sure you have enough
|
||||
funds in your channels for this app to make payments matching its
|
||||
balance.
|
||||
</AlertDialogDescription>
|
||||
<Input
|
||||
autoFocus
|
||||
id="amount"
|
||||
type="number"
|
||||
required
|
||||
value={amountSat}
|
||||
onChange={(e) => {
|
||||
setAmountSat(e.target.value.trim());
|
||||
}}
|
||||
/>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter className="mt-5">
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<LoadingButton loading={loading}>Top Up</LoadingButton>
|
||||
</AlertDialogFooter>
|
||||
</form>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { Link, Navigate, useLocation, useNavigate } from "react-router-dom";
|
|||
|
||||
import AppHeader from "src/components/AppHeader";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import { IsolatedAppTopupDialog } from "src/components/IsolatedAppTopupDialog";
|
||||
import Loading from "src/components/Loading";
|
||||
import QRCode from "src/components/QRCode";
|
||||
import { SuggestedApp, suggestedApps } from "src/components/SuggestedAppData";
|
||||
|
|
@ -87,22 +88,36 @@ function AppCreatedInternal() {
|
|||
/>
|
||||
<div className="flex flex-col gap-3 sensitive">
|
||||
<div>
|
||||
<p>
|
||||
1. Open{" "}
|
||||
{appstoreApp?.webLink ? (
|
||||
<ExternalLink
|
||||
className="font-semibold underline"
|
||||
to={appstoreApp.webLink}
|
||||
>
|
||||
{appstoreApp.title}
|
||||
</ExternalLink>
|
||||
) : (
|
||||
"the app you wish to connect"
|
||||
)}{" "}
|
||||
and look for a way to attach a wallet (most apps provide this option
|
||||
in settings)
|
||||
</p>
|
||||
<p>2. Scan or paste the connection secret</p>
|
||||
<ol className="list-decimal list-inside">
|
||||
<li>
|
||||
Open{" "}
|
||||
{appstoreApp?.webLink ? (
|
||||
<ExternalLink
|
||||
className="font-semibold underline"
|
||||
to={appstoreApp.webLink}
|
||||
>
|
||||
{appstoreApp.title}
|
||||
</ExternalLink>
|
||||
) : (
|
||||
"the app you wish to connect"
|
||||
)}{" "}
|
||||
and look for a way to attach a wallet (most apps provide this
|
||||
option in settings)
|
||||
</li>
|
||||
{app?.isolated && (
|
||||
<li>
|
||||
Optional: Increase isolated balance (
|
||||
{new Intl.NumberFormat().format(Math.floor(app.balance / 1000))}{" "}
|
||||
sats){" "}
|
||||
<IsolatedAppTopupDialog appPubkey={app.nostrPubkey}>
|
||||
<Button size="sm" variant="secondary">
|
||||
Increase
|
||||
</Button>
|
||||
</IsolatedAppTopupDialog>
|
||||
</li>
|
||||
)}
|
||||
<li>Scan or paste the connection secret</li>
|
||||
</ol>
|
||||
</div>
|
||||
{app && (
|
||||
<ConnectAppCard
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { request } from "src/utils/request"; // build the project for this to ap
|
|||
import { PencilIcon } from "lucide-react";
|
||||
import AppAvatar from "src/components/AppAvatar";
|
||||
import AppHeader from "src/components/AppHeader";
|
||||
import { IsolatedAppTopupDialog } from "src/components/IsolatedAppTopupDialog";
|
||||
import Loading from "src/components/Loading";
|
||||
import Permissions from "src/components/Permissions";
|
||||
import {
|
||||
|
|
@ -244,7 +245,12 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
|
|||
{new Intl.NumberFormat().format(
|
||||
Math.floor(app.balance / 1000)
|
||||
)}{" "}
|
||||
sats
|
||||
sats{" "}
|
||||
<IsolatedAppTopupDialog appPubkey={app.nostrPubkey}>
|
||||
<Button size="sm" variant="secondary">
|
||||
Increase
|
||||
</Button>
|
||||
</IsolatedAppTopupDialog>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import React from "react";
|
|||
import AppHeader from "src/components/AppHeader";
|
||||
import AppCard from "src/components/connections/AppCard";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import { IsolatedAppTopupDialog } from "src/components/IsolatedAppTopupDialog";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
|
|
@ -282,6 +283,21 @@ export function UncleJim() {
|
|||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
{app && (
|
||||
<>
|
||||
<p className="mt-5">
|
||||
{name} currently has{" "}
|
||||
{new Intl.NumberFormat().format(Math.floor(app.balance / 1000))}{" "}
|
||||
sats
|
||||
</p>
|
||||
<IsolatedAppTopupDialog appPubkey={appPublicKey}>
|
||||
<Button size="sm" variant="secondary">
|
||||
Increase
|
||||
</Button>
|
||||
</IsolatedAppTopupDialog>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/getAlby/hub/apps"
|
||||
"github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/events"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/service"
|
||||
|
|
@ -43,6 +43,7 @@ type HttpService struct {
|
|||
cfg config.Config
|
||||
eventPublisher events.EventPublisher
|
||||
db *gorm.DB
|
||||
appsSvc apps.AppsService
|
||||
}
|
||||
|
||||
func NewHttpService(svc service.Service, eventPublisher events.EventPublisher) *HttpService {
|
||||
|
|
@ -52,6 +53,7 @@ func NewHttpService(svc service.Service, eventPublisher events.EventPublisher) *
|
|||
cfg: svc.GetConfig(),
|
||||
eventPublisher: eventPublisher,
|
||||
db: svc.GetDB(),
|
||||
appsSvc: apps.NewAppsService(svc.GetDB(), eventPublisher),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +118,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
|
|||
restrictedGroup.GET("/api/apps/:pubkey", httpSvc.appsShowHandler)
|
||||
restrictedGroup.PATCH("/api/apps/:pubkey", httpSvc.appsUpdateHandler)
|
||||
restrictedGroup.DELETE("/api/apps/:pubkey", httpSvc.appsDeleteHandler)
|
||||
restrictedGroup.POST("/api/apps/:pubkey/topup", httpSvc.isolatedAppTopupHandler)
|
||||
restrictedGroup.POST("/api/apps", httpSvc.appsCreateHandler)
|
||||
restrictedGroup.POST("/api/mnemonic", httpSvc.mnemonicHandler)
|
||||
restrictedGroup.PATCH("/api/backup-reminder", httpSvc.backupReminderHandler)
|
||||
|
|
@ -756,16 +759,15 @@ func (httpSvc *HttpService) appsListHandler(c echo.Context) error {
|
|||
func (httpSvc *HttpService) appsShowHandler(c echo.Context) error {
|
||||
|
||||
// TODO: move this to DB service
|
||||
dbApp := db.App{}
|
||||
findResult := httpSvc.db.Where("nostr_pubkey = ?", c.Param("pubkey")).First(&dbApp)
|
||||
dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
|
||||
|
||||
if findResult.RowsAffected == 0 {
|
||||
if dbApp == nil {
|
||||
return c.JSON(http.StatusNotFound, ErrorResponse{
|
||||
Message: "App does not exist",
|
||||
Message: "App not found",
|
||||
})
|
||||
}
|
||||
|
||||
response := httpSvc.api.GetApp(&dbApp)
|
||||
response := httpSvc.api.GetApp(dbApp)
|
||||
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
|
@ -778,17 +780,15 @@ func (httpSvc *HttpService) appsUpdateHandler(c echo.Context) error {
|
|||
})
|
||||
}
|
||||
|
||||
// TODO: move this to DB service
|
||||
dbApp := db.App{}
|
||||
findResult := httpSvc.db.Where("nostr_pubkey = ?", c.Param("pubkey")).First(&dbApp)
|
||||
dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
|
||||
|
||||
if findResult.RowsAffected == 0 {
|
||||
if dbApp == nil {
|
||||
return c.JSON(http.StatusNotFound, ErrorResponse{
|
||||
Message: "App does not exist",
|
||||
Message: "App not found",
|
||||
})
|
||||
}
|
||||
|
||||
err := httpSvc.api.UpdateApp(&dbApp, &requestData)
|
||||
err := httpSvc.api.UpdateApp(dbApp, &requestData)
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to update app")
|
||||
|
|
@ -800,28 +800,43 @@ func (httpSvc *HttpService) appsUpdateHandler(c echo.Context) error {
|
|||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) appsDeleteHandler(c echo.Context) error {
|
||||
pubkey := c.Param("pubkey")
|
||||
if pubkey == "" {
|
||||
func (httpSvc *HttpService) isolatedAppTopupHandler(c echo.Context) error {
|
||||
var requestData api.TopupIsolatedAppRequest
|
||||
if err := c.Bind(&requestData); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, ErrorResponse{
|
||||
Message: "Invalid pubkey parameter",
|
||||
})
|
||||
}
|
||||
// TODO: move this to DB service
|
||||
dbApp := db.App{}
|
||||
result := httpSvc.db.Where("nostr_pubkey = ?", pubkey).First(&dbApp)
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
return c.JSON(http.StatusNotFound, ErrorResponse{
|
||||
Message: "App not found",
|
||||
})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: "Failed to fetch app",
|
||||
Message: fmt.Sprintf("Bad request: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
if err := httpSvc.api.DeleteApp(&dbApp); err != nil {
|
||||
dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
|
||||
|
||||
if dbApp == nil {
|
||||
return c.JSON(http.StatusNotFound, ErrorResponse{
|
||||
Message: "App not found",
|
||||
})
|
||||
}
|
||||
|
||||
err := httpSvc.api.TopupIsolatedApp(c.Request().Context(), dbApp, requestData.AmountSat*1000)
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to topup isolated app")
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: fmt.Sprintf("Failed to topup isolated app: %v", err),
|
||||
})
|
||||
}
|
||||
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) appsDeleteHandler(c echo.Context) error {
|
||||
dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
|
||||
if dbApp == nil {
|
||||
return c.JSON(http.StatusNotFound, ErrorResponse{
|
||||
Message: "App not found",
|
||||
})
|
||||
}
|
||||
|
||||
if err := httpSvc.api.DeleteApp(dbApp); err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: "Failed to delete app",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import (
|
|||
)
|
||||
|
||||
type makeInvoiceParams struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Amount uint64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
DescriptionHash string `json:"description_hash"`
|
||||
Expiry int64 `json:"expiry"`
|
||||
Expiry uint64 `json:"expiry"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
type makeInvoiceResponse struct {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ type transactionsService struct {
|
|||
|
||||
type TransactionsService interface {
|
||||
events.EventSubscriber
|
||||
MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
MakeInvoice(ctx context.Context, amount uint64, description string, descriptionHash string, expiry uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*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) (transactions []Transaction, err error)
|
||||
SendPaymentSync(ctx context.Context, payReq string, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
|
||||
|
|
@ -129,7 +129,7 @@ func NewTransactionsService(db *gorm.DB, eventPublisher events.EventPublisher) *
|
|||
}
|
||||
}
|
||||
|
||||
func (svc *transactionsService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) {
|
||||
func (svc *transactionsService) MakeInvoice(ctx context.Context, amount uint64, description string, descriptionHash string, expiry uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) {
|
||||
var metadataBytes []byte
|
||||
if metadata != nil {
|
||||
var err error
|
||||
|
|
@ -143,7 +143,7 @@ func (svc *transactionsService) MakeInvoice(ctx context.Context, amount int64, d
|
|||
}
|
||||
}
|
||||
|
||||
lnClientTransaction, err := lnClient.MakeInvoice(ctx, amount, description, descriptionHash, expiry)
|
||||
lnClientTransaction, err := lnClient.MakeInvoice(ctx, int64(amount), description, descriptionHash, int64(expiry))
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to create transaction")
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"embed"
|
||||
|
||||
"github.com/getAlby/hub/api"
|
||||
"github.com/getAlby/hub/apps"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/getAlby/hub/service"
|
||||
"github.com/wailsapp/wails/v2"
|
||||
|
|
@ -16,17 +17,19 @@ import (
|
|||
)
|
||||
|
||||
type WailsApp struct {
|
||||
ctx context.Context
|
||||
svc service.Service
|
||||
api api.API
|
||||
db *gorm.DB
|
||||
ctx context.Context
|
||||
svc service.Service
|
||||
api api.API
|
||||
db *gorm.DB
|
||||
appsSvc apps.AppsService
|
||||
}
|
||||
|
||||
func NewApp(svc service.Service) *WailsApp {
|
||||
return &WailsApp{
|
||||
svc: svc,
|
||||
api: api.NewAPI(svc, svc.GetDB(), svc.GetConfig(), svc.GetKeys(), svc.GetAlbyOAuthSvc(), svc.GetEventPublisher()),
|
||||
db: svc.GetDB(),
|
||||
svc: svc,
|
||||
api: api.NewAPI(svc, svc.GetDB(), svc.GetConfig(), svc.GetKeys(), svc.GetAlbyOAuthSvc(), svc.GetEventPublisher()),
|
||||
db: svc.GetDB(),
|
||||
appsSvc: apps.NewAppsService(svc.GetDB(), svc.GetEventPublisher()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import (
|
|||
|
||||
"github.com/getAlby/hub/alby"
|
||||
"github.com/getAlby/hub/api"
|
||||
"github.com/getAlby/hub/db"
|
||||
"github.com/getAlby/hub/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
|
@ -59,18 +58,14 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
switch {
|
||||
case len(appMatch) > 1:
|
||||
pubkey := appMatch[1]
|
||||
|
||||
// TODO: move this to DB service
|
||||
dbApp := db.App{}
|
||||
findResult := app.db.Where("nostr_pubkey = ?", pubkey).First(&dbApp)
|
||||
|
||||
if findResult.RowsAffected == 0 {
|
||||
dbApp := app.appsSvc.GetAppByPubkey(pubkey)
|
||||
if dbApp == nil {
|
||||
return WailsRequestRouterResponse{Body: nil, Error: "App does not exist"}
|
||||
}
|
||||
|
||||
switch method {
|
||||
case "GET":
|
||||
app := app.api.GetApp(&dbApp)
|
||||
app := app.api.GetApp(dbApp)
|
||||
return WailsRequestRouterResponse{Body: app, Error: ""}
|
||||
case "PATCH":
|
||||
updateAppRequest := &api.UpdateAppRequest{}
|
||||
|
|
@ -83,13 +78,13 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
}).WithError(err).Error("Failed to decode request to wails router")
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
err = app.api.UpdateApp(&dbApp, updateAppRequest)
|
||||
err = app.api.UpdateApp(dbApp, updateAppRequest)
|
||||
if err != nil {
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: nil, Error: ""}
|
||||
case "DELETE":
|
||||
err := app.api.DeleteApp(&dbApp)
|
||||
err := app.api.DeleteApp(dbApp)
|
||||
if err != nil {
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
|
|
@ -97,6 +92,37 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
}
|
||||
}
|
||||
|
||||
appTopupRegex := regexp.MustCompile(
|
||||
`/api/apps/([0-9a-f]+)/topup`,
|
||||
)
|
||||
|
||||
appTopupMatch := appTopupRegex.FindStringSubmatch(route)
|
||||
|
||||
switch {
|
||||
case len(appTopupMatch) > 1:
|
||||
pubkey := appTopupMatch[1]
|
||||
dbApp := app.appsSvc.GetAppByPubkey(pubkey)
|
||||
if dbApp == nil {
|
||||
return WailsRequestRouterResponse{Body: nil, Error: "App does not exist"}
|
||||
}
|
||||
|
||||
topupIsolatedAppRequest := &api.TopupIsolatedAppRequest{}
|
||||
err := json.Unmarshal([]byte(body), topupIsolatedAppRequest)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"route": route,
|
||||
"method": method,
|
||||
"body": body,
|
||||
}).WithError(err).Error("Failed to decode request to wails router")
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
err = app.api.TopupIsolatedApp(ctx, dbApp, topupIsolatedAppRequest.AmountSat*1000)
|
||||
if err != nil {
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: nil, Error: ""}
|
||||
}
|
||||
|
||||
peerChannelRegex := regexp.MustCompile(
|
||||
`/api/peers/([^/]+)/channels/([^/]+)`,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue