Reinterpreted from the plan's YAGNI 'Scale-out' (OpenSearch/object-storage /multi-tenant deferred at KB scale). Phase 4 = make the Phase 3 async + revision system correct, secure, observable, deployable. - T1 (correctness bug): restoreRevision now rebuilds semantic chunks via new @mcpedia/core reindexChunks(slug) so semantic/hybrid search stay consistent after a restore (previously document_chunks held the NEW body while documents.body held the restored OLD body -> stale search). - T2 (security): /hooks/* git-sync webhooks now require x-webhook-secret header matching WEBHOOK_SECRET (401 otherwise); API fails fast at startup if unset. Added WEBHOOK_SECRET to @mcpedia/config + .env.example; set real secret in .env. - T3 (UX): web doc page shows a History panel (revision no/reason/date/length) with per-revision Restore; app/api/revisions/restore/route.ts calls restoreRevision + revalidatePath (server-component only, no client JS). - T4: listRevisions gains offset paging; summary never includes body. - T5 (ops): deploy/mcpedia-api.service + deploy/mcpedia-worker.service systemd units (Restart=on-failure, EnvironmentFile=.env). Not auto-enabled on host. Verified against live imrnes Redis + Postgres: turbo typecheck+build green; restore-rebuilds-chunks (marker present -> gone after restore); webhook 401/200; web restore route redirects to doc + reverts body; revisions API returns summary (no body); systemd-analyze verify passes.
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
import { fileURLToPath } from "node:url";
|
|
import { dirname, resolve } from "node:path";
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
// packages/config -> repo root (../../..)
|
|
export const REPO_ROOT = resolve(here, "../../..");
|
|
|
|
/**
|
|
* Load .env (repo root) as the authoritative dev config and apply it to
|
|
* process.env. We intentionally OVERRIDE any inherited DATABASE_URL so a stray
|
|
* shell env var can never point the app at the wrong database. .env is
|
|
* gitignored; for deploy, set the real vars in the environment and omit .env.
|
|
*/
|
|
function loadDotEnv() {
|
|
const dotEnv = resolve(REPO_ROOT, ".env");
|
|
if (!existsSync(dotEnv)) return;
|
|
for (const line of readFileSync(dotEnv, "utf8").split("\n")) {
|
|
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
|
|
if (!m) continue;
|
|
const key = m[1];
|
|
let val = m[2];
|
|
if (
|
|
(val.startsWith('"') && val.endsWith('"')) ||
|
|
(val.startsWith("'") && val.endsWith("'"))
|
|
) {
|
|
val = val.slice(1, -1);
|
|
}
|
|
process.env[key] = val;
|
|
}
|
|
}
|
|
loadDotEnv();
|
|
|
|
export const CONTENT_ROOT =
|
|
process.env.CONTENT_ROOT ?? resolve(REPO_ROOT, "content");
|
|
|
|
export const DATABASE_URL = process.env.DATABASE_URL ?? "";
|
|
|
|
export const EMBED_BASE_URL = process.env.EMBED_BASE_URL ?? "";
|
|
export const EMBED_API_KEY = process.env.EMBED_API_KEY ?? "";
|
|
export const EMBED_MODEL = process.env.EMBED_MODEL ?? "";
|
|
|
|
// Phase 3: Redis + BullMQ (shared imrnes Redis, no auth by default).
|
|
export const REDIS_URL = process.env.REDIS_URL ?? "redis://100.121.180.82:6379";
|
|
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD ?? "";
|
|
// BullMQ key prefix to namespace jobs on the shared Redis instance.
|
|
export const QUEUE_PREFIX = process.env.QUEUE_PREFIX ?? "mcpedia";
|
|
|
|
// Phase 4: git-sync webhook shared secret. The API /hooks/* endpoints require
|
|
// this header (x-webhook-secret) to match, so an open port can't trigger reindex.
|
|
export const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET ?? "";
|
|
|
|
if (!DATABASE_URL) {
|
|
// Fail fast with an explicit message instead of a cryptic driver error.
|
|
throw new Error(
|
|
"DATABASE_URL is not set. Copy .env.example to .env and set it (dev uses imrnes Postgres :6432).",
|
|
);
|
|
}
|