Skip to content

Commit

Permalink
Merge pull request #11 from getAlby/task-metadata
Browse files Browse the repository at this point in the history
feat: store and send metadata during invoice creation
  • Loading branch information
im-adithya authored Dec 10, 2024
2 parents 10a2416 + eb97e47 commit 83cecef
Show file tree
Hide file tree
Showing 11 changed files with 4,515 additions and 4,465 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A minimal Lightning address server powered by [NWC](https://nwc.dev)

```json
{
"connectionSecret": "nostr+walletconnect://..."
"connectionSecret": "nostr+walletconnect://..."
}
```

Expand All @@ -26,7 +26,6 @@ A minimal Lightning address server powered by [NWC](https://nwc.dev)

- [Install Deno](https://docs.deno.com/runtime/manual/getting_started/installation/)
- Copy `.env.example` to `.env`
- Setup DB: `deno task db:migrate`
- Run in dev mode: `deno task dev`

### Creating a new migration
Expand Down
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"imports": {
"@nostr/tools": "jsr:@nostr/tools@^2.10.3",
"hono": "jsr:@hono/hono@^4.5.5",
"drizzle-orm": "npm:[email protected]",
"drizzle-kit": "npm:[email protected]",
Expand Down
8,592 changes: 4,162 additions & 4,430 deletions deno.lock

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions drizzle/0001_white_prism.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CREATE TABLE IF NOT EXISTS "invoices" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"amount" bigint NOT NULL,
"description" text,
"payment_request" text NOT NULL,
"payment_hash" text NOT NULL,
"preimage" text,
"metadata" jsonb,
"settled_at" timestamp,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "invoices_payment_request_unique" UNIQUE("payment_request"),
CONSTRAINT "invoices_payment_hash_unique" UNIQUE("payment_hash")
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "invoices" ADD CONSTRAINT "invoices_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "user_id_idx" ON "invoices" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "user_payment_hash_idx" ON "invoices" USING btree ("user_id","payment_hash");
196 changes: 196 additions & 0 deletions drizzle/meta/0001_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
{
"id": "52607bd8-7a1a-4a34-ad27-91b78733e85c",
"prevId": "e292703e-d08b-4f8b-a9eb-3937fe872be7",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.invoices": {
"name": "invoices",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"payment_request": {
"name": "payment_request",
"type": "text",
"primaryKey": false,
"notNull": true
},
"payment_hash": {
"name": "payment_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"preimage": {
"name": "preimage",
"type": "text",
"primaryKey": false,
"notNull": false
},
"metadata": {
"name": "metadata",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"settled_at": {
"name": "settled_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"user_id_idx": {
"name": "user_id_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"user_payment_hash_idx": {
"name": "user_payment_hash_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "payment_hash",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"invoices_user_id_users_id_fk": {
"name": "invoices_user_id_users_id_fk",
"tableFrom": "invoices",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"invoices_payment_request_unique": {
"name": "invoices_payment_request_unique",
"nullsNotDistinct": false,
"columns": [
"payment_request"
]
},
"invoices_payment_hash_unique": {
"name": "invoices_payment_hash_unique",
"nullsNotDistinct": false,
"columns": [
"payment_hash"
]
}
}
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"connection_secret": {
"name": "connection_secret",
"type": "text",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_username_unique": {
"name": "users_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
7 changes: 7 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
"when": 1728309815221,
"tag": "0000_greedy_phalanx",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1733813329314,
"tag": "0001_white_prism",
"breakpoints": true
}
]
}
65 changes: 57 additions & 8 deletions src/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { migrate } from "drizzle-orm/postgres-js/migrator";
import { nwc } from "npm:@getalby/sdk";
import postgres from "postgres";

import { eq } from "drizzle-orm";
import { and, eq } from "drizzle-orm";
import { DATABASE_URL } from "../constants.ts";
import { decrypt, encrypt } from "./aesgcm.ts";
import * as schema from "./schema.ts";
import { users } from "./schema.ts";
import { invoices, users } from "./schema.ts";

export async function runMigration() {
const migrationClient = postgres(DATABASE_URL, { max: 1 });
Expand All @@ -29,7 +29,7 @@ export class DB {
async createUser(
connectionSecret: string,
username?: string
): Promise<{ username: string }> {
) {
const parsed = nwc.NWCClient.parseWalletConnectUrl(connectionSecret);
if (!parsed.secret) {
throw new Error("no secret found in connection secret");
Expand All @@ -40,26 +40,75 @@ export class DB {

const encryptedConnectionSecret = await encrypt(connectionSecret);

await this._db.insert(users).values({
const [newUser] = await this._db.insert(users).values({
encryptedConnectionSecret,
username,
});
}).returning({ id: users.id, username: users.username });

return { username };
return newUser;
}

getAllUsers() {
return this._db.query.users.findMany();
}

async findWalletConnectionSecret(username: string) {
async findUser(username: string) {
const result = await this._db.query.users.findFirst({
where: eq(users.username, username),
});
if (!result) {
throw new Error("user not found");
}
const connectionSecret = await decrypt(result.encryptedConnectionSecret);
return connectionSecret;
return {
id: result.id,
connectionSecret
};
}

async createInvoice(
userId: number,
transaction: nwc.Nip47Transaction
) {
await this._db.insert(invoices).values({
userId,
amount: transaction.amount,
description: transaction.description,
paymentRequest: transaction.invoice,
paymentHash: transaction.payment_hash,
metadata: transaction.metadata,
});

return;
}

async findInvoice(paymentHash: string) {
const result = await this._db.query.invoices.findFirst({
where: eq(invoices.paymentHash, paymentHash),
});
if (!result) {
throw new Error("invoice not found");
}
return result;
}

async markInvoiceSettled(
userId: number,
transaction: nwc.Nip47Transaction
): Promise<void> {
await this._db
.update(invoices)
.set({
preimage: transaction.preimage,
settledAt: new Date(transaction.settled_at * 1000),
})
.where(
and(
eq(invoices.userId, userId),
eq(invoices.paymentHash, transaction.payment_hash)
)
)

return;
}
}
20 changes: 19 additions & 1 deletion src/db/schema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
import { bigint, index, integer, jsonb, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
id: serial("id").primaryKey(),
encryptedConnectionSecret: text("connection_secret").notNull(),
username: text("username").unique().notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
});

export const invoices = pgTable("invoices", {
id: serial("id").primaryKey(),
userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }).notNull(),
amount: bigint("amount", { mode: "number" }).notNull(),
description: text("description"),
paymentRequest: text("payment_request").unique().notNull(),
paymentHash: text("payment_hash").unique().notNull(),
preimage: text("preimage"),
metadata: jsonb("metadata"),
settledAt: timestamp("settled_at"),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (table) => {
return {
userIdIdx: index("user_id_idx").on(table.userId),
userPaymentHashIdx: index("user_payment_hash_idx").on(table.userId, table.paymentHash),
};
});
Loading

0 comments on commit 83cecef

Please sign in to comment.