feat(api): setting up
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
BASANGO_API_HOST=localhost
|
||||
BASANGO_API_PORT=3000
|
||||
BASANGO_API_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
|
||||
BASANGO_API_KEY=your_api_key_here
|
||||
@@ -11,10 +11,6 @@
|
||||
# deps
|
||||
node_modules/
|
||||
|
||||
# env
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
+6
-10
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@basango/db": "workspace:*",
|
||||
"@basango/encryption": "workspace:*",
|
||||
"@basango/logger": "workspace:*",
|
||||
"@hono/node-server": "^1.19.6",
|
||||
"@hono/zod-openapi": "^1.1.4",
|
||||
@@ -8,24 +9,19 @@
|
||||
"@trpc/server": "^11.7.1",
|
||||
"ai": "^5.0.89",
|
||||
"camelcase-keys": "^10.0.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns": "catalog:",
|
||||
"hono": "^4.10.4",
|
||||
"hono-rate-limiter": "^0.4.2",
|
||||
"jose": "^6.1.0",
|
||||
"zod": "^4.1.12",
|
||||
"zod": "catalog:",
|
||||
"zod-openapi": "^5.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.17",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"name": "@basango/api",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"start": "node dist/index.js"
|
||||
"dev": "bun run --hot src/index.ts",
|
||||
"start": "bun run src/index.ts",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import path from "node:path";
|
||||
|
||||
import { loadConfig as defineConfig } from "@devscast/config";
|
||||
import { z } from "zod";
|
||||
|
||||
export const PROJECT_DIR = path.resolve(__dirname, "../");
|
||||
|
||||
const ServerConfigurationSchema = z.object({
|
||||
cors: z.object({
|
||||
allowedHeaders: z.array(z.string()).optional(),
|
||||
allowMethods: z.array(z.string()).optional(),
|
||||
exposeHeaders: z.array(z.string()).optional(),
|
||||
maxAge: z.number().int().min(0).optional(),
|
||||
origin: z.array(z.string()).default([]),
|
||||
}),
|
||||
server: z.object({
|
||||
host: z.string().default("localhost"),
|
||||
port: z.number().int().min(1).max(65535).default(4000),
|
||||
version: z.string().default("1.0.0"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { env, config } = defineConfig({
|
||||
env: {
|
||||
knownKeys: [
|
||||
"BASANGO_API_HOST",
|
||||
"BASANGO_API_PORT",
|
||||
"BASANGO_API_ALLOWED_ORIGINS",
|
||||
"BASANGO_API_KEY",
|
||||
],
|
||||
path: path.join(PROJECT_DIR, ".env"),
|
||||
},
|
||||
schema: ServerConfigurationSchema,
|
||||
sources: [
|
||||
path.join(PROJECT_DIR, "config", "server.json"),
|
||||
path.join(PROJECT_DIR, "config", "cors.json"),
|
||||
],
|
||||
});
|
||||
|
||||
export type ServerConfiguration = z.infer<typeof ServerConfigurationSchema>;
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"allowedHeaders": [
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"accept-language",
|
||||
"x-trpc-source",
|
||||
"x-user-locale",
|
||||
"x-user-timezone",
|
||||
"x-user-country"
|
||||
],
|
||||
"allowMethods": ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
|
||||
"exposeHeaders": ["Content-Length"],
|
||||
"maxAge": 86400,
|
||||
"origin": "%env(BASANGO_API_ALLOWED_ORIGINS)%"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"host": "%env(BASANGO_API_HOST)%",
|
||||
"port": "%env(number:BASANGO_API_PORT)%",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
+14
-15
@@ -3,6 +3,7 @@ import { Scalar } from "@scalar/hono-api-reference";
|
||||
import { cors } from "hono/cors";
|
||||
import { secureHeaders } from "hono/secure-headers";
|
||||
|
||||
import { config, env } from "@/config";
|
||||
import { checkHealth } from "@/utils/health";
|
||||
|
||||
const app = new OpenAPIHono();
|
||||
@@ -12,19 +13,11 @@ app.use(secureHeaders());
|
||||
app.use(
|
||||
"*",
|
||||
cors({
|
||||
allowHeaders: [
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"accept-language",
|
||||
"x-trpc-source",
|
||||
"x-user-locale",
|
||||
"x-user-timezone",
|
||||
"x-user-country",
|
||||
],
|
||||
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
|
||||
exposeHeaders: ["Content-Length"],
|
||||
maxAge: 86400,
|
||||
origin: process.env.BASANGO_API_ALLOWED_ORIGINS?.split(",") ?? [],
|
||||
allowHeaders: config.cors.allowedHeaders,
|
||||
allowMethods: config.cors.allowMethods,
|
||||
exposeHeaders: config.cors.exposeHeaders,
|
||||
maxAge: config.cors.maxAge,
|
||||
origin: config.cors.origin,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -54,7 +47,7 @@ app.doc("/openapi", {
|
||||
description: "Basango is a platform that leverages AI to revolutionize news curation.",
|
||||
license: {
|
||||
name: "AGPL-3.0 license",
|
||||
url: "https://github.com/midday-ai/midday/blob/main/LICENSE",
|
||||
url: "https://github.com/bernard-ng/basango/blob/main/LICENSE",
|
||||
},
|
||||
title: "Basango API",
|
||||
version: "0.0.1",
|
||||
@@ -79,7 +72,13 @@ app.openAPIRegistry.registerComponent("securitySchemes", "token", {
|
||||
description: "Default authentication mechanism",
|
||||
scheme: "bearer",
|
||||
type: "http",
|
||||
"x-speakeasy-example": "BASANGO_API_KEY",
|
||||
"x-speakeasy-example": env("BASANGO_API_KEY"),
|
||||
});
|
||||
|
||||
app.get("/", Scalar({ pageTitle: "Basango API", theme: "saturn", url: "/openapi" }));
|
||||
|
||||
export default {
|
||||
fetch: app.fetch,
|
||||
hostname: config.server.host,
|
||||
port: config.server.port,
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@basango/encryption": "workspace:*",
|
||||
"@basango/logger": "workspace:*",
|
||||
"@devscast/config": "^1.0.3",
|
||||
"bullmq": "^4.18.3",
|
||||
"date-fns": "^3.6.0",
|
||||
"date-fns": "catalog:",
|
||||
"ioredis": "^5.8.2",
|
||||
"node-html-parser": "^7.0.1",
|
||||
"tiktoken": "^1.0.22",
|
||||
"turndown": "^7.2.2",
|
||||
"zod": "^4.1.12"
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/turndown": "^5.0.6",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { md5 } from "@basango/encryption";
|
||||
import logger from "@basango/logger";
|
||||
|
||||
import { Article } from "@/schema";
|
||||
@@ -45,11 +46,12 @@ export const persist = async (payload: Article, persistors: Persistor[]): Promis
|
||||
|
||||
const article = {
|
||||
...data,
|
||||
hash: md5(data.link),
|
||||
tokenStatistics: {
|
||||
body: countTokens(payload.body),
|
||||
categories: countTokens(payload.categories.join(",")),
|
||||
excerpt: countTokens(payload.body.substring(0, 200)),
|
||||
title: countTokens(payload.title),
|
||||
body: countTokens(data.body),
|
||||
categories: countTokens(data.categories.join(",")),
|
||||
excerpt: countTokens(data.body.substring(0, 200)),
|
||||
title: countTokens(data.title),
|
||||
},
|
||||
} as Article;
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ export const ArticleTokenStatisticsSchema = z.object({
|
||||
export const ArticleSchema = z.object({
|
||||
body: z.string(),
|
||||
categories: z.array(z.string()).default([]),
|
||||
hash: z.string().optional(),
|
||||
link: z.url(),
|
||||
metadata: ArticleMetadataSchema.optional(),
|
||||
source: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user