Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8c0acb0
docs: add Mermaid architecture diagram suite + log Phase 5.5 parallel…
xdroberto Jul 8, 2026
22dca09
docs(process): close Phase 5 (promoted, LIVE) + record 5.5 diagram su…
xdroberto Jul 8, 2026
4046725
fix(docs): flatten VPS topology diagram for GitHub render + record SS…
xdroberto Jul 8, 2026
ab42bc2
feat(brand): aurora-tech v2 tokens (operator design upgrade)
xdroberto Jul 8, 2026
fa77a08
docs(design): add operator landing design reference (aurora-tech v2 s…
xdroberto Jul 8, 2026
185ff2d
docs(security): close SSH F1/F2 (keys-only audited, fail2ban stats) +…
xdroberto Jul 8, 2026
5ade59e
docs(delivery): add SUBMISSION.md + README badges; rotate reviewer cr…
xdroberto Jul 8, 2026
68b7615
feat(landing): hero v2 + marketing typography foundation
xdroberto Jul 8, 2026
8cfba87
Merge remote-tracking branch 'origin/develop' into feature/ui-landing…
xdroberto Jul 8, 2026
6358c66
docs: refresh canonical state (north star delivered, next-step + hygi…
xdroberto Jul 8, 2026
f50528b
fix(ci): exclude design-reference evidence from biome lint
xdroberto Jul 8, 2026
cf4058f
Merge pull request #8 from xdroberto/feature/ui-landing-polish
xdroberto Jul 8, 2026
7f987d8
feat(landing): features section + cursor interactions + scroll-aware nav
xdroberto Jul 8, 2026
f40f845
fix: two prod-only bugs — clear __Secure cookie with Secure + abort o…
xdroberto Jul 8, 2026
134dfd1
fix(security): harden auth surface (audit findings)
xdroberto Jul 8, 2026
3437045
chore(ci): pin all GitHub Actions to commit SHAs + scope ci.yml permi…
xdroberto Jul 8, 2026
b4c09da
docs(security): document deferred CSP + logout-CSRF audit findings
xdroberto Jul 8, 2026
662160d
Merge pull request #9 from xdroberto/feature/ui-landing-features
xdroberto Jul 8, 2026
4208cdc
Merge pull request #10 from xdroberto/fix/security-audit
xdroberto Jul 8, 2026
2306403
feat(landing): metrics, marquee, CTA v2 + nav/footer polish (Phase 5.5)
xdroberto Jul 8, 2026
6f504ef
fix(branding): purge residual template branding + harden CI gate
xdroberto Jul 8, 2026
70fabac
feat(dashboard): coherent Nova metrics on the default page
xdroberto Jul 8, 2026
fb49fe1
feat(dashboard): coherent Nova metrics on the analytics page
xdroberto Jul 8, 2026
f35cfc6
feat(dashboard): coherent Nova metrics on the ecommerce page
xdroberto Jul 8, 2026
6a25741
feat(dashboard): reframe finance page as Usage & Billing
xdroberto Jul 8, 2026
9eb31e3
Merge pull request #11 from xdroberto/feature/ui-landing-sections
xdroberto Jul 8, 2026
d9cdccc
Merge pull request #12 from xdroberto/feature/dashboard-data
xdroberto Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
DATABASE_URL=postgresql://nova:novadev@localhost:5434/nova
BETTER_AUTH_SECRET=<generate: openssl rand -base64 32>
BETTER_AUTH_URL=http://localhost:3000
# Required for `npm run seed` (the seeded reviewer/admin password). No default —
# seeding now fails closed if this is unset (never ships a repo-visible credential).
SEED_PASSWORD=<choose a strong password>
15 changes: 9 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ on:
push:
branches: [main, develop]
pull_request:
# Least privilege: this workflow only reads the repo (lint/test/build); no write scope needed.
permissions:
contents: read
jobs:
commits:
# repo-steward gate: conventional-commit check on the PR's OWN commits only.
Expand All @@ -12,17 +15,17 @@ jobs:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: { node-version: 22, cache: npm }
- run: npm ci
- run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: { node-version: 22, cache: npm }
- run: npm ci
- run: npm run lint
Expand All @@ -45,8 +48,8 @@ jobs:
BETTER_AUTH_SECRET: ci-only-secret-not-production
BETTER_AUTH_URL: http://localhost:3000
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with: { node-version: 22, cache: npm }
- run: npm ci
# --force: drizzle-kit push silently no-ops in non-TTY without it
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with: { registry: ghcr.io, username: "${{ github.actor }}", password: "${{ secrets.GITHUB_TOKEN }}" }
- uses: docker/build-push-action@v6
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
push: true
Expand All @@ -27,16 +27,16 @@ jobs:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: appleboy/scp-action@v0.1.7
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
source: deploy/remote-deploy.sh
target: /opt/nova
strip_components: 1
- uses: appleboy/ssh-action@v1
- uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Nova Analytics

[![CI](https://github.com/xdroberto/nova-analytics/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/xdroberto/nova-analytics/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-22.x-brightgreen.svg)](.nvmrc)

**See your data become light.** Nova Analytics turns raw numbers into decisions your whole team can read — real-time dashboards, self-hosted and private.

> Live: `https://nova.robertobh.dev` (Phase 4) · Built with Next.js 16, TypeScript, Tailwind CSS v4, shadcn/ui, Better Auth, Drizzle ORM, and Postgres 17.
> **Live:** https://nova.robertobh.dev · Built with Next.js 16, TypeScript, Tailwind CSS v4, shadcn/ui, Better Auth, Drizzle ORM, and Postgres 17 — deployed to a Hetzner VPS with rollback-safe push-to-deploy CI/CD and uptime monitoring.
>
> **Reviewers:** [`SUBMISSION.md`](SUBMISSION.md) has the live URL, credentials, and what was built. Architecture diagrams: [`docs/architecture.md`](docs/architecture.md).

## Quickstart

Expand Down
58 changes: 58 additions & 0 deletions SUBMISSION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Nova Analytics — Submission

A self-hosted analytics dashboard built for the technical trial: whitelabelled from an open-source
admin template, wired with real self-hosted auth, and deployed live with CI/CD and monitoring.

## Live

- **App:** https://nova.robertobh.dev
- **Repository:** https://github.com/xdroberto/nova-analytics
- **Health:** https://nova.robertobh.dev/api/health → `{"status":"ok","db":"up"}`

## Reviewer access

| | |
|---|---|
| Email | `admin@novaanalytics.io` |
| Password | `Nova-a9d6e1-Rev26!` |

Sign in at https://nova.robertobh.dev/login. The credential is intentionally shared for review; sign-in is
rate-limited and sessions are revocable.

## What was built

**Core (PRD requirements):**
- **Whitelabel** — zero original-template branding remains visible; CI-enforced by a branding gate.
- **Landing** — hero + features + CTA, dark-first, responsive (Lighthouse 94 mobile / 100 desktop).
- **Self-hosted auth** — email/password via Better Auth + Drizzle + Postgres (no managed vendors).
Signup/login → dashboard; revocable DB-backed sessions; `/dashboard/*` guarded by an optimistic edge
check plus an authoritative server-side session check.
- **Live HTTPS deployment**, stable across the review window.

**Bonus delivered:**
- **CI/CD** — GitHub Actions: conventional-commit gate, lint + typecheck + unit + e2e, and rollback-safe
push-to-deploy (build → GHCR → SSH → health-retry → auto-rollback on failure).
- **Tests** — Vitest unit tests + Playwright e2e, including a **security bypass suite** (no-cookie /
forged / expired-session denial, sign-in rate-limiting, security-header regression).
- **Security hardening** — app-layer rate limiting, security headers (HSTS / X-Frame-Options / etc.),
supply-chain `ignore-scripts`, VPS SSH hardening (key-only + fail2ban), and an adversarial code review.
- **Monitoring** — UptimeRobot on `/api/health` (5-minute interval, email + SSL-expiry alerts).
- **Custom domain** — nova.robertobh.dev with TLS via certbot.
- **Process transparency** — the full agentic build process is kept as canonical state (below).

## Deeper docs

- **Architecture (6 diagrams):** [`docs/architecture.md`](docs/architecture.md) — system, DB ERD, auth
flow, CI/CD, VPS topology, branching model.
- **AI development process:** [`docs/ai-process/`](docs/ai-process/) — roadmap, live BRAIN (canonical
state), session log, agent team, prompts.
- **Deployment:** [`docs/deployment.md`](docs/deployment.md) · **ADRs:** [`docs/adr/`](docs/adr/) ·
**Honest limitations:** [`docs/limitations.md`](docs/limitations.md).

## Walkthrough video

> **TODO (Roberto):** [VIDEO LINK]

## Behavioral questionnaire

> **TODO (Roberto):** [behavioral questionnaire]
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"!dist",
"!out",
"!src/components/ui",
"!src/components/calendar"
"!src/components/calendar",
"!docs/design-reference"
]
},
"formatter": {
Expand Down
5 changes: 4 additions & 1 deletion deploy/remote-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ wait_healthy() {
prev_image=$($COMPOSE ps -q web 2>/dev/null | xargs -r docker inspect --format '{{.Image}}' 2>/dev/null || true)
echo "previous image: ${prev_image:-<none, first deploy>}"

$COMPOSE pull web
# Guard the pull: without `set -e`, a failed pull would otherwise fall through to
# `up -d` (a no-op that keeps the OLD container), pass the health check against the
# stale app, and report a green deploy that never actually shipped the new image.
$COMPOSE pull web || { echo "❌ image pull failed — aborting; previous container keeps running"; exit 1; }
$COMPOSE up -d

if wait_healthy 20; then
Expand Down
Loading
Loading