Skip to content

Commit

Permalink
fix: run db migration at startup
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Oct 7, 2024
1 parent 598c0f3 commit dd8e540
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 121 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/fly-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/

name: Fly Deploy
on:
push:
branches:
- main
jobs:
deploy:
name: Deploy app
runs-on: ubuntu-latest
concurrency: deploy-group # optional: ensure only one action runs at a time
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
1 change: 0 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"cache": "deno cache ./src/main.ts ./src/db/schema.ts npm:@libsql/client",
"cache:reload": "deno cache --reload ./src/main.ts ./src/db/schema.ts",
"db:generate": "deno run -A --node-modules-dir npm:drizzle-kit generate",
"db:migrate": "deno run -A --node-modules-dir npm:drizzle-kit migrate",
"dev": "deno run --env --allow-net --allow-env --unstable-ffi --allow-ffi --allow-read --allow-write --watch src/main.ts",
"start": "deno run --allow-net --allow-env --allow-read=favicon.ico src/main.ts"
},
Expand Down
26 changes: 15 additions & 11 deletions fly.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
app = 'alby-lite'
# fly.toml app configuration file generated for albylite on 2024-09-27T11:16:12+07:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'albylite'
primary_region = 'lax'

[build]
strategy = "rolling"
max_unavailable = 1

[env]
BASE_URL = 'https://albylite.fly.dev'
PORT = '8080'
BASE_URL = "https://albylite.fly.dev"

[http_service]
internal_port = 8080
Expand All @@ -17,13 +20,14 @@ primary_region = 'lax'
min_machines_running = 0
processes = ['app']

[[http_service.checks]]
grace_period = "45s"
interval = "60s"
method = "GET"
timeout = "5s"
path = "/ping"
[[http_service.checks]]
interval = '1m0s'
timeout = '5s'
grace_period = '45s'
method = 'GET'
path = '/ping'

[[vm]]
memory = '1024mb'
memory = '1gb'
cpu_kind = 'shared'
cpus = 1
76 changes: 45 additions & 31 deletions src/db/db.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { drizzle } from "drizzle-orm/postgres-js";
import { drizzle, PostgresJsDatabase } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import { nwc } from "npm:@getalby/sdk";
import postgres from "postgres";

Expand All @@ -7,41 +8,54 @@ import { DATABASE_URL } from "../constants.ts";
import * as schema from "./schema.ts";
import { users } from "./schema.ts";

const queryClient = postgres(DATABASE_URL);
const db = drizzle(queryClient, {
schema,
});

export async function 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");
export async function runMigration() {
const migrationClient = postgres(DATABASE_URL, { max: 1 });
await migrate(drizzle(migrationClient), {
migrationsFolder: "./drizzle",
});
}

export class DB {
private _db: PostgresJsDatabase<typeof schema>;

constructor() {
const queryClient = postgres(DATABASE_URL);
this._db = drizzle(queryClient, {
schema,
});
}

// TODO: use haikunator
username = username || Math.floor(Math.random() * 100000000000).toString();
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");
}

await db.insert(users).values({
connectionSecret,
username,
});
// TODO: use haikunator
username = username || Math.floor(Math.random() * 100000000000).toString();

return { username };
}
await this._db.insert(users).values({
connectionSecret,
username,
});

export function getAllUsers() {
return db.query.users.findMany();
}
return { username };
}

