openreader/src/lib/server/library.ts
Richard R 6d5fb65a72 feat(docs): add configurable rate limiting and external docs
Add environment variables for fine-grained control over TTS rate limiting
and Better Auth behavior. Move documentation to external Docusaurus site
with automated deployment workflows.

- TTS rate limiting can now be enabled/disabled via TTS_ENABLE_RATE_LIMIT
- Customizable daily limits for anonymous/authenticated users and IP backstops
- Better Auth rate limiting can be disabled via DISABLE_AUTH_RATE_LIMIT
- Rename library import env vars to IMPORT_LIBRARY_DIRS/DIR
- Add docs-site with Docusaurus and GitHub Actions workflows
- Update README to reference external documentation
2026-02-10 15:11:37 -07:00

48 lines
1.6 KiB
TypeScript

import path from 'path';
export const DOCSTORE_DIR = path.join(process.cwd(), 'docstore');
export const DEFAULT_LIBRARY_DIR = path.join(DOCSTORE_DIR, 'library');
export function parseLibraryRoots(): string[] {
const raw = process.env.IMPORT_LIBRARY_DIRS ?? process.env.IMPORT_LIBRARY_DIR ?? '';
const roots = raw
.split(/[,:;]/g)
.map((value) => value.trim())
.filter(Boolean);
if (roots.length > 0) {
return roots;
}
return [DEFAULT_LIBRARY_DIR];
}
export function contentTypeForName(name: string): string {
const ext = path.extname(name).toLowerCase();
if (ext === '.pdf') return 'application/pdf';
if (ext === '.epub') return 'application/epub+zip';
if (ext === '.md' || ext === '.mdown' || ext === '.markdown') return 'text/markdown; charset=utf-8';
if (ext === '.html' || ext === '.htm') return 'text/html; charset=utf-8';
return 'text/plain; charset=utf-8';
}
export function decodeLibraryId(id: string): { rootIndex: number; relativePath: string } | null {
try {
const decoded = Buffer.from(id, 'base64url').toString('utf8');
const sepIndex = decoded.indexOf(':');
if (sepIndex <= 0) return null;
const rootIndex = Number(decoded.slice(0, sepIndex));
if (!Number.isInteger(rootIndex) || rootIndex < 0) return null;
const relativePath = decoded.slice(sepIndex + 1);
if (!relativePath) return null;
return { rootIndex, relativePath };
} catch {
return null;
}
}
export function isPathWithinRoot(resolvedRoot: string, resolvedFile: string): boolean {
return resolvedFile === resolvedRoot || resolvedFile.startsWith(resolvedRoot + path.sep);
}