Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,13 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
orderBy = "last_used_at IS NULL, " + orderBy
}

query = query.Order(orderBy + " DESC")
sortBySettled := orderBy == "last_settled_transaction"
if sortBySettled {
query = query.Select("apps.*, MAX(transactions.settled_at) as last_transaction_at").
Joins("LEFT JOIN transactions ON transactions.app_id = apps.id AND transactions.state = ?", constants.TRANSACTION_STATE_SETTLED).
Group("apps.id")
orderBy = "last_transaction_at IS NULL, last_transaction_at DESC, apps.last_used_at"
}

if limit == 0 {
limit = 100
Expand All @@ -555,6 +561,7 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
totalBalance = &totalBalanceMsat
}

query = query.Order(orderBy + " DESC")
query = query.Offset(int(offset)).Limit(int(limit))

err := query.Find(&dbApps).Error
Expand All @@ -581,6 +588,28 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
permissionsMap[perm.AppId] = append(permissionsMap[perm.AppId], perm)
}

// fetch last settled transaction time per app if needed
lastSettledMap := make(map[uint]*time.Time)
if sortBySettled {
type settledResult struct {
AppID uint `gorm:"column:app_id"`
LastTransactionAt *time.Time `gorm:"column:last_transaction_at"`
}
var results []settledResult
err = api.db.Model(&db.Transaction{}).
Select("app_id, MAX(settled_at) as last_transaction_at").
Where("app_id IN ? AND state = ?", appIds, constants.TRANSACTION_STATE_SETTLED).
Group("app_id").
Find(&results).Error
if err != nil {
logger.Logger.WithError(err).Error("Failed to fetch last settled transaction times")
return nil, err
}
for _, r := range results {
lastSettledMap[r.AppID] = r.LastTransactionAt
}
}

apiApps := []App{}
for _, dbApp := range dbApps {
walletPubkey := api.keys.GetNostrPublicKey()
Expand All @@ -600,6 +629,7 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
WalletPubkey: walletPubkey,
UniqueWalletPubkey: uniqueWalletPubkey,
LastUsedAt: dbApp.LastUsedAt,
LastSettledAt: lastSettledMap[dbApp.ID],
}

if dbApp.Isolated {
Expand Down
1 change: 1 addition & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type App struct {
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
LastUsedAt *time.Time `json:"lastUsedAt"`
LastSettledAt *time.Time `json:"lastSettledAt"`
ExpiresAt *time.Time `json:"expiresAt"`
Scopes []string `json:"scopes"`
MaxAmountSat uint64 `json:"maxAmount"`
Expand Down
47 changes: 23 additions & 24 deletions frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import { ALBY_ACCOUNT_APP_NAME } from "src/constants";
import { useApps } from "src/hooks/useApps";

export function LatestUsedAppsWidget() {
const { data: appsData } = useApps(3, undefined, undefined, "last_used_at");
const { data: appsData } = useApps(
3,
undefined,
undefined,
"last_settled_transaction"
);
const apps = appsData?.apps;
const usedApps = apps?.filter((x) => x.lastUsedAt);
const usedApps = apps?.filter((x) => x.lastSettledAt);

if (!usedApps?.length) {
return null;
Expand All @@ -32,28 +37,22 @@ export function LatestUsedAppsWidget() {
</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4">
{usedApps
.sort(
(a, b) =>
new Date(b.lastUsedAt ?? 0).getTime() -
new Date(a.lastUsedAt ?? 0).getTime()
)
.map((app) => (
<Link key={app.id} to={`/apps/${app.id}`}>
<div className="flex items-center w-full gap-4">
<AppAvatar app={app} className="w-14 h-14 rounded-lg" />
<p className="text-sm font-medium flex-1 truncate">
{app.name === ALBY_ACCOUNT_APP_NAME
? "Alby Account"
: app.name}
</p>
<p className="text-xs text-muted-foreground">
{app.lastUsedAt ? dayjs(app.lastUsedAt).fromNow() : "never"}
</p>
<ChevronRightIcon className="text-muted-foreground size-8" />
</div>
</Link>
))}
{usedApps.map((app) => (
<Link key={app.id} to={`/apps/${app.id}`}>
<div className="flex items-center w-full gap-4">
<AppAvatar app={app} className="w-14 h-14 rounded-lg" />
<p className="text-sm font-medium flex-1 truncate">
{app.name === ALBY_ACCOUNT_APP_NAME ? "Alby Account" : app.name}
</p>
<p className="text-xs text-muted-foreground">
{app.lastSettledAt
? dayjs(app.lastSettledAt).fromNow()
: "never"}
</p>
<ChevronRightIcon className="text-muted-foreground size-8" />
</div>
</Link>
))}
</CardContent>
</Card>
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function useApps(
unused?: boolean;
subWallets?: boolean;
},
orderBy?: "last_used_at" | "created_at",
orderBy?: "last_used_at" | "created_at" | "last_settled_transaction",
isEnabled = true
) {
const offset = (page - 1) * limit;
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export interface App {
createdAt: string;
updatedAt: string;
lastUsedAt?: string;
lastSettledAt?: string;
expiresAt?: string;
isolated: boolean;
balance: number;
Expand Down
2 changes: 1 addition & 1 deletion nip47/event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, pool nostrmodels.Simpl
err = svc.db.Model(&app).Update("last_used_at", &now).Error
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"it": app.ID,
"app_id": app.ID,
}).WithError(err).Error("Failed to update app last used time")
}

Expand Down
Loading