export async function findWalletConnection(username: string) {
const result = await db.query.users.findFirst({
where: eq(users.username, username),
});
if (!result) {
throw new Error("user not found");
getAllUsers() {
return this._db.query.users.findMany();
}

async findWalletConnection(username: string) {
const result = await this._db.query.users.findFirst({
where: eq(users.username, username),
});
if (!result) {
throw new Error("user not found");
}
return result?.connectionSecret;
}
return result?.connectionSecret;
}
73 changes: 38 additions & 35 deletions src/lnurlp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,56 @@ import { Hono } from "hono";
import { nwc } from "npm:@getalby/sdk";
import { logger } from "../src/logger.ts";
import { BASE_URL, DOMAIN } from "./constants.ts";
import { findWalletConnection } from "./db/db.ts";
import { DB } from "./db/db.ts";
import "./nwc/nwcPool.ts";

export const lnurlApp = new Hono();
export function createLnurlApp(db: DB) {
const hono = new Hono();

lnurlApp.get("/:username", (c) => {
const username = c.req.param("username");
hono.get("/:username", (c) => {
const username = c.req.param("username");

logger.debug("LNURLp request", { username });
logger.debug("LNURLp request", { username });

// TODO: zapper support
// TODO: zapper support

return c.json({
status: "OK",
tag: "payRequest",
commentAllowed: 255,
callback: `${BASE_URL}/.well-known/lnurlp/${username}/callback`,
minSendable: 1000,
maxSendable: 10000000000,
metadata: `[["text/identifier","${username}@${DOMAIN}"],["text/plain","Sats for ${username}"]]`,
return c.json({
status: "OK",
tag: "payRequest",
commentAllowed: 255,
callback: `${BASE_URL}/.well-known/lnurlp/${username}/callback`,
minSendable: 1000,
maxSendable: 10000000000,
metadata: `[["text/identifier","${username}@${DOMAIN}"],["text/plain","Sats for ${username}"]]`,
});
});
});

lnurlApp.get("/:username/callback", async (c) => {
const username = c.req.param("username");
const amount = c.req.query("amount");
const comment = c.req.query("comment") || "";
logger.debug("LNURLp callback", { username, amount, comment });
hono.get("/:username/callback", async (c) => {
const username = c.req.param("username");
const amount = c.req.query("amount");
const comment = c.req.query("comment") || "";
logger.debug("LNURLp callback", { username, amount, comment });

// TODO: store data (e.g. for zaps)
// TODO: store data (e.g. for zaps)

if (!amount) {
return c.text('No amount provided', 404);
}
if (!amount) {
return c.text("No amount provided", 404);
}

const connectionSecret = await findWalletConnection(username);
const connectionSecret = await db.findWalletConnection(username);

const nwcClient = new nwc.NWCClient({
nostrWalletConnectUrl: connectionSecret,
});
const nwcClient = new nwc.NWCClient({
nostrWalletConnectUrl: connectionSecret,
});

const transaction = await nwcClient.makeInvoice({
amount: +amount,
description: comment,
});
const transaction = await nwcClient.makeInvoice({
amount: +amount,
description: comment,
});

return c.json({
pr: transaction.invoice,
return c.json({
pr: transaction.invoice,
});
});
});
return hono;
}
34 changes: 21 additions & 13 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
import { Hono } from "hono";
import { serveStatic } from "hono/deno";
import { secureHeaders } from "hono/secure-headers";
import { loggerMiddleware, LOG_LEVEL, logger } from "./logger.ts";
import { sentry } from "npm:@hono/sentry";
import { lnurlApp } from "./lnurlp.ts";
import { PORT } from "./constants.ts";
import { usersApp } from "./users.ts";
import { DB, runMigration } from "./db/db.ts";
import { createLnurlApp } from "./lnurlp.ts";
import { LOG_LEVEL, logger, loggerMiddleware } from "./logger.ts";
import { NWCPool } from "./nwc/nwcPool.ts";
import { createUsersApp } from "./users.ts";

await runMigration();

const db = new DB();
const nwcPool = new NWCPool(db);
await nwcPool.init();

const SENTRY_DSN = Deno.env.get("SENTRY_DSN");

const baseApp = new Hono();
const hono = new Hono();

baseApp.use(loggerMiddleware());
baseApp.use(secureHeaders());
hono.use(loggerMiddleware());
hono.use(secureHeaders());
if (SENTRY_DSN) {
baseApp.use("*", sentry({ dsn: SENTRY_DSN }));
hono.use("*", sentry({ dsn: SENTRY_DSN }));
}

baseApp.route("/.well-known/lnurlp", lnurlApp);
baseApp.route("/users", usersApp);
hono.route("/.well-known/lnurlp", createLnurlApp(db));
hono.route("/users", createUsersApp(db, nwcPool));

baseApp.get("/ping", (c) => {
hono.get("/ping", (c) => {
return c.body("OK");
});

baseApp.use("/favicon.ico", serveStatic({ path: "./favicon.ico" }));
hono.use("/favicon.ico", serveStatic({ path: "./favicon.ico" }));

baseApp.get("/robots.txt", (c) => {
hono.get("/robots.txt", (c) => {
return c.body("User-agent: *\nDisallow: /", 200);
});

Deno.serve({ port: PORT }, baseApp.fetch);
Deno.serve({ port: PORT }, hono.fetch);

logger.info("Server started", { port: PORT, log_level: LOG_LEVEL });
22 changes: 11 additions & 11 deletions src/nwc/nwcPool.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { nwc } from "npm:@getalby/sdk";
import { getAllUsers } from "../db/db.ts";
import { DB } from "../db/db.ts";
import { logger } from "../logger.ts";

export class NWCPool {
readonly _nwcs: nwc.NWCClient[];
constructor() {
private readonly _db: DB;
private readonly _nwcs: nwc.NWCClient[];
constructor(db: DB) {
this._nwcs = [];
this._db = db;
}

(async () => {
const users = await getAllUsers();
for (const user of users) {
this.addNWCClient(user.connectionSecret, user.username);
}
})();
async init() {
const users = await this._db.getAllUsers();
for (const user of users) {
this.addNWCClient(user.connectionSecret, user.username);
}
}

addNWCClient(connectionSecret: string, username: string) {
Expand All @@ -29,5 +31,3 @@ export class NWCPool {
);
}
}

export const nwcPool = new NWCPool();
Loading

0 comments on commit dd8e540

Please sign in to comment.