Complete merge.

This commit is contained in:
Mike Cao
2026-02-25 02:32:25 -08:00
parent e73d827d21
commit 7d2dbda5d1
3 changed files with 10 additions and 198 deletions
-97
View File
@@ -1,97 +0,0 @@
# AGENTS.md
This file provides guidance for AI coding agents working in this repository.
## Project overview
Umami is a privacy-focused web analytics platform built with Next.js 15, React 19, and TypeScript.
- Primary database: PostgreSQL
- Optional analytics backend: ClickHouse
- Optional cache/session backend: Redis
## Development rules
- Assume a dev server is already running on port `3001`.
- Do **not** start another dev server.
- Use `pnpm` (not `npm` or `yarn`).
- Avoid destructive shell commands unless explicitly requested.
- Ask before running `git commit` or `git push`.
## Common commands
```bash
# Development
pnpm dev
pnpm build
pnpm start
# Database
pnpm build-db
pnpm update-db
pnpm check-db
pnpm seed-data
# Code quality
pnpm lint
pnpm format
pnpm check
pnpm test
# Build specific parts
pnpm build-tracker
pnpm build-geo
```
## Architecture
- `src/app/`: Next.js App Router routes and API endpoints
- `src/components/`: UI components and hooks
- `src/lib/`: shared utilities and infrastructure helpers
- `src/queries/`: data access layer (Prisma + raw SQL)
- `src/store/`: Zustand stores
- `src/tracker/`: standalone client tracking script
- `prisma/`: schema and migrations
## Key implementation patterns
### API request validation
Use Zod + `parseRequest` in API handlers:
```ts
const schema = z.object({ /* fields */ });
const { body, error } = await parseRequest(request, schema);
if (error) return error();
```
### Authentication
- JWT via `Authorization: Bearer <token>`
- Share token via `x-umami-share-token`
- Role model: `admin`, `manager`, `user`
### Client data fetching
- React Query defaults: `staleTime` 60s, no retry, no refetch on window focus
### Styling
- CSS Modules with CSS variables and theme support
## Environment variables
Common env vars:
- `DATABASE_URL`
- `APP_SECRET`
- `CLICKHOUSE_URL`
- `REDIS_URL`
- `BASE_PATH`
- `DEBUG`
## Runtime requirements
- Node.js 18.18+
- PostgreSQL 12.14+
- `pnpm`
-100
View File
@@ -1,100 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Umami is a privacy-focused web analytics platform built with Next.js 15, React 19, and TypeScript. It serves as an alternative to Google Analytics, storing data in PostgreSQL (primary) with optional ClickHouse for time-series analytics.
## Development
Assume a dev server is always running on port 3001. Do not start the dev server.
## Common Commands
```bash
# Development
npm run dev # Start dev server on port 3001 with Turbopack
npm run build # Full production build (includes db setup, tracker, geo data)
npm run start # Start production server
# Database
npm run build-db # Generate Prisma client
npm run update-db # Run Prisma migrations
npm run check-db # Verify database connection
npm run seed-data # Seed test data
# Code Quality
npm run lint # Lint with Biome
npm run format # Format with Biome
npm run check # Format and lint with Biome
npm run test # Run Jest tests
# Building specific parts
npm run build-tracker # Build client-side tracking script (Rollup)
npm run build-geo # Build geolocation database
```
## Architecture
### Directory Structure
- `src/app/` - Next.js App Router (routes and API endpoints)
- `(main)/` - Authenticated app routes (dashboard, websites, teams, boards, etc.)
- `(collect)/` - Data collection routes
- `api/` - REST API endpoints
- `src/components/` - React components (charts, forms, common UI, hooks)
- `src/lib/` - Core utilities (auth, crypto, date, prisma helpers, redis)
- `src/queries/` - Data access layer (Prisma queries and raw SQL)
- `src/store/` - Zustand state stores (app, dashboard, websites, cache)
- `src/tracker/` - Client-side tracking script (lightweight IIFE)
- `prisma/` - Database schema and migrations
### Key Patterns
**API Request Handling** - All API endpoints use Zod validation with `parseRequest`:
```typescript
const schema = z.object({ /* fields */ });
const { body, error } = await parseRequest(request, schema);
if (error) return error();
```
**Authentication** - JWT tokens via Bearer header, share tokens via `x-umami-share-token` header for public dashboards. Role-based access: admin, manager, user.
**Data Fetching** - React Query with 60s stale time, no retry, no refetch on window focus.
**State Management** - Zustand for client state, localStorage for user preferences.
**Styling** - CSS Modules with CSS variables for theming (light/dark mode).
### Database
- **ORM**: Prisma 7.x with PostgreSQL adapter
- **Schema**: `prisma/schema.prisma` - 14 models (User, Team, Website, Session, WebsiteEvent, EventData, etc.)
- **Query helpers**: `src/lib/prisma.ts` has dynamic SQL generation functions (`getDateSQL`, `mapFilter`, `getSearchSQL`)
- **Raw SQL**: Complex analytics queries use `{{param}}` template placeholders for safe binding
### Tracker Script
The tracking script in `src/tracker/index.js` is a standalone IIFE (~3-4KB) built with Rollup. It sends events to `/api/send`. Alternative script names can be configured via `TRACKER_SCRIPT_NAME` env var.
## Environment Variables
Key variables in `.env`:
```
DATABASE_URL # PostgreSQL connection string (required)
APP_SECRET # Encryption/signing secret
CLICKHOUSE_URL # Optional ClickHouse for analytics
REDIS_URL # Optional Redis for caching/sessions
BASE_PATH # App base path (e.g., /analytics)
DEBUG # Debug namespaces (e.g., umami:*)
```
## Requirements
- Node.js 18.18+
- PostgreSQL 12.14+
- pnpm (package manager)
## Git Workflow
Always ask for confirmation before running `git commit` or `git push`.
@@ -4,7 +4,16 @@ import { useBoard } from '@/components/hooks';
export function BoardControls() {
const { board } = useBoard();
const websiteId = board?.parameters?.websiteId;
const boardWebsiteId = board?.parameters?.websiteId;
const componentWebsiteIds = board?.parameters?.rows
?.flatMap(row => row.columns)
.map(column => column.component?.websiteId)
.filter(Boolean);
const fallbackWebsiteId =
componentWebsiteIds && new Set(componentWebsiteIds).size === 1
? componentWebsiteIds[0]
: undefined;
const websiteId = boardWebsiteId || fallbackWebsiteId;
if (!websiteId) {
return null;