rsocials-online/api/src/index.ts

78 lines
2.4 KiB
TypeScript

import { Hono } from "hono";
import { logger } from "hono/logger";
import { cors } from "hono/cors";
import { initDb } from "./db/schema.js";
import { InstanceStore } from "./db/queries.js";
import { siweAuth } from "./middleware/siwe-auth.js";
import { tokenGate } from "./middleware/token-gate.js";
import { rateLimiter, provisionRateLimiter } from "./middleware/rate-limit.js";
import { setupX402FromEnv } from "./middleware/x402.js";
import { healthRoutes } from "./routes/health.js";
import { authRoutes } from "./routes/auth.js";
import { spacesRoutes } from "./routes/spaces.js";
import { provisionRoutes } from "./routes/provision.js";
import { usageRoutes } from "./routes/usage.js";
import { adminRoutes } from "./routes/admin.js";
const PORT = parseInt(process.env.PORT ?? "3001", 10);
const DB_PATH = process.env.DATABASE_PATH ?? "./data/instances.db";
// Initialize database
const db = initDb(DB_PATH);
const store = new InstanceStore(db);
// Create app
const app = new Hono();
// Global middleware
app.use("*", logger());
app.use("*", cors());
// Public routes
app.route("/health", healthRoutes(store));
app.route("/v1/auth", authRoutes());
// Protected routes (SIWE wallet or API key)
app.use("/v1/spaces/*", siweAuth);
app.use("/v1/spaces/*", tokenGate);
app.use("/v1/spaces/*", rateLimiter);
// x402 payment gate on provisioning (only if X402_PAY_TO is set)
const x402Middleware = setupX402FromEnv();
if (x402Middleware) {
// Apply x402 only to POST (provisioning) and pay endpoint
app.use("/v1/spaces", async (c, next) => {
if (c.req.method === "POST") return x402Middleware(c, next);
return next();
});
}
// Provision rate limiter (stricter — 2/hour)
app.post("/v1/spaces", provisionRateLimiter);
// Space routes
app.route("/v1/spaces", provisionRoutes(store));
app.route("/v1/spaces", spacesRoutes(store));
app.route("/v1/spaces", usageRoutes(store));
// Admin routes (API key only)
app.use("/v1/admin/*", siweAuth); // reuse auth (checks API key first)
app.use("/v1/admin/*", rateLimiter);
app.route("/v1/admin", adminRoutes(store));
// 404 handler
app.notFound((c) => c.json({ error: "Not found" }, 404));
// Error handler
app.onError((err, c) => {
console.error("Unhandled error:", err);
return c.json({ error: "Internal server error" }, 500);
});
console.log(`rSocials API starting on port ${PORT}`);
export default {
port: PORT,
fetch: app.fetch,
};