{"id":38543431,"url":"https://github.com/sphildreth/narravo","last_synced_at":"2026-01-18T11:22:07.606Z","repository":{"id":315412960,"uuid":"1059395715","full_name":"sphildreth/narravo","owner":"sphildreth","description":"narravo - A blog engine built for devs, loved by readers.","archived":false,"fork":false,"pushed_at":"2025-10-20T22:15:15.000Z","size":2678,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-22T08:50:05.510Z","etag":null,"topics":["blog-engine","cms","comments-system","postgresql","self-hosted","wordpress-import","wxr"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sphildreth.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":"docs/AGENTS.md","dco":null,"cla":null},"funding":{"ko_fi":"sphildreth"}},"created_at":"2025-09-18T11:44:00.000Z","updated_at":"2025-10-16T22:19:41.000Z","dependencies_parsed_at":"2025-09-18T13:42:14.752Z","dependency_job_id":"546e8cca-f051-4c22-8827-7d9f6ed882fb","html_url":"https://github.com/sphildreth/narravo","commit_stats":null,"previous_names":["sphildreth/narravo"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/sphildreth/narravo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphildreth%2Fnarravo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphildreth%2Fnarravo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphildreth%2Fnarravo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphildreth%2Fnarravo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sphildreth","download_url":"https://codeload.github.com/sphildreth/narravo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphildreth%2Fnarravo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28503242,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T06:57:29.758Z","status":"ssl_error","status_checked_at":"2026-01-17T06:56:03.931Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blog-engine","cms","comments-system","postgresql","self-hosted","wordpress-import","wxr"],"created_at":"2026-01-17T07:13:49.753Z","updated_at":"2026-01-17T07:13:50.163Z","avatar_url":"https://github.com/sphildreth.png","language":"TypeScript","funding_links":["https://ko-fi.com/sphildreth"],"categories":[],"sub_categories":[],"readme":"\u003c!-- SPDX-License-Identifier: Apache-2.0 --\u003e\n![Narravo Logo](./public/images/logo-60x57.png) Narravo: A Modern Next.js Blog Engine\n\n[![CI](https://github.com/sphildreth/narravo/actions/workflows/ci.yml/badge.svg)](https://github.com/sphildreth/narravo/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?logo=typescript\u0026logoColor=white)](https://www.typescriptlang.org/)\n[![React](https://img.shields.io/badge/React-19.1-61dafb?logo=react\u0026logoColor=white)](https://react.dev/)\n[![Next.js](https://img.shields.io/badge/Next.js-15-black?logo=next.js\u0026logoColor=white)](https://nextjs.org/)\n[![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-blue?logo=tailwind-css\u0026logoColor=white)](https://tailwindcss.com/)\n[![Drizzle ORM](https://img.shields.io/badge/Drizzle_ORM-blue?logo=drizzle\u0026logoColor=white)](https://orm.drizzle.team/)\n[![Auth.js](https://img.shields.io/badge/Auth.js-blue?logo=next.js\u0026logoColor=white)](https://authjs.dev/)\n\nNarravo is a sleek, minimal, and feature-rich blog engine designed for developers who appreciate modern web technologies and a robust content management experience. Built with the latest Next.js App Router, TypeScript, and Drizzle ORM, it offers a powerful foundation for your next personal blog or content platform.\n\n✨ **Key Features:**\n\n*   **Next.js 15 App Router:** Server Components, Server Actions, ISR, and smart caching tuned for fast content delivery.\n*   **React 19 + TypeScript 5.9:** Strict typing, modern React patterns, and explicit null handling across the stack.\n*   **Tailwind CSS Design System:** Utility-first styling with theme tokens, dark mode, and Radix-powered primitives.\n*   **PostgreSQL + Drizzle ORM:** Type-safe queries, migrations, and transactions with first-class PostgreSQL support.\n*   **Auth.js (NextAuth):** GitHub/Google OAuth, admin allowlists, and session management ready to go.\n*   **Two-Factor Authentication (2FA):** TOTP authenticator apps, WebAuthn passkeys (Face ID, Touch ID, YubiKey), recovery codes, and trusted device management.\n*   **Config Service \u0026 Feature Flags:** Database-backed settings power runtime toggles (themes, rate limits, UI modules).\n*   **WordPress WXR Import:** Resume-safe imports covering posts, media, redirects, excerpts, and CLI/admin workflows.\n*   **Data Operations Toolkit:** One-command backup/export, selective restore, purging, and manifest verification with audit logs.\n*   **Comments, Media \u0026 Reactions:** Threaded discussions, image/video uploads, emoji reactions, honeypot/rate-limited submissions.\n*   **Admin Control Center:** Moderation queue, system settings, analytics dashboards, and import/backups in one place.\n*   **S3/R2 Media Storage:** Presigned uploads with validation for AWS S3 or Cloudflare R2 plus local fallbacks.\n*   **Analytics \u0026 Observability:** Privacy-aware trending metrics, Core Web Vitals RUM pipeline, Server-Timing headers, and perf scripts.\n*   **Security Hardening:** CSP, HSTS, DOMPurify sanitization, hashed IP analytics, rate limits, and audit-friendly logging.\n*   **Dockerized Development \u0026 Testing:** Docker Compose Postgres, Vitest test suites, and perf/load tooling baked in.\n\n---\n\n## 👀 DEMO SITE: (My personal blog) [Knowledge Tome](https://www.shildreth.com)\n\n---\n\n## 🚀 Quick Start\n\nGet Narravo up and running in minutes!\n\n**Prerequisites:**\n*   [Docker](https://www.docker.com/get-started)\n*   [Node.js](https://nodejs.org/en/download/) (v18+)\n*   [pnpm](https://pnpm.io/installation)\n\n```bash\n# 0) Start PostgreSQL database with Docker Compose\ndocker compose up -d db\n\n# 1) Install project dependencies\npnpm install\n\n# 2) Configure environment variables\ncp .env.example .env\n# IMPORTANT: Ensure DATABASE_URL, NEXTAUTH_SECRET, ADMIN_EMAILS, and ANALYTICS_IP_SALT are set.\n# Adjust NEXTAUTH_URL and OAuth provider IDs as needed for your environment.\n\n# 3) Run database migrations (create tables)\npnpm drizzle:migrate\n\n# 4) Seed essential configuration defaults\npnpm seed:config\n\n# 5) (Optional) Seed demo posts and comments\npnpm seed:posts\n\n# 6) Start the development server\npnpm dev\n# Open your browser to http://localhost:3000\n\n# 7) (First-time setup) Sign in with OAuth and set up 2FA\n# - Sign in with GitHub or Google (your email must be in ADMIN_EMAILS)\n# - Navigate to /admin/security to enable Two-Factor Authentication\n# - Choose TOTP (authenticator app) or Passkey (biometric/hardware key)\n```\n\n\u003e 💡 **Manual DB Setup:** If you prefer a manual PostgreSQL instance, update `DATABASE_URL` in your `.env` file and skip the `docker compose up -d db` step.\n\u003e \n\u003e 🔐 **Admin Access:** Only emails listed in `ADMIN_EMAILS` can access the admin dashboard at `/admin`. After first login, you'll be prompted to set up Two-Factor Authentication for enhanced security.\n\n---\n\n\n## 💾 Data Operations\n\nNavravo ships with first-class data lifecycle tooling powered by Drizzle and S3-compatible manifests, accessible via both CLI scripts and the Admin UI at `/admin/data-operations`.\n\n### Export \u0026 Backup\n- **Full Backups:** `pnpm backup` creates a ZIP archive with JSON table exports, manifest hashes, and (optionally) media references.\n- **Audit Friendly:** Commands log counts, checksum data, and skip reasons to aid compliance reviews.\n- **Admin UI:** Export backups directly from the admin dashboard with progress tracking.\n\n### Restore \u0026 Import\n- **Selective Restore:** `pnpm restore -- \u003cbackup.zip\u003e` supports dry runs, slug/date filters, and skipping users/configuration.\n- **Smart Conflict Handling:** Choose how to handle existing content during restore operations.\n- **Media Awareness:** Manifests capture attachment URLs and hashes so you can verify remote assets before rehydration.\n\n### Purge Operations\n- **Soft Purge:** Remove published posts while preserving drafts, users, and configuration.\n- **Hard Purge:** Complete data reset removing all posts, comments, categories, tags, redirects, and uploaded files.\n- **Confirmation Required:** All destructive operations require explicit confirmation in the UI.\n\n### Audit Logging\n- **Complete Trail:** All data operations (export, restore, purge) are logged with timestamps, user info, and operation details.\n- **Compliance Ready:** Detailed audit logs support security reviews and compliance requirements.\n\n```bash\n# Create a verbose backup without media payloads\npnpm backup -- --skip-media --verbose\n\n# Preview restore scope for specific slugs before applying changes\npnpm restore -- backups/blog-2025-09-01.zip --dry-run --slugs my-first-post,second-post\n```\n---\n\n## 🚢 Deployment\n\nNarravo is designed for flexible deployment. You can host it on your own infrastructure using Docker or deploy it to a serverless platform like Vercel.\n\n*   **Option A: Docker Compose** - A single VM setup using Docker Compose to run the application and database. This is a good option for self-hosting.\n*   **Option B: Vercel + Neon** - A serverless setup using Vercel for the application and Neon for the database. This is a good option for a managed, scalable solution.\n\nFor detailed deployment instructions, please refer to the [**Production Deployment Guide**](./deploy/README_DEPLOY.md).\n\n---\n\n## 🧱 Architecture Overview\n\n- **App Router Layout:** The `src/app` tree separates public routes (`(public)`), authentication (`(auth)`), and admin tooling (`(admin)`), leaning on React Server Components by default. Middleware injects route context and handles legacy redirect resolution at the edge.\n- **Domain Modules:** Business logic lives in `src/lib` with clear boundaries for posts, comments, reactions, imports, backups, rate limiting, analytics, 2FA, and configuration. Server Actions validate input with Zod and revalidate cache tags after mutations.\n- **Authentication \u0026 Security:** Auth.js handles OAuth sessions with admin allowlists. Two-factor authentication (TOTP/WebAuthn) protects admin routes with 8-hour verification windows, trusted device management, and comprehensive audit logging.\n- **Data \u0026 Configuration:** Drizzle ORM drives the schema (`drizzle/schema.ts`) with migrations and transactions. A Config Service exposes database-backed feature flags (banner, theming, rate limits, render badges, etc.) to both server and client code.\n- **Engagement Pipeline:** Comments support nested threads, media attachments, moderation queues, and reaction toggles. Rate limiting, honeypots, media validation, and audit logging protect the workflow.\n- **Content Migration:** The WordPress importer coordinates resumable jobs, media rewriting, redirect creation, and excerpt rebuilding from either the Admin UI or CLI scripts.\n- **Data Operations:** Export, restore, and purge operations with manifest verification, selective filtering, and audit trails accessible via CLI or admin UI.\n- **Observability:** Privacy-aware analytics aggregate daily view counts, power trending widgets, and surface dashboards. Real User Monitoring collects Core Web Vitals, while Server-Timing instrumentation and perf scripts keep regressions in check.\n\n---\n\n## ⚙️ Configuration\n\nAll environment variables are documented in detail within the `.env.example` file. Copy it to `.env` and adjust as needed:\n\n```bash\ncp .env.example .env\n```\n\n**Required Variables:**\n\n*   `DATABASE_URL`: PostgreSQL connection string (e.g., `postgres://user:pass@localhost:5432/narravo`)\n*   `NEXTAUTH_SECRET`: Strong random string for session/JWT encryption (generate with `openssl rand -base64 32`)\n*   `NEXTAUTH_URL`: Your application's URL (e.g., `http://localhost:3000` or `https://yourdomain.com`)\n*   `ADMIN_EMAILS`: Comma-separated list of emails for admin access (case-insensitive)\n\n**Authentication \u0026 Security:**\n\n*   **OAuth Providers:** `GITHUB_ID`, `GITHUB_SECRET`, `GOOGLE_ID`, `GOOGLE_SECRET` (optional but recommended)\n*   **Analytics Privacy:** `ANALYTICS_IP_SALT` — Salt for hashing IP addresses before persisting view events (strongly recommended)\n\n**Media Storage (Optional):**\n\nChoose one of the following:\n*   **AWS S3:** `S3_REGION`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_BUCKET`, `S3_ENDPOINT` (optional for S3-compatible services)\n*   **Cloudflare R2:** `R2_REGION`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`, `R2_ENDPOINT`\n\nIf neither is configured, media will be stored locally in the `public/uploads` directory.\n\n**Performance \u0026 Observability (Optional):**\n\n*   `NEXT_PUBLIC_RUM_SAMPLING_RATE` — Client-side Real User Monitoring sampling rate (default: `0.1` = 10%)\n*   `RUM_SAMPLING_RATE` — Server-side RUM sampling rate (default: `0.1`)\n*   `PERF_LOG_SLOW_QUERIES` — Enable verbose query timing logs in development/CI (default: `false`)\n\n**Content Configuration (Optional):**\n\n*   `EXCERPT_MAX_CHARS` — Maximum characters for auto-generated excerpts (default: varies by algorithm)\n*   `EXCERPT_ELLIPSIS` — Ellipsis string for truncated excerpts (default: `…`)\n*   `EXCERPT_INCLUDE_BLOCK_CODE` — Include code blocks in excerpts (default: `false`)\n\n---\n\n## 🗄️ Database Management\n\nNarravo uses Drizzle ORM for type-safe database interactions and migrations.\n\n*   **Generate Migrations:** Create new migration files based on schema changes.\n    ```bash\n    pnpm drizzle:generate\n    ```\n*   **Apply Migrations:** Run pending migrations (production-safe).\n    ```bash\n    pnpm drizzle:migrate\n    ```\n*   **Check Migration Status:** Verify which migrations have been applied.\n    ```bash\n    pnpm drizzle:check\n    ```\n*   **Sync Migration Tracking:** Fix migration tracking after using `drizzle:push`.\n    ```bash\n    CONFIRM_MIGRATION_SYNC=yes pnpm drizzle:sync\n    ```\n*   **Push Schema (Dev Only):** Quickly sync schema during local development.\n    ```bash\n    pnpm drizzle:push\n    ```\n*   **Seed Configuration:** Essential for initial setup and default settings.\n    ```bash\n    pnpm seed:config\n    ```\n*   **Seed Demo Content:** Populate your blog with sample posts and comments.\n    ```bash\n    pnpm seed:posts\n    ```\n\n\u003e 📖 **For detailed migration workflows and troubleshooting**, see the [Database Migration Guide](./docs/DATABASE_MIGRATIONS.md).\n\n---\n\n## � Data Operations\n\nNarravo ships with first-class data lifecycle tooling powered by Drizzle and S3-compatible manifests.\n\n- **Full Backups:** `pnpm backup` creates a ZIP archive with JSON table exports, manifest hashes, and (optionally) media references.\n- **Selective Restore:** `pnpm restore -- \u003cbackup.zip\u003e [--slugs slug-a,slug-b] [--start-date YYYY-MM-DD]` supports dry runs, slug/date filters, and skipping users/configuration.\n- **Media Awareness:** Manifests capture attachment URLs and hashes so you can verify remote assets before rehydration.\n- **Audit Friendly:** Both commands log counts, checksum data, and skip reasons to aid compliance reviews.\n\n```bash\n# Create a verbose backup without media payloads\npnpm backup -- --skip-media --verbose\n\n# Preview restore scope for specific slugs before applying changes\npnpm restore -- backups/blog-2025-09-01.zip --dry-run --slugs my-first-post,second-post\n```\n\n---\n\n## �🛠️ Development Scripts\n\nA quick reference for common development tasks:\n\n| Command                      | Description                                           |\n| :--------------------------- | :---------------------------------------------------- |\n| `pnpm dev`                   | Start the Next.js development server                  |\n| `pnpm build`                 | Create a production-ready build                       |\n| `pnpm start`                 | Start the production server                           |\n| `pnpm typecheck`             | Run TypeScript type checks                            |\n| `pnpm test`                  | Execute the full test suite (Vitest)                  |\n| `pnpm test:watch`            | Run tests in watch mode                               |\n| `pnpm drizzle:generate`      | Generate new Drizzle migrations                       |\n| `pnpm drizzle:migrate`       | Apply pending migrations (production-safe)            |\n| `pnpm drizzle:push`          | Sync schema directly (development only)               |\n| `pnpm drizzle:check`         | Check migration status and database state             |\n| `pnpm drizzle:sync`          | Fix migration tracking (after push)                   |\n| `pnpm seed:config`           | Seed default configuration values                     |\n| `pnpm seed:posts`            | Seed demo posts and comments                          |\n| `pnpm wxr:import -- path=…`  | Launch the WordPress importer via CLI (supports flags) |\n| `pnpm backup`                | Create a manifested ZIP backup (see Backups section)   |\n| `pnpm restore -- \u003cfile\u003e`     | Restore or dry run a backup archive                    |\n| `pnpm cleanup:uploads`       | Remove orphaned temporary uploads (see below)          |\n| `pnpm perf:benchmark`        | Run the combined performance benchmark suite           |\n| `pnpm perf:lighthouse`       | Execute Lighthouse CI checks                          |\n| `pnpm perf:loadtest`         | Run Autocannon smoke load testing                      |\n| `pnpm perf:analyze`          | Build with bundle analyzer enabled                     |\n| `pnpm perf:weekly`           | Produce a weekly performance rollup report             |\n\n### Upload Cleanup\n\nNarravo tracks all image/video uploads in the database. When users add images to posts in the editor, they're initially marked as \"temporary\". Once the post is saved/published, these uploads are committed. The cleanup script removes orphaned temporary uploads:\n\n```bash\n# Dry run - see what would be deleted\nnpm run cleanup:uploads -- --dry-run\n\n# Delete temporary uploads older than 24 hours (default)\nnpm run cleanup:uploads\n\n# Delete temporary uploads older than 48 hours\nnpm run cleanup:uploads -- --age-hours=48\n```\n\n**Recommended:** Set up a cron job to run this cleanup daily or weekly to prevent accumulation of orphaned uploads from abandoned post drafts.\n\n---\n\n## 📂 Project Structure\n\nA high-level overview of the project's directory layout:\n\n```\nsrc/app/                # Next.js App Router routes\n  ├── (admin)/          # Protected admin routes (posts, users, analytics, security, data-operations)\n  ├── (auth)/           # Authentication routes (signin, 2FA verification)\n  ├── (public)/         # Public-facing pages (posts, archives, search)\n  └── api/              # API routes (2FA, comments, reactions, media uploads)\nsrc/components/         # Reusable UI components (admin, auth, public)\nsrc/lib/                # Server-side services and business logic\n  ├── 2fa/              # Two-factor authentication (TOTP, WebAuthn, recovery codes)\n  ├── excerpts/         # Excerpt generation algorithms\n  └── *.ts              # Domain modules (posts, comments, analytics, config, etc.)\nsrc/types/              # TypeScript types (consolidated)\ndrizzle/                # Drizzle ORM schema and database migrations\nscripts/                # Utility scripts (seeding, imports, backups, migrations)\ntests/                  # Unit and integration tests (Vitest)\ndocs/                   # Project documentation and specifications\n```\n\n---\n\n## 🔄 WordPress WXR Import\n\nNarravo includes a powerful and resilient WordPress import tool to migrate your content from a WordPress WXR export file.\n\n**Import Capabilities:**\n\n*   **Comprehensive Content:** Imports posts, comments (with threading), categories, and tags.\n*   **Flexible Statuses:** Choose which post statuses to import (e.g., `publish`, `draft`).\n*   **Media \u0026 SEO:** Downloads media to S3/R2, rewrites content URLs, and creates 301 redirects from old WordPress post URLs.\n*   **Offline Import Support:** Import media from local uploads directory when original site is offline or unreachable.\n*   **Enhanced Purge:** Complete data reset option that removes all posts, comments, categories, tags, redirects, and uploaded files.\n*   **Resilient Process:** Features include dry-runs, real-time progress, job cancellation, and detailed error logging.\n*   **Admin UI \u0026 CLI:** Manage imports through the Admin Dashboard or automate them with a CLI script.\n\nFor a complete guide, see the [**WordPress Import Documentation**](./docs/wordpress-import.md).\n\n### CLI Options\n\nThe `wxr:import` script supports several command-line flags to customize the import process:\n\n*   `path=\u003cfile\u003e`: (Required) The path to your WXR export file.\n*   `--verbose`: Enables detailed logging for troubleshooting.\n*   `--dry-run`: Simulates the import without making any changes to the database.\n*   `--skip-media`: Skips downloading and processing of any media files.\n*   `--rebuild-excerpts`: Forces regeneration of excerpts for all posts, even if they already have one.\n*   `--purge`: **Deletes all existing posts, comments, tags, categories, redirects, and uploaded files** before starting the import. Use with caution.\n*   `uploads=\u003cpath\u003e`: Specifies the path to a local folder containing your WordPress `uploads` directory. Use this for offline imports where the original site is not reachable.\n*   `root=\u003cpattern\u003e`: A regular expression to match the root URL of your old site (e.g., `^https?://my-old-site.com`). Required when using `uploads`.\n*   `allowedHosts=\u003chosts\u003e`: Comma-separated list of allowed domains for media downloads (e.g., `example.com,cdn.example.com`).\n*   `concurrency=\u003cnumber\u003e`: Number of simultaneous media downloads (1-10, default: 4).\n\n#### Example Offline Import\n\nThis command runs an import using a local backup of media files, purging all existing data and files first.\n\n```bash\npnpm wxr:import -- path=./export.xml --purge uploads=/path/to/wp-backup/uploads root='^https?://my-old-site\\.com$' --verbose\n```\n\n---\n\n## 📊 Analytics \u0026 Observability\n\n- **Trending \u0026 Dashboards:** View aggregation tables power the public \"Trending Posts\" widget and the `/admin/analytics` dashboard with sparklines, totals, and configurable date windows.\n- **Privacy-Aware Metrics:** View events dedupe by session/IP, respect bot filters, and hash IP addresses when `ANALYTICS_IP_SALT` is set.\n- **Real User Monitoring:** Drop `\u003cRUMCollector /\u003e` into layouts to post Core Web Vitals to `/api/rum`. Control sampling with `NEXT_PUBLIC_RUM_SAMPLING_RATE` (client) and `RUM_SAMPLING_RATE` (server), both defaulting to 10%.\n- **Performance Instrumentation:** Middleware adds Server-Timing headers, and the config flag `VIEW.PUBLIC-SHOW-RENDER-BADGE` surfaces render times in the UI. Perf scripts (`perf:*`) automate Lighthouse, bundle analysis, and load testing—see [`docs/perf/README.md`](./docs/perf/README.md).\n- **Weekly Rollups \u0026 Benchmarks:** Use `pnpm perf:benchmark` or `pnpm perf:weekly` to archive reports in `docs/perf/` and catch regressions early.\n\n---\n\n## 🧑 About Me Sidebar\n\nAn optional About Me section can appear in the public sidebar.\n\n- Enable/disable: SITE.ABOUT-ME.ENABLED (boolean)\n- Title: SITE.ABOUT-ME.TITLE (string)\n- Content: SITE.ABOUT-ME.CONTENT (string)\n\nYou can manage these in the Admin Dashboard:\n\n- Navigate to Admin -\u003e System -\u003e About Me\n- Toggle enable, edit the title and content, then Save Changes\n\nWhen enabled, the About Me section renders above the Recent posts list.\n\n---\n\n## 🔐 Security\n\n- **Sanitized Content:** All HTML passes through DOMPurify allowlists before storage/rendering, and markdown editors sanitize on save.\n- **Strict Headers:** CSP, HSTS, Referrer-Policy, and X-Content-Type-Options are enabled via Next config and middleware defaults.\n- **Rate Limiting \u0026 Abuse Controls:** Shared helpers cap comment/reaction/import rates, enforce submission delays, and add honeypots.\n- **Safe Media Handling:** Attachments validate MIME types, file signatures, byte limits, and generate poster images for videos.\n- **Privacy Respecting Analytics:** View tracking dedupes sessions, hashes IPs with `ANALYTICS_IP_SALT`, and honors DNT where possible.\n- **Two-Factor Authentication:** Admin routes protected with 2FA enforcement, with 8-hour verification windows and secure session management.\n\n---\n\n### 🔐 Two-Factor Authentication (2FA)\n\nNarravo includes a robust 2FA implementation protecting admin routes with multiple authentication methods and comprehensive security features.\n\n**Features:**\n- **Multiple Methods:** TOTP (Google Authenticator, Authy, etc.), WebAuthn/Passkeys (Face ID, Touch ID, YubiKey), Recovery codes\n- **Trusted Devices:** Optional 30-day device trust to reduce friction for frequent users\n- **Security Activity Log:** Comprehensive audit trail of all 2FA events (enable, disable, verification attempts, device management)\n- **Admin Enforcement:** Admins must complete 2FA setup before accessing admin dashboard\n- **8-Hour Verification Window:** Re-verification required after 8 hours for security-sensitive operations\n- **Rate Limiting:** Protection against brute force attacks with progressive backoff\n- **Recovery Options:** Backup codes and passkey fallbacks prevent lockouts\n\n**Technical Implementation:**\n- TOTP via `@levminer/speakeasy` with 30-second window and drift tolerance\n- WebAuthn via SimpleWebAuthn with RP ID validation and credential counter protection\n- Trusted device tokens use SHA-256 hashed identifiers with 30-day expiration\n- Security activity logged to database with IP, user agent, and detailed event context\n- Client-side guard component (TwoFactorGuard) enforces verification UI\n- Server-side helpers (requireAdmin2FA) protect API routes\n- Test suite: **120+ tests** across 15+ test files covering TOTP, WebAuthn, rate limiting, trusted devices, security logging, admin enforcement, and all API endpoints\n\n---\n\n## 🧪 Testing\n\nNarravo has a comprehensive test suite with 500+ tests covering core functionality, security features, and edge cases.\n\n**Run Tests:**\n```bash\npnpm test              # Run all tests\npnpm test:watch        # Run tests in watch mode\npnpm typecheck         # Type checking only\n```\n\n**Test Coverage Includes:**\n- **2FA \u0026 Security:** TOTP verification, WebAuthn flows, passkey support, recovery codes, trusted devices, rate limiting, admin enforcement, security activity logging (60+ tests across 15+ test files)\n- **API Endpoints:** All admin APIs (config, data operations, user management, purge), 2FA endpoints (TOTP, WebAuthn, recovery, trusted devices), uploads, metrics, redirects, import jobs (100+ tests)\n- **Server Actions:** Post deletion with cascade, WordPress import job management, theme preferences (64+ tests)\n- **Middleware:** Request flow security, redirects (date-based and database), caching, authentication checks (27+ tests)\n- **Content Management:** Post creation, WordPress imports (30+ specialized import tests), markdown processing, excerpt generation, taxonomy, blocks parsing\n- **Analytics:** View tracking, deduplication, bot detection, privacy features, trending metrics\n- **Comments \u0026 Moderation:** Threading, timing attacks, honeypots, auto-approval, path resolution\n- **Data Operations:** Backup/restore workflows, manifest verification, purge operations, audit logging\n- **Configuration:** Feature flags, boolean parsing, system settings, user preferences\n- **Utilities:** Logger with log levels, frame-src CSP configuration, admin access control, date formatting, HTML normalization\n\n**Test Infrastructure:**\n- [Vitest](https://vitest.dev/) as the test runner with TypeScript strict mode\n- [React Testing Library](https://testing-library.com/) for component testing\n- In-memory SQLite database for fast, isolated unit tests\n- Comprehensive mocking strategies for Auth.js, Next.js, and external services\n- No external dependencies required for running tests\n- All tests pass TypeScript strict null checks and build validation\n\n**Quality Standards:**\n- ✅ All tests pass `pnpm typecheck` (TypeScript strict mode)\n- ✅ All tests pass `pnpm build` (production build validation)\n- ✅ All tests pass `pnpm test` (100% pass rate)\n- ✅ Comprehensive security test coverage (2FA, authentication, authorization)\n- ✅ Critical path coverage for all admin functionality\n\n---\n\n## 📄 License\n\nThis project is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) and [NOTICE](./NOTICE) files for more details.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsphildreth%2Fnarravo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsphildreth%2Fnarravo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsphildreth%2Fnarravo/lists"}