NobGit
public anord read

Ledger

Why work hard when you can work easier?

Languages

Repository composition by tracked source files.

TypeScript
TypeScript 86% CSS 10% SQL 3% Shell 1% HTML 0%
Create file Wiki Documentation
Clone
https://nobgit.com/orgs/anord/ledger.git
ssh://[email protected]:2222/orgs/anord/ledger.git
index.ts
import "dotenv/config";
import crypto from "node:crypto";
import { Worker } from "bullmq";
import Redis from "ioredis";
import { Pool } from "pg";

const connection = new Redis(process.env.REDIS_URL ?? "redis://localhost:6379", {
  maxRetriesPerRequest: null
});
const pool = new Pool({
  connectionString: process.env.DATABASE_URL ?? "postgresql://ledger:ledger@localhost:5432/ledger"
});

function buildSignedWebhookBody(secret: string, timestamp: string, body: string) {
  return crypto.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
}

async function deliverWebhook(deliveryId: string) {
  const delivery = await pool.query(
    `
      SELECT
        wd.id,
        wd.event_name,
        wd.payload,
        wd.attempt_count,
        w.target_url,
        w.signing_secret,
        w.is_active
      FROM webhook_deliveries wd
      JOIN webhooks w ON w.id = wd.webhook_id
      WHERE wd.id = $1
    `,
    [deliveryId]
  );

  if (!delivery.rowCount) {
    return { ignored: true };
  }

  const row = delivery.rows[0];
  if (!row.is_active) {
    return { skipped: true };
  }

  const payload = JSON.stringify(row.payload);
  const timestamp = new Date().toISOString();
  const signature = buildSignedWebhookBody(row.signing_secret, timestamp, payload);

  try {
    const response = await fetch(row.target_url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Ledger-Event": row.event_name,
        "X-Ledger-Timestamp": timestamp,
        "X-Ledger-Signature": signature
      },
      body: payload
    });

    const responseBody = await response.text();
    await pool.query(
      `
        UPDATE webhook_deliveries
        SET response_status = $2,
            response_body = $3,
            success = $4,
            delivered_at = now(),
            error_message = NULL,
            attempt_count = $5
        WHERE id = $1
      `,
      [deliveryId, response.status, responseBody.slice(0, 5000), response.ok, Number(row.attempt_count ?? 0) + 1]
    );

    if (!response.ok) {
      throw new Error(`Webhook returned ${response.status}`);
    }

    return { delivered: true };
  } catch (error) {
    await pool.query(
      `
        UPDATE webhook_deliveries
        SET success = false,
            error_message = $2,
            delivered_at = now(),
            attempt_count = $3
        WHERE id = $1
      `,
      [deliveryId, error instanceof Error ? error.message : "Delivery failed", Number(row.attempt_count ?? 0) + 1]
    );
    throw error;
  }
}

const worker = new Worker(
  "ledger-jobs",
  async (job) => {
    if (job.name === "webhook.deliver") {
      console.log(`processing webhook delivery ${job.id}`);
      return deliverWebhook(String(job.data.deliveryId));
    }

    if (job.name === "analytics.rollup") {
      console.log(`processing analytics rollup ${job.id}`);
      return { rolledUp: true };
    }

    return { ignored: true };
  },
  { connection }
);

worker.on("completed", (job) => {
  console.log(`job completed: ${job.name}:${job.id}`);
});

worker.on("failed", (job, error) => {
  console.error(`job failed: ${job?.name}:${job?.id}`, error);
});

console.log("Ledger worker started");