feat(db): migration and database setup

This commit is contained in:
2025-11-10 16:57:27 +02:00
parent 594b08a2d1
commit fbca02bec6
31 changed files with 2854 additions and 1928 deletions
+18
View File
@@ -0,0 +1,18 @@
import type { MiddlewareHandler } from "hono";
import { HTTPException } from "hono/http-exception";
import { env } from "@/config";
export const withCrawlerAuth: MiddlewareHandler = async (c, next) => {
const token = c.req.header("Authorization");
if (!token) {
throw new HTTPException(401, { message: "Authorization header required" });
}
if (token !== env("BASANGO_CRAWLER_KEY")) {
throw new HTTPException(403, { message: "Invalid token" });
}
await next();
};
+8
View File
@@ -0,0 +1,8 @@
import { db } from "@basango/db/client";
import type { MiddlewareHandler } from "hono";
export const withDatabase: MiddlewareHandler = async (c, next) => {
c.set("db", db);
await next();
};
+36
View File
@@ -0,0 +1,36 @@
import type { MiddlewareHandler } from "hono";
import type { Scope } from "@/utils/scopes";
export const withRequiredScope = (...requiredScopes: Scope[]): MiddlewareHandler => {
return async (c, next) => {
const scopes = c.get("scopes") as Scope[] | undefined;
if (!scopes) {
return c.json(
{
description: "No scopes found for the current user. Authentication is required.",
error: "Unauthorized",
},
401,
);
}
// Check if user has at least one of the required scopes
const hasRequiredScope = requiredScopes.some((requiredScope) => scopes.includes(requiredScope));
if (!hasRequiredScope) {
return c.json(
{
description: `Insufficient permissions. Required scopes: ${requiredScopes.join(
", ",
)}. Your scopes: ${scopes.join(", ")}`,
error: "Forbidden",
},
403,
);
}
await next();
};
};
+53
View File
@@ -0,0 +1,53 @@
import { createArticle } from "@basango/db/queries";
import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
import { withCrawlerAuth } from "@/rest/middlewares/crawler";
import type { Context } from "@/rest/types";
import { createArticleResponseSchema, createArticleSchema } from "@/schemas/articles";
import { validateResponse } from "@/utils/response";
const app = new OpenAPIHono<Context>();
app.openapi(
createRoute({
description: "Store a new crawled article in the database.",
method: "post",
middleware: [withCrawlerAuth],
operationId: "CreateArticle",
path: "/",
request: {
body: {
content: {
"application/json": {
schema: createArticleSchema,
},
},
},
},
responses: {
201: {
content: {
"application/json": {
schema: createArticleResponseSchema,
},
},
description: "Article created",
},
},
summary: "Create Article",
tags: ["Articles"],
"x-speakeasy-name-override": "create",
}),
async (c) => {
const db = c.get("db");
const body = c.req.valid("json");
const result = await createArticle(db, { ...body });
return c.json(
validateResponse(result, createArticleResponseSchema) as { id: string; sourceId: string },
201,
);
},
);
export const articlesRouter = app;
+9
View File
@@ -0,0 +1,9 @@
import { OpenAPIHono } from "@hono/zod-openapi";
import { articlesRouter } from "@/rest/routers/articles";
const routers = new OpenAPIHono();
routers.route("/articles", articlesRouter);
export { routers };
+7
View File
@@ -0,0 +1,7 @@
import type { Database } from "@basango/db/client";
export type Context = {
Variables: {
db: Database;
};
};