http_service.go raw
1 package http
2
3 import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "net/http"
9 "slices"
10 "strconv"
11 "strings"
12 "time"
13
14 "github.com/golang-jwt/jwt/v5"
15 echojwt "github.com/labstack/echo-jwt/v4"
16 "github.com/labstack/echo/v4"
17 "github.com/labstack/echo/v4/middleware"
18 "github.com/sirupsen/logrus"
19 "gorm.io/gorm"
20
21 "github.com/getAlby/hub/apps"
22 "github.com/getAlby/hub/config"
23 "github.com/getAlby/hub/events"
24 "github.com/getAlby/hub/logger"
25 "github.com/getAlby/hub/service"
26
27 "github.com/getAlby/hub/api"
28 "github.com/getAlby/hub/frontend"
29 )
30
31 type authTokenResponse struct {
32 Token string `json:"token"`
33 }
34
35 type jwtCustomClaims struct {
36 // we can add extra claims here
37 // Name string `json:"name"`
38 // Admin bool `json:"admin"`
39 Permission string `json:"permission,omitempty"` // "full" or "readonly"
40 jwt.RegisteredClaims
41 }
42
43 type HttpService struct {
44 api api.API
45 albyHttpSvc *AlbyHttpService
46 cfg config.Config
47 eventPublisher events.EventPublisher
48 db *gorm.DB
49 appsSvc apps.AppsService
50 }
51
52 func NewHttpService(svc service.Service, eventPublisher events.EventPublisher) *HttpService {
53 return &HttpService{
54 api: api.NewAPI(svc, svc.GetDB(), svc.GetConfig(), svc.GetKeys(), svc.GetAlbySvc(), svc.GetAlbyOAuthSvc(), eventPublisher),
55 albyHttpSvc: NewAlbyHttpService(svc, svc.GetAlbySvc(), svc.GetAlbyOAuthSvc(), svc.GetConfig().GetEnv()),
56 cfg: svc.GetConfig(),
57 eventPublisher: eventPublisher,
58 db: svc.GetDB(),
59 appsSvc: apps.NewAppsService(svc.GetDB(), eventPublisher, svc.GetKeys(), svc.GetConfig()),
60 }
61 }
62
63 func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
64 e.HideBanner = true
65
66 e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
67 ContentTypeNosniff: "nosniff",
68 XFrameOptions: "DENY",
69 // when making changes here, also update the CSP in frontend/vite.config.ts
70 ContentSecurityPolicy: "default-src 'self'; img-src 'self' https://uploads.getalby-assets.com https://cdn.getalby-assets.com https://getalby.com; connect-src 'self' https://api.getalby.com https://getalby.com https://zapplanner.albylabs.com wss://relay.mleku.dev wss://relay.getalby.com wss://relay2.getalby.com; frame-src https://www.youtube-nocookie.com",
71 ReferrerPolicy: "no-referrer",
72 }))
73 e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
74 LogURI: true,
75 LogStatus: true,
76 LogRemoteIP: true,
77 LogUserAgent: true,
78 LogHost: true,
79 LogRequestID: true,
80 LogValuesFunc: func(c echo.Context, values middleware.RequestLoggerValues) error {
81 logger.Logger.WithFields(logrus.Fields{
82 "uri": values.URI,
83 "status": values.Status,
84 "remote_ip": values.RemoteIP,
85 "user_agent": values.UserAgent,
86 "host": values.Host,
87 "request_id": values.RequestID,
88 }).Info("handled API request")
89 return nil
90 },
91 }))
92
93 e.Use(middleware.Recover())
94 e.Use(middleware.RequestID())
95
96 e.GET("/api/info", httpSvc.infoHandler)
97 e.POST("/api/setup", httpSvc.setupHandler)
98 e.POST("/api/restore", httpSvc.restoreBackupHandler)
99
100 // A single global rate limiter (one bucket for all callers, not per-IP)
101 // shared by every endpoint that verifies the unlock password, to bound how
102 // fast the password can be guessed.
103 unlockRateLimiter := middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
104 Store: middleware.NewRateLimiterMemoryStoreWithConfig(
105 // burst of 2 so unlocking and then immediately acting is not blocked
106 middleware.RateLimiterMemoryStoreConfig{Rate: 1, Burst: 2},
107 ),
108 IdentifierExtractor: func(c echo.Context) (string, error) {
109 return "", nil
110 },
111 })
112 e.POST("/api/start", httpSvc.startHandler, unlockRateLimiter)
113 e.POST("/api/unlock", httpSvc.unlockHandler, unlockRateLimiter)
114 e.POST("/api/backup", httpSvc.createBackupHandler, unlockRateLimiter)
115 e.GET("/logout", httpSvc.logoutHandler)
116
117 frontend.RegisterHandlers(e)
118
119 // restricted routes
120 // Configure middleware with the custom claims type
121 jwtConfig := echojwt.Config{
122 NewClaimsFunc: func(c echo.Context) jwt.Claims {
123 return new(jwtCustomClaims)
124 },
125 // use a custom key func as the JWT secret will change if the user changes their unlock password
126 KeyFunc: func(token *jwt.Token) (interface{}, error) {
127 secret, err := httpSvc.cfg.GetJWTSecret()
128 if err != nil {
129 return nil, err
130 }
131 return []byte(secret), nil
132 },
133 }
134 // Read-only API group - accessible to both full and readonly tokens
135 readOnlyApiGroup := e.Group("/api")
136 readOnlyApiGroup.Use(echojwt.WithConfig(jwtConfig))
137
138 readOnlyApiGroup.GET("/apps", httpSvc.appsListHandler)
139 readOnlyApiGroup.GET("/apps/:pubkey", httpSvc.appsShowByPubkeyHandler)
140 readOnlyApiGroup.GET("/v2/apps/:id", httpSvc.appsShowHandler)
141 readOnlyApiGroup.GET("/channels", httpSvc.channelsListHandler)
142 readOnlyApiGroup.GET("/channels/suggestions", httpSvc.channelPeerSuggestionsHandler)
143 readOnlyApiGroup.GET("/channel-offer", httpSvc.channelOfferHandler)
144 readOnlyApiGroup.GET("/node/connection-info", httpSvc.nodeConnectionInfoHandler)
145 readOnlyApiGroup.GET("/node/status", httpSvc.nodeStatusHandler)
146 readOnlyApiGroup.GET("/node/network-graph", httpSvc.nodeNetworkGraphHandler)
147 readOnlyApiGroup.GET("/node/transactions", httpSvc.listOnchainTransactionsHandler)
148 readOnlyApiGroup.GET("/peers", httpSvc.listPeers)
149 readOnlyApiGroup.GET("/wallet/address", httpSvc.onchainAddressHandler)
150 readOnlyApiGroup.GET("/wallet/capabilities", httpSvc.capabilitiesHandler)
151 readOnlyApiGroup.GET("/transactions", httpSvc.listTransactionsHandler)
152 readOnlyApiGroup.GET("/transactions/:paymentHash", httpSvc.lookupTransactionHandler)
153 readOnlyApiGroup.GET("/balances", httpSvc.balancesHandler)
154 readOnlyApiGroup.GET("/mempool", httpSvc.mempoolApiHandler)
155 readOnlyApiGroup.GET("/health", httpSvc.healthHandler)
156 readOnlyApiGroup.GET("/commands", httpSvc.getCustomNodeCommandsHandler)
157 readOnlyApiGroup.GET("/swaps", httpSvc.listSwapsHandler)
158 readOnlyApiGroup.GET("/swaps/:swapId", httpSvc.lookupSwapHandler)
159 readOnlyApiGroup.GET("/swaps/out/info", httpSvc.getSwapOutInfoHandler)
160 readOnlyApiGroup.GET("/swaps/in/info", httpSvc.getSwapInInfoHandler)
161 readOnlyApiGroup.GET("/autoswap", httpSvc.getAutoSwapConfigHandler)
162 readOnlyApiGroup.GET("/forwards", httpSvc.forwardsHandler)
163
164 // Full access API group - requires a token with full permissions
165 fullAccessApiGroup := e.Group("/api")
166 fullAccessApiGroup.Use(echojwt.WithConfig(jwtConfig))
167 fullAccessApiGroup.Use(httpSvc.requireFullAccess)
168
169 fullAccessApiGroup.POST("/event", httpSvc.eventHandler)
170 fullAccessApiGroup.PATCH("/unlock-password", httpSvc.changeUnlockPasswordHandler, unlockRateLimiter)
171 fullAccessApiGroup.PATCH("/auto-unlock", httpSvc.autoUnlockHandler, unlockRateLimiter)
172 fullAccessApiGroup.PATCH("/settings", httpSvc.updateSettingsHandler)
173 fullAccessApiGroup.PATCH("/apps/:pubkey", httpSvc.appsUpdateHandler)
174 fullAccessApiGroup.PATCH("/transactions/:id/labels", httpSvc.setTransactionUserLabelsHandler)
175 fullAccessApiGroup.DELETE("/apps/:pubkey", httpSvc.appsDeleteHandler)
176 fullAccessApiGroup.POST("/transfers", httpSvc.transfersHandler)
177 fullAccessApiGroup.POST("/apps", httpSvc.appsCreateHandler, unlockRateLimiter)
178 fullAccessApiGroup.POST("/lightning-addresses", httpSvc.lightningAddressesCreateHandler)
179 fullAccessApiGroup.DELETE("/lightning-addresses/:appId", httpSvc.lightningAddressesDeleteHandler)
180 fullAccessApiGroup.POST("/mnemonic", httpSvc.mnemonicHandler, unlockRateLimiter)
181 fullAccessApiGroup.PATCH("/backup-reminder", httpSvc.backupReminderHandler)
182 fullAccessApiGroup.POST("/channels", httpSvc.openChannelHandler)
183 fullAccessApiGroup.POST("/channels/rebalance", httpSvc.rebalanceChannelHandler)
184 fullAccessApiGroup.POST("/lsp-orders", httpSvc.newInstantChannelInvoiceHandler)
185 fullAccessApiGroup.POST("/node/migrate-storage", httpSvc.migrateNodeStorageHandler)
186 fullAccessApiGroup.POST("/peers", httpSvc.connectPeerHandler)
187 fullAccessApiGroup.DELETE("/peers/:peerId", httpSvc.disconnectPeerHandler)
188 fullAccessApiGroup.DELETE("/peers/:peerId/channels/:channelId", httpSvc.closeChannelHandler)
189 fullAccessApiGroup.PATCH("/peers/:peerId/channels/:channelId", httpSvc.updateChannelHandler)
190 fullAccessApiGroup.POST("/wallet/new-address", httpSvc.newOnchainAddressHandler)
191 fullAccessApiGroup.POST("/wallet/redeem-onchain-funds", httpSvc.redeemOnchainFundsHandler)
192 fullAccessApiGroup.POST("/wallet/sign-message", httpSvc.signMessageHandler)
193 fullAccessApiGroup.POST("/wallet/sync", httpSvc.walletSyncHandler)
194 fullAccessApiGroup.POST("/payments/:invoice", httpSvc.sendPaymentHandler)
195 fullAccessApiGroup.POST("/invoices", httpSvc.makeInvoiceHandler)
196 fullAccessApiGroup.POST("/offers", httpSvc.makeOfferHandler)
197 fullAccessApiGroup.POST("/reset-router", httpSvc.resetRouterHandler)
198 fullAccessApiGroup.POST("/stop", httpSvc.stopHandler)
199 fullAccessApiGroup.POST("/command", httpSvc.execCustomNodeCommandHandler)
200 fullAccessApiGroup.POST("/swaps/out", httpSvc.initiateSwapOutHandler)
201 fullAccessApiGroup.POST("/swaps/in", httpSvc.initiateSwapInHandler)
202 fullAccessApiGroup.POST("/swaps/refund", httpSvc.refundSwapHandler)
203 fullAccessApiGroup.GET("/swaps/mnemonic", httpSvc.swapMnemonicHandler)
204 fullAccessApiGroup.GET("/log/:type", httpSvc.getLogOutputHandler)
205 fullAccessApiGroup.POST("/autoswap", httpSvc.enableAutoSwapOutHandler, unlockRateLimiter)
206 fullAccessApiGroup.DELETE("/autoswap", httpSvc.disableAutoSwapOutHandler)
207 fullAccessApiGroup.POST("/node/alias", httpSvc.setNodeAliasHandler)
208
209 httpSvc.albyHttpSvc.RegisterSharedRoutes(readOnlyApiGroup, fullAccessApiGroup, e)
210 }
211
212 func (httpSvc *HttpService) infoHandler(c echo.Context) error {
213 responseBody, err := httpSvc.api.GetInfo(c.Request().Context())
214 if err != nil {
215 return c.JSON(http.StatusInternalServerError, ErrorResponse{
216 Message: err.Error(),
217 })
218 }
219
220 authHeader := c.Request().Header.Get("Authorization")
221 if authHeader != "" {
222 parts := strings.Split(authHeader, " ")
223 if parts[0] == "Bearer" {
224 tokenString := parts[1]
225 token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
226 secret, err := httpSvc.cfg.GetJWTSecret()
227 if err != nil {
228 return nil, err
229 }
230
231 return []byte(secret), nil
232 })
233 if err != nil {
234 logger.Logger.WithError(err).Error("failed to parse token")
235 }
236 responseBody.Unlocked = err == nil && token != nil && token.Valid
237 }
238 }
239
240 return c.JSON(http.StatusOK, responseBody)
241 }
242
243 func (httpSvc *HttpService) eventHandler(c echo.Context) error {
244 var sendEventRequest api.SendEventRequest
245 if err := c.Bind(&sendEventRequest); err != nil {
246 return c.JSON(http.StatusBadRequest, ErrorResponse{
247 Message: fmt.Sprintf("Bad request: %s", err.Error()),
248 })
249 }
250
251 httpSvc.api.SendEvent(sendEventRequest.Event, sendEventRequest.Properties)
252
253 return c.NoContent(http.StatusOK)
254 }
255
256 func (httpSvc *HttpService) mnemonicHandler(c echo.Context) error {
257 var mnemonicRequest api.MnemonicRequest
258 if err := c.Bind(&mnemonicRequest); err != nil {
259 return c.JSON(http.StatusBadRequest, ErrorResponse{
260 Message: fmt.Sprintf("Bad request: %s", err.Error()),
261 })
262 }
263
264 responseBody, err := httpSvc.api.GetMnemonic(mnemonicRequest.UnlockPassword)
265
266 if err != nil {
267 return c.JSON(http.StatusInternalServerError, ErrorResponse{
268 Message: err.Error(),
269 })
270 }
271
272 return c.JSON(http.StatusOK, responseBody)
273 }
274
275 func (httpSvc *HttpService) backupReminderHandler(c echo.Context) error {
276 var backupReminderRequest api.BackupReminderRequest
277 if err := c.Bind(&backupReminderRequest); err != nil {
278 return c.JSON(http.StatusBadRequest, ErrorResponse{
279 Message: fmt.Sprintf("Bad request: %s", err.Error()),
280 })
281 }
282
283 err := httpSvc.api.SetNextBackupReminder(&backupReminderRequest)
284 if err != nil {
285 return c.JSON(http.StatusInternalServerError, ErrorResponse{
286 Message: fmt.Sprintf("Failed to store backup reminder: %s", err.Error()),
287 })
288 }
289
290 return c.NoContent(http.StatusNoContent)
291 }
292
293 func (httpSvc *HttpService) startHandler(c echo.Context) error {
294 var startRequest api.StartRequest
295 if err := c.Bind(&startRequest); err != nil {
296 return c.JSON(http.StatusBadRequest, ErrorResponse{
297 Message: fmt.Sprintf("Bad request: %s", err.Error()),
298 })
299 }
300
301 if !httpSvc.cfg.CheckUnlockPassword(startRequest.UnlockPassword) {
302 return c.JSON(http.StatusUnauthorized, ErrorResponse{
303 Message: "Invalid password",
304 })
305 }
306
307 err := httpSvc.cfg.LoadJWTSecret(startRequest.UnlockPassword)
308
309 if err != nil {
310 return c.JSON(http.StatusInternalServerError, ErrorResponse{
311 Message: fmt.Sprintf("Failed to load JWT secret: %s", err.Error()),
312 })
313 }
314
315 token, err := httpSvc.createJWT(nil, "full")
316
317 if err != nil {
318 return c.JSON(http.StatusInternalServerError, ErrorResponse{
319 Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
320 })
321 }
322
323 go httpSvc.api.Start(&startRequest)
324
325 return c.JSON(http.StatusOK, &authTokenResponse{
326 Token: token,
327 })
328 }
329
330 func (httpSvc *HttpService) unlockHandler(c echo.Context) error {
331 var unlockRequest api.UnlockRequest
332 if err := c.Bind(&unlockRequest); err != nil {
333 return c.JSON(http.StatusBadRequest, ErrorResponse{
334 Message: fmt.Sprintf("Bad request: %s", err.Error()),
335 })
336 }
337
338 if !httpSvc.cfg.CheckUnlockPassword(unlockRequest.UnlockPassword) {
339 return c.JSON(http.StatusUnauthorized, ErrorResponse{
340 Message: "Invalid password",
341 })
342 }
343
344 if unlockRequest.Permission == "" {
345 return c.JSON(http.StatusBadRequest, ErrorResponse{
346 Message: "Permission field is required",
347 })
348 }
349
350 if !slices.Contains([]string{"full", "readonly"}, unlockRequest.Permission) {
351 return c.JSON(http.StatusBadRequest, ErrorResponse{
352 Message: "Permission field is unknown",
353 })
354 }
355
356 _, err := httpSvc.api.GetNodeStatus(c.Request().Context())
357 if err != nil {
358 if errors.Is(err, api.ErrLNClientNotStarted) {
359 return c.JSON(http.StatusBadRequest, ErrorResponse{
360 Message: "Node is not running, start it before unlocking.",
361 })
362 }
363
364 return c.JSON(http.StatusInternalServerError, ErrorResponse{
365 Message: err.Error(),
366 })
367 }
368
369 token, err := httpSvc.createJWT(unlockRequest.TokenExpiryDays, unlockRequest.Permission)
370 if err != nil {
371 return c.JSON(http.StatusInternalServerError, ErrorResponse{
372 Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
373 })
374 }
375
376 httpSvc.eventPublisher.Publish(&events.Event{
377 Event: "nwc_unlocked",
378 })
379
380 return c.JSON(http.StatusOK, &authTokenResponse{
381 Token: token,
382 })
383 }
384
385 func (httpSvc *HttpService) requireFullAccess(next echo.HandlerFunc) echo.HandlerFunc {
386 return func(c echo.Context) error {
387 token := c.Get("user").(*jwt.Token)
388 claims := token.Claims.(*jwtCustomClaims)
389
390 // Allow if no permission specified (backward compatibility) or if full access
391 if claims.Permission == "" || claims.Permission == "full" {
392 return next(c)
393 }
394
395 return c.JSON(http.StatusForbidden, ErrorResponse{
396 Message: "This operation requires full access permissions",
397 })
398 }
399 }
400
401 func (httpSvc *HttpService) changeUnlockPasswordHandler(c echo.Context) error {
402 var changeUnlockPasswordRequest api.ChangeUnlockPasswordRequest
403 if err := c.Bind(&changeUnlockPasswordRequest); err != nil {
404 return c.JSON(http.StatusBadRequest, ErrorResponse{
405 Message: fmt.Sprintf("Bad request: %s", err.Error()),
406 })
407 }
408
409 err := httpSvc.api.ChangeUnlockPassword(&changeUnlockPasswordRequest)
410 if err != nil {
411 return c.JSON(http.StatusInternalServerError, ErrorResponse{
412 Message: fmt.Sprintf("Failed to change unlock password: %s", err.Error()),
413 })
414 }
415
416 return c.NoContent(http.StatusNoContent)
417 }
418
419 func (httpSvc *HttpService) updateSettingsHandler(c echo.Context) error {
420 var updateSettingsRequest api.UpdateSettingsRequest
421 if err := c.Bind(&updateSettingsRequest); err != nil {
422 return c.JSON(http.StatusBadRequest, ErrorResponse{
423 Message: fmt.Sprintf("Bad request: %s", err.Error()),
424 })
425 }
426
427 err := httpSvc.api.UpdateSettings(&updateSettingsRequest)
428 if err != nil {
429 return c.JSON(http.StatusInternalServerError, ErrorResponse{
430 Message: fmt.Sprintf("Failed to update settings: %s", err.Error()),
431 })
432 }
433
434 return c.NoContent(http.StatusNoContent)
435 }
436
437 func (httpSvc *HttpService) autoUnlockHandler(c echo.Context) error {
438 var autoUnlockRequest api.AutoUnlockRequest
439 if err := c.Bind(&autoUnlockRequest); err != nil {
440 return c.JSON(http.StatusBadRequest, ErrorResponse{
441 Message: fmt.Sprintf("Bad request: %s", err.Error()),
442 })
443 }
444
445 err := httpSvc.api.SetAutoUnlockPassword(autoUnlockRequest.UnlockPassword)
446 if err != nil {
447 return c.JSON(http.StatusInternalServerError, ErrorResponse{
448 Message: fmt.Sprintf("Failed to set auto unlock password: %s", err.Error()),
449 })
450 }
451
452 return c.NoContent(http.StatusNoContent)
453 }
454
455 func (httpSvc *HttpService) createJWT(tokenExpiryDays *uint64, permission string) (string, error) {
456 if !slices.Contains([]string{"full", "readonly"}, permission) {
457 return "", errors.New("invalid token permission")
458 }
459
460 expiryDays := uint64(30)
461 if tokenExpiryDays != nil {
462 expiryDays = *tokenExpiryDays
463 }
464
465 // Set custom claims
466 claims := &jwtCustomClaims{
467 Permission: permission,
468 RegisteredClaims: jwt.RegisteredClaims{
469 ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * time.Duration(expiryDays))),
470 },
471 }
472
473 // Create token with claims
474 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
475
476 if token == nil {
477 return "", errors.New("failed to create token")
478 }
479
480 secret, err := httpSvc.cfg.GetJWTSecret()
481 if err != nil {
482 return "", err
483 }
484
485 signed, err := token.SignedString([]byte(secret))
486 if err != nil {
487 return "", err
488 }
489 return signed, nil
490 }
491
492 func (httpSvc *HttpService) channelsListHandler(c echo.Context) error {
493 ctx := c.Request().Context()
494
495 channels, err := httpSvc.api.ListChannels(ctx)
496
497 if err != nil {
498 return c.JSON(http.StatusInternalServerError, ErrorResponse{
499 Message: err.Error(),
500 })
501 }
502
503 return c.JSON(http.StatusOK, channels)
504 }
505
506 func (httpSvc *HttpService) channelPeerSuggestionsHandler(c echo.Context) error {
507 ctx := c.Request().Context()
508
509 suggestions, err := httpSvc.api.GetChannelPeerSuggestions(ctx)
510
511 if err != nil {
512 return c.JSON(http.StatusInternalServerError, ErrorResponse{
513 Message: err.Error(),
514 })
515 }
516
517 return c.JSON(http.StatusOK, suggestions)
518 }
519
520 func (httpSvc *HttpService) channelOfferHandler(c echo.Context) error {
521 ctx := c.Request().Context()
522
523 suggestions, err := httpSvc.api.GetLSPChannelOffer(ctx)
524
525 if err != nil {
526 return c.JSON(http.StatusInternalServerError, ErrorResponse{
527 Message: err.Error(),
528 })
529 }
530
531 return c.JSON(http.StatusOK, suggestions)
532 }
533
534 func (httpSvc *HttpService) resetRouterHandler(c echo.Context) error {
535 var resetRouterRequest api.ResetRouterRequest
536 if err := c.Bind(&resetRouterRequest); err != nil {
537 return c.JSON(http.StatusBadRequest, ErrorResponse{
538 Message: fmt.Sprintf("Bad request: %s", err.Error()),
539 })
540 }
541
542 err := httpSvc.api.ResetRouter(resetRouterRequest.Key)
543
544 if err != nil {
545 return c.JSON(http.StatusInternalServerError, ErrorResponse{
546 Message: err.Error(),
547 })
548 }
549
550 return c.NoContent(http.StatusNoContent)
551 }
552
553 func (httpSvc *HttpService) stopHandler(c echo.Context) error {
554
555 err := httpSvc.api.Stop()
556
557 if err != nil {
558 return c.JSON(http.StatusInternalServerError, ErrorResponse{
559 Message: err.Error(),
560 })
561 }
562
563 return c.NoContent(http.StatusNoContent)
564 }
565
566 func (httpSvc *HttpService) nodeConnectionInfoHandler(c echo.Context) error {
567 ctx := c.Request().Context()
568
569 info, err := httpSvc.api.GetNodeConnectionInfo(ctx)
570
571 if err != nil {
572 return c.JSON(http.StatusInternalServerError, ErrorResponse{
573 Message: err.Error(),
574 })
575 }
576
577 return c.JSON(http.StatusOK, info)
578 }
579
580 func (httpSvc *HttpService) nodeStatusHandler(c echo.Context) error {
581 ctx := c.Request().Context()
582
583 info, err := httpSvc.api.GetNodeStatus(ctx)
584
585 if err != nil {
586 return c.JSON(http.StatusInternalServerError, ErrorResponse{
587 Message: err.Error(),
588 })
589 }
590
591 return c.JSON(http.StatusOK, info)
592 }
593
594 func (httpSvc *HttpService) nodeNetworkGraphHandler(c echo.Context) error {
595 ctx := c.Request().Context()
596
597 nodeIds := strings.Split(c.QueryParam("nodeIds"), ",")
598
599 info, err := httpSvc.api.GetNetworkGraph(ctx, nodeIds)
600
601 if err != nil {
602 return c.JSON(http.StatusInternalServerError, ErrorResponse{
603 Message: err.Error(),
604 })
605 }
606
607 return c.JSON(http.StatusOK, info)
608 }
609
610 func (httpSvc *HttpService) migrateNodeStorageHandler(c echo.Context) error {
611 ctx := c.Request().Context()
612 var migrateNodeStorageRequest api.MigrateNodeStorageRequest
613 if err := c.Bind(&migrateNodeStorageRequest); err != nil {
614 return c.JSON(http.StatusBadRequest, ErrorResponse{
615 Message: fmt.Sprintf("Bad request: %s", err.Error()),
616 })
617 }
618
619 err := httpSvc.api.MigrateNodeStorage(ctx, migrateNodeStorageRequest.To)
620
621 if err != nil {
622 return c.JSON(http.StatusInternalServerError, ErrorResponse{
623 Message: err.Error(),
624 })
625 }
626
627 return c.NoContent(http.StatusNoContent)
628 }
629
630 func (httpSvc *HttpService) balancesHandler(c echo.Context) error {
631 ctx := c.Request().Context()
632
633 balances, err := httpSvc.api.GetBalances(ctx)
634
635 if err != nil {
636 return c.JSON(http.StatusInternalServerError, ErrorResponse{
637 Message: err.Error(),
638 })
639 }
640
641 return c.JSON(http.StatusOK, balances)
642 }
643
644 func (httpSvc *HttpService) sendPaymentHandler(c echo.Context) error {
645 ctx := c.Request().Context()
646
647 var payInvoiceRequest api.PayInvoiceRequest
648 if err := c.Bind(&payInvoiceRequest); err != nil {
649 return c.JSON(http.StatusBadRequest, ErrorResponse{
650 Message: fmt.Sprintf("Bad request: %s", err.Error()),
651 })
652 }
653 amountMsat := api.ResolveToMsat(payInvoiceRequest.AmountSat, payInvoiceRequest.AmountMsat, nil, payInvoiceRequest.Amount)
654
655 paymentResponse, err := httpSvc.api.SendPayment(ctx, c.Param("invoice"), amountMsat, payInvoiceRequest.Metadata, payInvoiceRequest.FromAppID)
656
657 if err != nil {
658 return c.JSON(http.StatusInternalServerError, ErrorResponse{
659 Message: err.Error(),
660 })
661 }
662
663 return c.JSON(http.StatusOK, paymentResponse)
664 }
665
666 func (httpSvc *HttpService) makeOfferHandler(c echo.Context) error {
667 ctx := c.Request().Context()
668
669 var makeOfferRequest api.MakeOfferRequest
670 if err := c.Bind(&makeOfferRequest); err != nil {
671 return c.JSON(http.StatusBadRequest, ErrorResponse{
672 Message: fmt.Sprintf("Bad request: %s", err.Error()),
673 })
674 }
675
676 offer, err := httpSvc.api.MakeOffer(ctx, makeOfferRequest.Description)
677
678 if err != nil {
679 return c.JSON(http.StatusInternalServerError, ErrorResponse{
680 Message: fmt.Sprintf("Failed to generate BOLT-12 offer: %s", err.Error()),
681 })
682 }
683
684 return c.JSON(http.StatusOK, offer)
685 }
686
687 func (httpSvc *HttpService) makeInvoiceHandler(c echo.Context) error {
688 var makeInvoiceRequest api.MakeInvoiceRequest
689 if err := c.Bind(&makeInvoiceRequest); err != nil {
690 return c.JSON(http.StatusBadRequest, ErrorResponse{
691 Message: fmt.Sprintf("Bad request: %s", err.Error()),
692 })
693 }
694
695 amountMsat := uint64(0)
696 resolvedAmountMsat := api.ResolveToMsat(makeInvoiceRequest.AmountSat, makeInvoiceRequest.AmountMsat, nil, makeInvoiceRequest.Amount)
697 if resolvedAmountMsat != nil {
698 amountMsat = *resolvedAmountMsat
699 }
700
701 invoice, err := httpSvc.api.CreateInvoice(c.Request().Context(), amountMsat, makeInvoiceRequest.Description, makeInvoiceRequest.ToAppID)
702
703 if err != nil {
704 return c.JSON(http.StatusInternalServerError, ErrorResponse{
705 Message: err.Error(),
706 })
707 }
708
709 return c.JSON(http.StatusOK, invoice)
710 }
711
712 func (httpSvc *HttpService) lookupTransactionHandler(c echo.Context) error {
713 ctx := c.Request().Context()
714
715 transaction, err := httpSvc.api.LookupInvoice(ctx, c.Param("paymentHash"))
716
717 if err != nil {
718 return c.JSON(http.StatusInternalServerError, ErrorResponse{
719 Message: err.Error(),
720 })
721 }
722
723 return c.JSON(http.StatusOK, transaction)
724 }
725
726 func (httpSvc *HttpService) setTransactionUserLabelsHandler(c echo.Context) error {
727 ctx := c.Request().Context()
728
729 var requestData api.SetTransactionUserLabelsRequest
730 if err := c.Bind(&requestData); err != nil {
731 return c.JSON(http.StatusBadRequest, ErrorResponse{
732 Message: fmt.Sprintf("Bad request: %s", err.Error()),
733 })
734 }
735
736 transactionID, err := strconv.ParseUint(c.Param("id"), 10, 64)
737 if err != nil {
738 return c.JSON(http.StatusBadRequest, ErrorResponse{
739 Message: "Invalid transaction ID",
740 })
741 }
742
743 err = httpSvc.api.SetTransactionUserLabels(ctx, uint(transactionID), requestData.Labels)
744 if err != nil {
745 return c.JSON(http.StatusInternalServerError, ErrorResponse{
746 Message: err.Error(),
747 })
748 }
749
750 return c.NoContent(http.StatusNoContent)
751 }
752
753 func (httpSvc *HttpService) listTransactionsHandler(c echo.Context) error {
754 ctx := c.Request().Context()
755
756 limit := uint64(20)
757 offset := uint64(0)
758 var appId *uint
759
760 if limitParam := c.QueryParam("limit"); limitParam != "" {
761 if parsedLimit, err := strconv.ParseUint(limitParam, 10, 64); err == nil {
762 limit = parsedLimit
763 }
764 }
765
766 if offsetParam := c.QueryParam("offset"); offsetParam != "" {
767 if parsedOffset, err := strconv.ParseUint(offsetParam, 10, 64); err == nil {
768 offset = parsedOffset
769 }
770 }
771
772 if appIdParam := c.QueryParam("appId"); appIdParam != "" {
773 if parsedAppId, err := strconv.ParseUint(appIdParam, 10, 64); err == nil {
774 var unsignedAppId = uint(parsedAppId)
775 appId = &unsignedAppId
776 }
777 }
778
779 filters, err := api.ParseListTransactionsFilters(c.QueryParams())
780 if err != nil {
781 return c.JSON(http.StatusBadRequest, ErrorResponse{
782 Message: err.Error(),
783 })
784 }
785
786 transactions, err := httpSvc.api.ListTransactions(ctx, appId, limit, offset, filters)
787
788 if err != nil {
789 return c.JSON(http.StatusInternalServerError, ErrorResponse{
790 Message: err.Error(),
791 })
792 }
793
794 return c.JSON(http.StatusOK, transactions)
795 }
796
797 func (httpSvc *HttpService) listOnchainTransactionsHandler(c echo.Context) error {
798 ctx := c.Request().Context()
799
800 transactions, err := httpSvc.api.ListOnchainTransactions(ctx)
801
802 if err != nil {
803 return c.JSON(http.StatusInternalServerError, ErrorResponse{
804 Message: err.Error(),
805 })
806 }
807
808 return c.JSON(http.StatusOK, transactions)
809 }
810
811 func (httpSvc *HttpService) walletSyncHandler(c echo.Context) error {
812 httpSvc.api.SyncWallet()
813
814 return c.NoContent(http.StatusNoContent)
815 }
816
817 func (httpSvc *HttpService) mempoolApiHandler(c echo.Context) error {
818 endpoint := c.QueryParam("endpoint")
819 if endpoint == "" {
820 return c.JSON(http.StatusBadRequest, ErrorResponse{
821 Message: "Invalid pubkey parameter",
822 })
823 }
824
825 response, err := httpSvc.api.RequestMempoolApi(c.Request().Context(), endpoint)
826 if err != nil {
827 logger.Logger.WithField("endpoint", endpoint).WithError(err).Error("Failed to request mempool API")
828 return c.JSON(http.StatusInternalServerError, ErrorResponse{
829 Message: fmt.Sprintf("Failed to request mempool API: %s", err.Error()),
830 })
831 }
832
833 return c.JSON(http.StatusOK, response)
834 }
835
836 func (httpSvc *HttpService) capabilitiesHandler(c echo.Context) error {
837 response, err := httpSvc.api.GetWalletCapabilities(c.Request().Context())
838 if err != nil {
839 logger.Logger.WithError(err).Error("Failed to request wallet capabilities")
840 return c.JSON(http.StatusInternalServerError, ErrorResponse{
841 Message: fmt.Sprintf("Failed to request wallet capabilities: %s", err.Error()),
842 })
843 }
844
845 return c.JSON(http.StatusOK, response)
846 }
847
848 func (httpSvc *HttpService) listPeers(c echo.Context) error {
849 peers, err := httpSvc.api.ListPeers(c.Request().Context())
850 if err != nil {
851 return c.JSON(http.StatusInternalServerError, ErrorResponse{
852 Message: fmt.Sprintf("Failed to list peers: %s", err.Error()),
853 })
854 }
855
856 return c.JSON(http.StatusOK, peers)
857 }
858
859 func (httpSvc *HttpService) connectPeerHandler(c echo.Context) error {
860 ctx := c.Request().Context()
861
862 var connectPeerRequest api.ConnectPeerRequest
863 if err := c.Bind(&connectPeerRequest); err != nil {
864 return c.JSON(http.StatusBadRequest, ErrorResponse{
865 Message: fmt.Sprintf("Bad request: %s", err.Error()),
866 })
867 }
868
869 err := httpSvc.api.ConnectPeer(ctx, &connectPeerRequest)
870
871 if err != nil {
872 return c.JSON(http.StatusInternalServerError, ErrorResponse{
873 Message: fmt.Sprintf("Failed to connect peer: %s", err.Error()),
874 })
875 }
876
877 return c.NoContent(http.StatusNoContent)
878 }
879
880 func (httpSvc *HttpService) openChannelHandler(c echo.Context) error {
881 ctx := c.Request().Context()
882
883 var openChannelRequest api.OpenChannelRequest
884 if err := c.Bind(&openChannelRequest); err != nil {
885 return c.JSON(http.StatusBadRequest, ErrorResponse{
886 Message: fmt.Sprintf("Bad request: %s", err.Error()),
887 })
888 }
889
890 openChannelResponse, err := httpSvc.api.OpenChannel(ctx, &openChannelRequest)
891
892 if err != nil {
893 return c.JSON(http.StatusInternalServerError, ErrorResponse{
894 Message: fmt.Sprintf("Failed to open channel: %s", err.Error()),
895 })
896 }
897
898 return c.JSON(http.StatusOK, openChannelResponse)
899 }
900
901 func (httpSvc *HttpService) rebalanceChannelHandler(c echo.Context) error {
902 ctx := c.Request().Context()
903
904 var rebalanceChannelRequest api.RebalanceChannelRequest
905 if err := c.Bind(&rebalanceChannelRequest); err != nil {
906 return c.JSON(http.StatusBadRequest, ErrorResponse{
907 Message: fmt.Sprintf("Bad request: %s", err.Error()),
908 })
909 }
910
911 rebalanceChannelResponse, err := httpSvc.api.RebalanceChannel(ctx, &rebalanceChannelRequest)
912
913 if err != nil {
914 return c.JSON(http.StatusInternalServerError, ErrorResponse{
915 Message: fmt.Sprintf("Failed to rebalance channel: %s", err.Error()),
916 })
917 }
918
919 return c.JSON(http.StatusOK, rebalanceChannelResponse)
920 }
921
922 func (httpSvc *HttpService) disconnectPeerHandler(c echo.Context) error {
923 ctx := c.Request().Context()
924
925 err := httpSvc.api.DisconnectPeer(ctx, c.Param("peerId"))
926
927 if err != nil {
928 return c.JSON(http.StatusInternalServerError, ErrorResponse{
929 Message: fmt.Sprintf("Failed to disconnect peer: %s", err.Error()),
930 })
931 }
932
933 return c.NoContent(http.StatusNoContent)
934 }
935
936 func (httpSvc *HttpService) closeChannelHandler(c echo.Context) error {
937 ctx := c.Request().Context()
938
939 closeChannelResponse, err := httpSvc.api.CloseChannel(ctx, c.Param("peerId"), c.Param("channelId"), c.QueryParam("force") == "true")
940
941 if err != nil {
942 return c.JSON(http.StatusInternalServerError, ErrorResponse{
943 Message: fmt.Sprintf("Failed to close channel: %s", err.Error()),
944 })
945 }
946
947 return c.JSON(http.StatusOK, closeChannelResponse)
948 }
949
950 func (httpSvc *HttpService) updateChannelHandler(c echo.Context) error {
951 ctx := c.Request().Context()
952
953 var updateChannelRequest api.UpdateChannelRequest
954 if err := c.Bind(&updateChannelRequest); err != nil {
955 return c.JSON(http.StatusBadRequest, ErrorResponse{
956 Message: fmt.Sprintf("Bad request: %s", err.Error()),
957 })
958 }
959
960 updateChannelRequest.NodeId = c.Param("peerId")
961 updateChannelRequest.ChannelId = c.Param("channelId")
962
963 err := httpSvc.api.UpdateChannel(ctx, &updateChannelRequest)
964
965 if err != nil {
966 return c.JSON(http.StatusInternalServerError, ErrorResponse{
967 Message: fmt.Sprintf("Failed to update channel: %s", err.Error()),
968 })
969 }
970
971 return c.NoContent(http.StatusNoContent)
972 }
973
974 func (httpSvc *HttpService) newInstantChannelInvoiceHandler(c echo.Context) error {
975 ctx := c.Request().Context()
976
977 var newWrappedInvoiceRequest api.LSPOrderRequest
978 if err := c.Bind(&newWrappedInvoiceRequest); err != nil {
979 return c.JSON(http.StatusBadRequest, ErrorResponse{
980 Message: fmt.Sprintf("Bad request: %s", err.Error()),
981 })
982 }
983
984 newLSPOrderResponse, err := httpSvc.api.RequestLSPOrder(ctx, &newWrappedInvoiceRequest)
985
986 if err != nil {
987 return c.JSON(http.StatusInternalServerError, ErrorResponse{
988 Message: fmt.Sprintf("Failed to request new channel order from LSP: %s", err.Error()),
989 })
990 }
991
992 return c.JSON(http.StatusOK, newLSPOrderResponse)
993 }
994
995 func (httpSvc *HttpService) onchainAddressHandler(c echo.Context) error {
996 ctx := c.Request().Context()
997
998 address, err := httpSvc.api.GetUnusedOnchainAddress(ctx)
999
1000 if err != nil {
1001 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1002 Message: fmt.Sprintf("Failed to request new onchain address: %s", err.Error()),
1003 })
1004 }
1005
1006 return c.JSON(http.StatusOK, address)
1007 }
1008
1009 func (httpSvc *HttpService) newOnchainAddressHandler(c echo.Context) error {
1010 ctx := c.Request().Context()
1011
1012 address, err := httpSvc.api.GetNewOnchainAddress(ctx)
1013
1014 if err != nil {
1015 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1016 Message: fmt.Sprintf("Failed to request new onchain address: %s", err.Error()),
1017 })
1018 }
1019
1020 return c.JSON(http.StatusOK, address)
1021 }
1022
1023 func (httpSvc *HttpService) redeemOnchainFundsHandler(c echo.Context) error {
1024 ctx := c.Request().Context()
1025
1026 var redeemOnchainFundsRequest api.RedeemOnchainFundsRequest
1027 if err := c.Bind(&redeemOnchainFundsRequest); err != nil {
1028 return c.JSON(http.StatusBadRequest, ErrorResponse{
1029 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1030 })
1031 }
1032
1033 amountSat := uint64(0)
1034 resolvedAmountSat := api.ResolveToSat(redeemOnchainFundsRequest.AmountSat, nil, redeemOnchainFundsRequest.Amount, nil)
1035 if resolvedAmountSat != nil {
1036 amountSat = *resolvedAmountSat
1037 }
1038
1039 redeemOnchainFundsResponse, err := httpSvc.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, amountSat, redeemOnchainFundsRequest.FeeRate, redeemOnchainFundsRequest.SendAll)
1040
1041 if err != nil {
1042 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1043 Message: fmt.Sprintf("Failed to redeem onchain funds: %s", err.Error()),
1044 })
1045 }
1046
1047 return c.JSON(http.StatusOK, redeemOnchainFundsResponse)
1048 }
1049
1050 func (httpSvc *HttpService) signMessageHandler(c echo.Context) error {
1051 ctx := c.Request().Context()
1052
1053 var signMessageRequest api.SignMessageRequest
1054 if err := c.Bind(&signMessageRequest); err != nil {
1055 return c.JSON(http.StatusBadRequest, ErrorResponse{
1056 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1057 })
1058 }
1059
1060 signMessageResponse, err := httpSvc.api.SignMessage(ctx, signMessageRequest.Message)
1061
1062 if err != nil {
1063 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1064 Message: fmt.Sprintf("Failed to sign message: %s", err.Error()),
1065 })
1066 }
1067 return c.JSON(http.StatusOK, signMessageResponse)
1068 }
1069
1070 func (httpSvc *HttpService) appsListHandler(c echo.Context) error {
1071 limit := uint64(0)
1072 offset := uint64(0)
1073
1074 if limitParam := c.QueryParam("limit"); limitParam != "" {
1075 if parsedLimit, err := strconv.ParseUint(limitParam, 10, 64); err == nil {
1076 limit = parsedLimit
1077 }
1078 }
1079
1080 if offsetParam := c.QueryParam("offset"); offsetParam != "" {
1081 if parsedOffset, err := strconv.ParseUint(offsetParam, 10, 64); err == nil {
1082 offset = parsedOffset
1083 }
1084 }
1085
1086 filtersJSON := c.QueryParam("filters")
1087 var filters api.ListAppsFilters
1088 if filtersJSON != "" {
1089 err := json.Unmarshal([]byte(filtersJSON), &filters)
1090 if err != nil {
1091 logger.Logger.WithError(err).WithFields(logrus.Fields{
1092 "filters": filtersJSON,
1093 }).Error("Failed to deserialize app filters")
1094 return err
1095 }
1096 }
1097
1098 orderBy := c.QueryParam("order_by")
1099
1100 apps, err := httpSvc.api.ListApps(limit, offset, filters, orderBy)
1101
1102 if err != nil {
1103 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1104 Message: err.Error(),
1105 })
1106 }
1107
1108 return c.JSON(http.StatusOK, apps)
1109 }
1110
1111 func (httpSvc *HttpService) appsShowByPubkeyHandler(c echo.Context) error {
1112 dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
1113
1114 if dbApp == nil {
1115 return c.JSON(http.StatusNotFound, ErrorResponse{
1116 Message: "App not found",
1117 })
1118 }
1119
1120 response, err := httpSvc.api.GetApp(dbApp)
1121 if err != nil {
1122 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1123 Message: err.Error(),
1124 })
1125 }
1126
1127 return c.JSON(http.StatusOK, response)
1128 }
1129
1130 func (httpSvc *HttpService) appsShowHandler(c echo.Context) error {
1131 appIdStr := c.Param("id")
1132 if appIdStr == "" {
1133 return c.JSON(http.StatusBadRequest, ErrorResponse{
1134 Message: "App ID is required",
1135 })
1136 }
1137
1138 appId, err := strconv.ParseUint(appIdStr, 10, 64)
1139 if err != nil {
1140 return c.JSON(http.StatusBadRequest, ErrorResponse{
1141 Message: "Invalid App ID",
1142 })
1143 }
1144
1145 dbApp := httpSvc.appsSvc.GetAppById(uint(appId))
1146
1147 if dbApp == nil {
1148 return c.JSON(http.StatusNotFound, ErrorResponse{
1149 Message: "App not found",
1150 })
1151 }
1152
1153 response, err := httpSvc.api.GetApp(dbApp)
1154 if err != nil {
1155 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1156 Message: err.Error(),
1157 })
1158 }
1159
1160 return c.JSON(http.StatusOK, response)
1161 }
1162
1163 func (httpSvc *HttpService) appsUpdateHandler(c echo.Context) error {
1164 var requestData api.UpdateAppRequest
1165 if err := c.Bind(&requestData); err != nil {
1166 return c.JSON(http.StatusBadRequest, ErrorResponse{
1167 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1168 })
1169 }
1170
1171 dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
1172
1173 if dbApp == nil {
1174 return c.JSON(http.StatusNotFound, ErrorResponse{
1175 Message: "App not found",
1176 })
1177 }
1178
1179 err := httpSvc.api.UpdateApp(dbApp, &requestData)
1180
1181 if err != nil {
1182 logger.Logger.WithError(err).Error("Failed to update app")
1183 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1184 Message: fmt.Sprintf("Failed to update app: %v", err),
1185 })
1186 }
1187
1188 return c.NoContent(http.StatusNoContent)
1189 }
1190
1191 func (httpSvc *HttpService) transfersHandler(c echo.Context) error {
1192 var requestData api.TransferRequest
1193 if err := c.Bind(&requestData); err != nil {
1194 return c.JSON(http.StatusBadRequest, ErrorResponse{
1195 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1196 })
1197 }
1198
1199 amountMsat := uint64(0)
1200 resolvedAmountMsat := api.ResolveToMsat(requestData.AmountSat, requestData.AmountMsat, nil, nil)
1201 if resolvedAmountMsat != nil {
1202 amountMsat = *resolvedAmountMsat
1203 }
1204
1205 err := httpSvc.api.Transfer(c.Request().Context(), requestData.FromAppId, requestData.ToAppId, amountMsat, requestData.Description)
1206
1207 if err != nil {
1208 logger.Logger.WithError(err).Error("Failed to transfer funds")
1209 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1210 Message: fmt.Sprintf("Failed to transfer funds: %v", err),
1211 })
1212 }
1213
1214 return c.NoContent(http.StatusNoContent)
1215 }
1216
1217 func (httpSvc *HttpService) appsDeleteHandler(c echo.Context) error {
1218 dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
1219 if dbApp == nil {
1220 return c.JSON(http.StatusNotFound, ErrorResponse{
1221 Message: "App not found",
1222 })
1223 }
1224
1225 if err := httpSvc.api.DeleteApp(dbApp); err != nil {
1226 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1227 Message: "Failed to delete app",
1228 })
1229 }
1230 return c.NoContent(http.StatusNoContent)
1231 }
1232
1233 func (httpSvc *HttpService) appsCreateHandler(c echo.Context) error {
1234 var requestData api.CreateAppRequest
1235 if err := c.Bind(&requestData); err != nil {
1236 return c.JSON(http.StatusBadRequest, ErrorResponse{
1237 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1238 })
1239 }
1240
1241 responseBody, err := httpSvc.api.CreateApp(&requestData)
1242
1243 if err != nil {
1244 logger.Logger.WithField("appName", requestData.Name).WithError(err).Error("Failed to save app")
1245 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1246 Message: fmt.Sprintf("Failed to save app: %v", err),
1247 })
1248 }
1249
1250 return c.JSON(http.StatusOK, responseBody)
1251 }
1252
1253 func (httpSvc *HttpService) lightningAddressesCreateHandler(c echo.Context) error {
1254 var requestData api.CreateLightningAddressRequest
1255 if err := c.Bind(&requestData); err != nil {
1256 return c.JSON(http.StatusBadRequest, ErrorResponse{
1257 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1258 })
1259 }
1260
1261 err := httpSvc.api.CreateLightningAddress(c.Request().Context(), &requestData)
1262
1263 if err != nil {
1264 logger.Logger.WithField("request", requestData).WithError(err).Error("Failed to create lightning address")
1265 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1266 Message: err.Error(),
1267 })
1268 }
1269
1270 return c.NoContent(http.StatusNoContent)
1271 }
1272
1273 func (httpSvc *HttpService) lightningAddressesDeleteHandler(c echo.Context) error {
1274 appIdStr := c.Param("appId")
1275 if appIdStr == "" {
1276 return c.JSON(http.StatusBadRequest, ErrorResponse{
1277 Message: "App ID is required",
1278 })
1279 }
1280
1281 appId, err := strconv.ParseUint(appIdStr, 10, 64)
1282 if err != nil {
1283 return c.JSON(http.StatusBadRequest, ErrorResponse{
1284 Message: "Invalid App ID",
1285 })
1286 }
1287
1288 err = httpSvc.api.DeleteLightningAddress(c.Request().Context(), uint(appId))
1289 if err != nil {
1290 logger.Logger.WithField("appId", appId).WithError(err).Error("Failed to delete lightning address")
1291 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1292 Message: err.Error(),
1293 })
1294 }
1295
1296 return c.NoContent(http.StatusNoContent)
1297 }
1298
1299 func (httpSvc *HttpService) setupHandler(c echo.Context) error {
1300 var setupRequest api.SetupRequest
1301 if err := c.Bind(&setupRequest); err != nil {
1302 return c.JSON(http.StatusBadRequest, ErrorResponse{
1303 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1304 })
1305 }
1306
1307 err := httpSvc.api.Setup(c.Request().Context(), &setupRequest)
1308 if err != nil {
1309 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1310 Message: fmt.Sprintf("Failed to setup node: %s", err.Error()),
1311 })
1312 }
1313
1314 return c.NoContent(http.StatusNoContent)
1315 }
1316
1317 func (httpSvc *HttpService) getLogOutputHandler(c echo.Context) error {
1318 var getLogRequest api.GetLogOutputRequest
1319 if err := c.Bind(&getLogRequest); err != nil {
1320 return c.JSON(http.StatusBadRequest, ErrorResponse{
1321 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1322 })
1323 }
1324
1325 logType := c.Param("type")
1326 if logType != api.LogTypeNode && logType != api.LogTypeApp {
1327 return c.JSON(http.StatusBadRequest, ErrorResponse{
1328 Message: fmt.Sprintf("Invalid log type parameter: '%s'", logType),
1329 })
1330 }
1331
1332 getLogResponse, err := httpSvc.api.GetLogOutput(c.Request().Context(), logType, &getLogRequest)
1333 if err != nil {
1334 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1335 Message: fmt.Sprintf("Failed to get log output: %v", err),
1336 })
1337 }
1338
1339 return c.JSON(http.StatusOK, getLogResponse)
1340 }
1341
1342 func (httpSvc *HttpService) getCustomNodeCommandsHandler(c echo.Context) error {
1343 nodeCommandsResponse, err := httpSvc.api.GetCustomNodeCommands()
1344 if err != nil {
1345 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1346 Message: fmt.Sprintf("Failed to get node commands: %v", err),
1347 })
1348 }
1349
1350 return c.JSON(http.StatusOK, nodeCommandsResponse)
1351 }
1352
1353 func (httpSvc *HttpService) execCustomNodeCommandHandler(c echo.Context) error {
1354 var execCommandRequest api.ExecuteCustomNodeCommandRequest
1355 if err := c.Bind(&execCommandRequest); err != nil {
1356 return c.JSON(http.StatusBadRequest, ErrorResponse{
1357 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1358 })
1359 }
1360
1361 execCommandResponse, err := httpSvc.api.ExecuteCustomNodeCommand(c.Request().Context(), execCommandRequest.Command)
1362 if err != nil {
1363 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1364 Message: fmt.Sprintf("Failed to execute command: %v", err),
1365 })
1366 }
1367
1368 return c.JSON(http.StatusOK, execCommandResponse)
1369 }
1370
1371 func (httpSvc *HttpService) logoutHandler(c echo.Context) error {
1372 redirectUrl := httpSvc.cfg.GetEnv().GetBaseFrontendUrl()
1373 if redirectUrl == "" {
1374 redirectUrl = "/"
1375 }
1376
1377 return c.Redirect(http.StatusFound, redirectUrl)
1378 }
1379
1380 func (httpSvc *HttpService) createBackupHandler(c echo.Context) error {
1381 var backupRequest api.BasicBackupRequest
1382 if err := c.Bind(&backupRequest); err != nil {
1383 return c.JSON(http.StatusBadRequest, ErrorResponse{
1384 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1385 })
1386 }
1387
1388 if !httpSvc.cfg.CheckUnlockPassword(backupRequest.UnlockPassword) {
1389 return c.JSON(http.StatusUnauthorized, ErrorResponse{
1390 Message: "Invalid password",
1391 })
1392 }
1393
1394 var buffer bytes.Buffer
1395 err := httpSvc.api.CreateBackup(backupRequest.UnlockPassword, &buffer)
1396 if err != nil {
1397 return c.String(500, fmt.Sprintf("Failed to create backup: %v", err))
1398 }
1399
1400 c.Response().Header().Set("Content-Type", "application/octet-stream")
1401 c.Response().Header().Set("Content-Disposition", "attachment; filename=albyhub.bkp")
1402 c.Response().WriteHeader(http.StatusOK)
1403 c.Response().Write(buffer.Bytes())
1404 return nil
1405 }
1406
1407 func (httpSvc *HttpService) restoreBackupHandler(c echo.Context) error {
1408 info, err := httpSvc.api.GetInfo(c.Request().Context())
1409 if err != nil {
1410 return err
1411 }
1412 if info.SetupCompleted {
1413 return errors.New("setup already completed")
1414 }
1415
1416 password := c.FormValue("unlockPassword")
1417
1418 fileHeader, err := c.FormFile("backup")
1419 if err != nil {
1420 return c.JSON(http.StatusBadRequest, ErrorResponse{
1421 Message: fmt.Sprintf("Failed to get backup file header: %v", err),
1422 })
1423 }
1424
1425 file, err := fileHeader.Open()
1426 if err != nil {
1427 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1428 Message: fmt.Sprintf("Failed to open backup file: %v", err),
1429 })
1430 }
1431 defer file.Close()
1432
1433 err = httpSvc.api.RestoreBackup(password, file)
1434 if err != nil {
1435 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1436 Message: fmt.Sprintf("Failed to restore backup: %v", err),
1437 })
1438 }
1439
1440 return c.NoContent(http.StatusNoContent)
1441 }
1442
1443 func (httpSvc *HttpService) healthHandler(c echo.Context) error {
1444 healthResponse, err := httpSvc.api.Health(c.Request().Context())
1445 if err != nil {
1446 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1447 Message: fmt.Sprintf("Failed to check node health: %v", err),
1448 })
1449 }
1450
1451 return c.JSON(http.StatusOK, healthResponse)
1452 }
1453
1454 func (httpSvc *HttpService) listSwapsHandler(c echo.Context) error {
1455 swaps, err := httpSvc.api.ListSwaps()
1456
1457 if err != nil {
1458 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1459 Message: err.Error(),
1460 })
1461 }
1462
1463 return c.JSON(http.StatusOK, swaps)
1464 }
1465
1466 func (httpSvc *HttpService) lookupSwapHandler(c echo.Context) error {
1467 swap, err := httpSvc.api.LookupSwap(c.Param("swapId"))
1468 if err != nil {
1469 return c.JSON(http.StatusNotFound, ErrorResponse{
1470 Message: "App not found",
1471 })
1472 }
1473
1474 return c.JSON(http.StatusOK, swap)
1475 }
1476
1477 func (httpSvc *HttpService) getSwapOutInfoHandler(c echo.Context) error {
1478 swapOutFeesResponse, err := httpSvc.api.GetSwapOutInfo()
1479 if err != nil {
1480 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1481 Message: fmt.Sprintf("Failed to get swap out info: %v", err),
1482 })
1483 }
1484
1485 return c.JSON(http.StatusOK, swapOutFeesResponse)
1486 }
1487
1488 func (httpSvc *HttpService) getSwapInInfoHandler(c echo.Context) error {
1489 swapOutFeesResponse, err := httpSvc.api.GetSwapInInfo()
1490 if err != nil {
1491 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1492 Message: fmt.Sprintf("Failed to get swap in info: %v", err),
1493 })
1494 }
1495
1496 return c.JSON(http.StatusOK, swapOutFeesResponse)
1497 }
1498
1499 func (httpSvc *HttpService) initiateSwapOutHandler(c echo.Context) error {
1500 var initiateSwapOutRequest api.InitiateSwapRequest
1501 if err := c.Bind(&initiateSwapOutRequest); err != nil {
1502 return c.JSON(http.StatusBadRequest, ErrorResponse{
1503 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1504 })
1505 }
1506
1507 swapOutResponse, err := httpSvc.api.InitiateSwapOut(c.Request().Context(), &initiateSwapOutRequest)
1508 if err != nil {
1509 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1510 Message: fmt.Sprintf("Failed to initiate swap out: %v", err),
1511 })
1512 }
1513
1514 return c.JSON(http.StatusOK, swapOutResponse)
1515 }
1516
1517 func (httpSvc *HttpService) initiateSwapInHandler(c echo.Context) error {
1518 var initiateSwapInRequest api.InitiateSwapRequest
1519 if err := c.Bind(&initiateSwapInRequest); err != nil {
1520 return c.JSON(http.StatusBadRequest, ErrorResponse{
1521 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1522 })
1523 }
1524
1525 txId, err := httpSvc.api.InitiateSwapIn(c.Request().Context(), &initiateSwapInRequest)
1526 if err != nil {
1527 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1528 Message: fmt.Sprintf("Failed to initiate swap in: %v", err),
1529 })
1530 }
1531
1532 return c.JSON(http.StatusOK, txId)
1533 }
1534
1535 func (httpSvc *HttpService) refundSwapHandler(c echo.Context) error {
1536 var refundSwapInRequest api.RefundSwapRequest
1537 if err := c.Bind(&refundSwapInRequest); err != nil {
1538 return c.JSON(http.StatusBadRequest, ErrorResponse{
1539 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1540 })
1541 }
1542
1543 err := httpSvc.api.RefundSwap(&refundSwapInRequest)
1544 if err != nil {
1545 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1546 Message: err.Error(),
1547 })
1548 }
1549
1550 return c.NoContent(http.StatusNoContent)
1551 }
1552
1553 func (httpSvc *HttpService) swapMnemonicHandler(c echo.Context) error {
1554 mnemonic := httpSvc.api.GetSwapMnemonic()
1555 return c.JSON(http.StatusOK, mnemonic)
1556 }
1557
1558 func (httpSvc *HttpService) getAutoSwapConfigHandler(c echo.Context) error {
1559 getAutoSwapConfigResponse, err := httpSvc.api.GetAutoSwapConfig()
1560 if err != nil {
1561 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1562 Message: fmt.Sprintf("Failed to get swap settings: %v", err),
1563 })
1564 }
1565
1566 return c.JSON(http.StatusOK, getAutoSwapConfigResponse)
1567 }
1568
1569 func (httpSvc *HttpService) enableAutoSwapOutHandler(c echo.Context) error {
1570 var enableAutoSwapRequest api.EnableAutoSwapRequest
1571 if err := c.Bind(&enableAutoSwapRequest); err != nil {
1572 return c.JSON(http.StatusBadRequest, ErrorResponse{
1573 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1574 })
1575 }
1576
1577 err := httpSvc.api.EnableAutoSwapOut(c.Request().Context(), &enableAutoSwapRequest)
1578 if err != nil {
1579 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1580 Message: fmt.Sprintf("Failed to save swap settings: %v", err),
1581 })
1582 }
1583
1584 return c.NoContent(http.StatusNoContent)
1585 }
1586
1587 func (httpSvc *HttpService) disableAutoSwapOutHandler(c echo.Context) error {
1588 err := httpSvc.api.DisableAutoSwap()
1589
1590 if err != nil {
1591 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1592 Message: err.Error(),
1593 })
1594 }
1595
1596 return c.NoContent(http.StatusNoContent)
1597 }
1598
1599 func (httpSvc *HttpService) setNodeAliasHandler(c echo.Context) error {
1600 var setNodeAliasRequest api.SetNodeAliasRequest
1601 if err := c.Bind(&setNodeAliasRequest); err != nil {
1602 return c.JSON(http.StatusBadRequest, ErrorResponse{
1603 Message: fmt.Sprintf("Bad request: %s", err.Error()),
1604 })
1605 }
1606
1607 err := httpSvc.api.SetNodeAlias(setNodeAliasRequest.NodeAlias)
1608 if err != nil {
1609 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1610 Message: fmt.Sprintf("Failed to set node alias: %s", err.Error()),
1611 })
1612 }
1613
1614 return c.NoContent(http.StatusNoContent)
1615 }
1616
1617 func (httpSvc *HttpService) forwardsHandler(c echo.Context) error {
1618 forwards, err := httpSvc.api.GetForwards()
1619 if err != nil {
1620 return c.JSON(http.StatusInternalServerError, ErrorResponse{
1621 Message: fmt.Sprintf("Failed to get forwards: %s", err.Error()),
1622 })
1623 }
1624
1625 return c.JSON(http.StatusOK, forwards)
1626 }
1627