Skip to content

Commit

Permalink
feat: add uncle jim internal app (#557)
Browse files Browse the repository at this point in the history
* feat: add uncle jim internal app

* fix: wrong number in instructions

* chore: simplify alby jim internal app copy

* Feat: app metadata (#570)

* chore: wg.Add() before go (#559)

* chore: code cleanup (#558)

* Update README.md

Add command to make the install script executable in the instructions.

* feat: add migration card in wallet screen (#555)

* feat: add migration card in wallet screen

* chore: transfer funds if channel exists

* chore: typo

* chore: improve copy

* chore: add transfer funds button

* fix: remove duplicate icon

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>

* chore: run extra workflows on PR (#563)

* chore: run extra workflows on PR

* fix: pull request workflows

* build(deps): bump github.com/nbd-wtf/go-nostr from 0.34.5 to 0.34.10 (#551)

Bumps [github.com/nbd-wtf/go-nostr](https://github.com/nbd-wtf/go-nostr) from 0.34.5 to 0.34.10.
- [Commits](nbd-wtf/go-nostr@v0.34.5...v0.34.10)

---
updated-dependencies:
- dependency-name: github.com/nbd-wtf/go-nostr
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump gopkg.in/DataDog/dd-trace-go.v1 from 1.66.0 to 1.67.0 (#552)

Bumps gopkg.in/DataDog/dd-trace-go.v1 from 1.66.0 to 1.67.0.

---
updated-dependencies:
- dependency-name: gopkg.in/DataDog/dd-trace-go.v1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix: do not render 0 on withdraw onchain funds page when reserve balance is 0 (#549)

* chore: update fly default kill timeout to ensure app gracefully shuts down (#547)

* feat: app metadata

* fix(wails): parse max length in log output endpoint (#568)

* fix: alby account image and name on transaction list

* fix: vertically center transaction item content

* fix: use slashed zero on wallet balance

* chore: align app image with app name in transaction list

* fix: ensure auth with correct user if refresh token expires (#572)

* fix: do not allow editing name of Alby Account connection

* fix: change "Create Wallet" to "Create Subaccount"

* fix: subaccount copy

* chore: update friends and family app icon

* fix: update app store images (#574)

* fix: update app store images

* chore: update paper scissors hodl and zapplanner icons

* chore: update lume and habla news icons

* chore: resize and compress zapplanner and rps logos

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>

* feat: add new auto channel flow that works with existing channels (#556)

* feat: add new auto channel flow that works with existing channels

* chore: remove "first" from auto channel copy

* feat: new transaction list item design

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Matjaž Lipuš <matjazl@gmail.com>
Co-authored-by: BtcPins <pardus79@gmail.com>
Co-authored-by: Michael Bumann <hello@michaelbumann.com>
Co-authored-by: Adithya Vardhan <imadithyavardhan@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix: copy

* fix: less detailed icon for uncle jim app

* fix: shorten app description

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Matjaž Lipuš <matjazl@gmail.com>
Co-authored-by: BtcPins <pardus79@gmail.com>
Co-authored-by: Michael Bumann <hello@michaelbumann.com>
Co-authored-by: Adithya Vardhan <imadithyavardhan@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: René Aaron <100827540+reneaaron@users.noreply.github.com>
  • Loading branch information
8 people authored Sep 2, 2024
1 parent 956f5d5 commit d257437
Show file tree
Hide file tree
Showing 28 changed files with 675 additions and 148 deletions.
1 change: 1 addition & 0 deletions alby/alby_oauth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.
nil,
scopes,
false,
nil,
)

if err != nil {
Expand Down
25 changes: 24 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ func (api *api) CreateApp(createAppRequest *CreateAppRequest) (*CreateAppRespons
createAppRequest.BudgetRenewal,
expiresAt,
createAppRequest.Scopes,
createAppRequest.Isolated)
createAppRequest.Isolated,
createAppRequest.Metadata)

if err != nil {
return nil, err
Expand Down Expand Up @@ -220,6 +221,16 @@ func (api *api) GetApp(dbApp *db.App) *App {
maxAmount := uint64(paySpecificPermission.MaxAmountSat)
budgetUsage = queries.GetBudgetUsageSat(api.db, &paySpecificPermission)

var metadata Metadata
if dbApp.Metadata != nil {
jsonErr := json.Unmarshal(dbApp.Metadata, &metadata)
if jsonErr != nil {
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to deserialize app metadata")
}
}

response := App{
ID: dbApp.ID,
Name: dbApp.Name,
Expand All @@ -233,6 +244,7 @@ func (api *api) GetApp(dbApp *db.App) *App {
BudgetUsage: budgetUsage,
BudgetRenewal: paySpecificPermission.BudgetRenewal,
Isolated: dbApp.Isolated,
Metadata: metadata,
}

if dbApp.Isolated {
Expand Down Expand Up @@ -300,6 +312,17 @@ func (api *api) ListApps() ([]App, error) {
apiApp.LastEventAt = &lastEvent.CreatedAt
}

var metadata Metadata
if dbApp.Metadata != nil {
jsonErr := json.Unmarshal(dbApp.Metadata, &metadata)
if jsonErr != nil {
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to deserialize app metadata")
}
apiApp.Metadata = metadata
}

apiApps = append(apiApps, apiApp)
}
return apiApps, nil
Expand Down
2 changes: 2 additions & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type App struct {
BudgetRenewal string `json:"budgetRenewal"`
Isolated bool `json:"isolated"`
Balance uint64 `json:"balance"`
Metadata Metadata `json:"metadata,omitempty"`
}

type ListAppsResponse struct {
Expand All @@ -93,6 +94,7 @@ type CreateAppRequest struct {
Scopes []string `json:"scopes"`
ReturnTo string `json:"returnTo"`
Isolated bool `json:"isolated"`
Metadata Metadata `json:"metadata,omitempty"`
}

type StartRequest struct {
Expand Down
22 changes: 15 additions & 7 deletions db/db_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package db

import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"slices"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/logger"
"github.com/nbd-wtf/go-nostr"
"gorm.io/datatypes"
"gorm.io/gorm"
)

Expand All @@ -26,14 +28,10 @@ func NewDBService(db *gorm.DB, eventPublisher events.EventPublisher) *dbService
}
}

func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool) (*App, string, error) {
if isolated && (slices.Contains(scopes, constants.GET_INFO_SCOPE)) {
// cannot return node info because the isolated app is a custodial subaccount
return nil, "", errors.New("Isolated app cannot have get_info scope")
}
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) {
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")
return nil, "", errors.New("isolated app cannot have sign_message scope")
}

var pairingPublicKey string
Expand All @@ -51,7 +49,17 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmountSat uint64,
}
}

app := App{Name: name, NostrPubkey: pairingPublicKey, Isolated: isolated}
var metadataBytes []byte
if metadata != nil {
var err error
metadataBytes, err = json.Marshal(metadata)
if err != nil {
logger.Logger.WithError(err).Error("Failed to serialize metadata")
return nil, "", err
}
}

app := 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
Expand Down
25 changes: 25 additions & 0 deletions db/migrations/202408291715_app_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package migrations

import (
_ "embed"

"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)

var _202408291715_app_metadata = &gormigrate.Migration{
ID: "202408291715_app_metadata",
Migrate: func(tx *gorm.DB) error {

if err := tx.Exec(`
ALTER TABLE apps ADD COLUMN metadata JSON;
`).Error; err != nil {
return err
}

return nil
},
Rollback: func(tx *gorm.DB) error {
return nil
},
}
1 change: 1 addition & 0 deletions db/migrations/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func Migrate(gormDB *gorm.DB) error {
_202407262257_remove_invalid_scopes,
_202408061737_add_boostagrams_and_use_json,
_202408191242_transaction_failure_reason,
_202408291715_app_metadata,
})

return m.Migrate()
Expand Down
3 changes: 2 additions & 1 deletion db/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type App struct {
CreatedAt time.Time
UpdatedAt time.Time
Isolated bool
Metadata datatypes.JSON
}

type AppPermission struct {
Expand Down Expand Up @@ -86,7 +87,7 @@ type Transaction struct {
}

type DBService interface {
CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool) (*App, string, error)
CreateApp(name string, pubkey string, maxAmountSat uint64, budgetRenewal string, expiresAt *time.Time, scopes []string, isolated bool, metadata map[string]interface{}) (*App, string, error)
}

const (
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"@getalby/bitcoin-connect-react": "^3.6.2",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
Expand Down
Binary file added frontend/src/assets/suggested-apps/uncle-jim.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 29 additions & 8 deletions frontend/src/components/AppAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
import { suggestedApps } from "src/components/SuggestedAppData";
import UserAvatar from "src/components/UserAvatar";
import { cn } from "src/lib/utils";
import { App } from "src/types";

type Props = {
appName: string;
app: App;
className?: string;
};

export default function AppAvatar({ appName, className }: Props) {
export default function AppAvatar({ app, className }: Props) {
if (app.name === "getalby.com") {
return <UserAvatar className={className} />;
}
const appStoreApp = app?.metadata?.app_store_app_id
? suggestedApps.find(
(suggestedApp) => suggestedApp.id === app.metadata?.app_store_app_id
)
: undefined;
const image = appStoreApp?.logo;

const gradient =
appName
app.name
.split("")
.map((c) => c.charCodeAt(0))
.reduce((a, b) => a + b, 0) % 10;
return (
<div
className={cn(
"rounded-lg border relative",
`avatar-gradient-${gradient}`,
"rounded-lg border relative overflow-hidden",
!image && `avatar-gradient-${gradient}`,
className
)}
>
<span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white text-xl font-medium capitalize">
{appName.charAt(0)}
</span>
{image && (
<img
src={image}
className={cn("absolute w-full h-full rounded-lg", className)}
/>
)}
{!image && (
<span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white text-xl font-medium capitalize">
{app.name.charAt(0)}
</span>
)}
</div>
);
}
1 change: 0 additions & 1 deletion frontend/src/components/CloseChannelDialogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function CloseChannelDialogContent({ alias, channel }: Props) {

const copy = (text: string) => {
copyToClipboard(text, toast);
toast({ title: "Copied to clipboard." });
};

async function closeChannel() {
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/components/SuggestedAppData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import paperScissorsHodl from "src/assets/suggested-apps/paper-scissors-hodl.png
import primal from "src/assets/suggested-apps/primal.png";
import snort from "src/assets/suggested-apps/snort.png";
import stackernews from "src/assets/suggested-apps/stacker-news.png";
import uncleJim from "src/assets/suggested-apps/uncle-jim.png";
import wavlake from "src/assets/suggested-apps/wavlake.png";
import wherostr from "src/assets/suggested-apps/wherostr.png";
import yakihonne from "src/assets/suggested-apps/yakihonne.png";
Expand All @@ -20,7 +21,8 @@ import zappybird from "src/assets/suggested-apps/zappy-bird.png";

export type SuggestedApp = {
id: string;
webLink: string;
webLink?: string;
internal?: boolean;
playLink?: string;
appleLink?: string;
title: string;
Expand All @@ -29,6 +31,13 @@ export type SuggestedApp = {
};

export const suggestedApps: SuggestedApp[] = [
{
id: "uncle-jim",
title: "Friends & Family",
description: "Subaccounts powered by your Hub",
internal: true,
logo: uncleJim,
},
{
id: "alby-extension",
title: "Alby Extension",
Expand Down
48 changes: 39 additions & 9 deletions frontend/src/components/SuggestedApps.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Globe } from "lucide-react";
import { ExternalLinkIcon, Globe } from "lucide-react";
import { Link } from "react-router-dom";
import ExternalLink from "src/components/ExternalLink";
import { AppleIcon } from "src/components/icons/Apple";
Expand Down Expand Up @@ -36,11 +36,13 @@ function SuggestedAppCard({
</CardContent>
<CardFooter className="flex flex-row justify-between">
<div className="flex flex-row gap-4">
<ExternalLink to={webLink}>
<Button variant="outline" size="icon">
<Globe className="w-4 h-4" />
</Button>
</ExternalLink>
{webLink && (
<ExternalLink to={webLink}>
<Button variant="outline" size="icon">
<Globe className="w-4 h-4" />
</Button>
</ExternalLink>
)}
{appleLink && (
<ExternalLink to={appleLink}>
<Button variant="outline" size="icon">
Expand All @@ -67,13 +69,41 @@ function SuggestedAppCard({
);
}

function InternalAppCard({ id, title, description, logo }: SuggestedApp) {
return (
<Card>
<CardContent className="pt-6">
<div className="flex gap-3 items-center">
<img src={logo} alt="logo" className="inline rounded-lg w-12 h-12" />
<div className="flex-grow">
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</div>
</div>
</CardContent>
<CardFooter className="flex flex-row justify-end">
<Link to={`/internal-apps/${id}`}>
<Button variant="outline">
<ExternalLinkIcon className="w-4 h-4 mr-2" />
Open
</Button>
</Link>
</CardFooter>
</Card>
);
}

export default function SuggestedApps() {
return (
<>
<div className="grid sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{suggestedApps.map((app) => (
<SuggestedAppCard key={app.id} {...app} />
))}
{suggestedApps.map((app) =>
app.internal ? (
<InternalAppCard key={app.id} {...app} />
) : (
<SuggestedAppCard key={app.id} {...app} />
)
)}
</div>
</>
);
Expand Down
Loading

0 comments on commit d257437

Please sign in to comment.