Compare commits

7 Commits
master ... main

Author SHA1 Message Date
apple
06f5d7d143 修改docker部署
Some checks failed
Node.js CI / build (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
2026-03-14 16:31:38 +08:00
apple
a8d2b84f35 修改docker部署
Some checks failed
Node.js CI / build (push) Has been cancelled
2026-03-14 16:29:28 +08:00
apple
fd9fe51b8f 修改docker部署
Some checks failed
Node.js CI / build (push) Has been cancelled
2026-03-14 16:22:28 +08:00
apple
27b89c5f1f 修改docker部署
Some checks failed
Node.js CI / build (push) Has been cancelled
2026-03-14 16:21:47 +08:00
apple
87a1372f20 修改docker部署
Some checks failed
Node.js CI / build (push) Has been cancelled
2026-03-14 16:15:56 +08:00
apple
50e29f88f1 修改docker部署
Some checks failed
Node.js CI / build (push) Has been cancelled
2026-03-14 16:09:59 +08:00
apple
f0eb19c417 修改docker部署
Some checks failed
Node.js CI / build (push) Has been cancelled
2026-03-14 15:51:38 +08:00
7 changed files with 214 additions and 15112 deletions

187
CLAUDE.md Normal file
View File

@@ -0,0 +1,187 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Umami is a simple, fast, privacy-focused alternative to Google Analytics. It's built with Next.js 15 (App Router), TypeScript, and supports multiple database backends (PostgreSQL, ClickHouse, Kafka).
## Development Commands
### Package Manager
- Uses **pnpm** as the package manager (monorepo with `pnpm-workspace.yaml`)
- Install dependencies: `pnpm install`
### Development
- Start development server: `pnpm dev` (runs on port 3001 with Turbo)
- Build application: `pnpm build` (runs multiple build steps including database setup)
- Start production server: `pnpm start`
- Build for Docker: `pnpm build-docker`
### Database Operations
- Build Prisma client: `pnpm build-prisma-client`
- Update database schema: `pnpm update-db`
- Check database connection: `pnpm check-db`
- Seed test data: `pnpm seed-data`
- Change admin password: `pnpm change-password`
### Code Quality
- Lint: `pnpm lint` (uses Biome)
- Format: `pnpm format` (uses Biome)
- Check (lint + format): `pnpm check` (uses Biome)
- Test: `pnpm test` (uses Jest)
- Cypress E2E tests: `pnpm cypress-run` or `pnpm cypress-open`
### Tracker Script
- Build tracker script: `pnpm build-tracker` (rollup builds `/public/script.js`)
- Update tracker: `pnpm update-tracker`
### Internationalization
- Generate language files: `pnpm generate-lang`
- Format language files: `pnpm format-lang`
- Compile language files: `pnpm compile-lang`
## Architecture
### Tech Stack
- **Frontend**: Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS
- **Backend**: Next.js API routes, Prisma ORM, multiple database support
- **Database**: PostgreSQL (primary), ClickHouse (analytics), Kafka (event streaming)
- **State Management**: Zustand, React Query (TanStack)
- **Styling**: CSS Modules, Tailwind CSS, `@umami/react-zen` component library
- **Code Quality**: Biome (linting/formatting), TypeScript, Jest, Cypress
### Key Directories
- `src/app/` - Next.js App Router pages and API routes
- `(collect)/` - Collection endpoints (pixels, links)
- `(main)/` - Main application UI routes
- `api/` - REST API endpoints
- `src/components/` - React components
- `common/` - Shared UI components
- `charts/` - Data visualization components
- `hooks/` - Custom React hooks
- `src/lib/` - Utility libraries and shared logic
- `src/tracker/` - Analytics tracker JavaScript (built to `/public/script.js`)
- `src/styles/` - Global CSS and design tokens
- `prisma/` - Database schema and migrations
- `db/` - Database-specific schemas (PostgreSQL, ClickHouse)
- `scripts/` - Build and utility scripts
### Database Architecture
- **Primary Database**: PostgreSQL with Prisma ORM
- **Analytics Database**: ClickHouse for high-volume analytics data
- **Event Streaming**: Kafka for real-time event processing
- **Cache**: Redis for session and data caching
- Database type determined by `DATABASE_URL` environment variable
- Queries use `runQuery()` helper that routes to appropriate database
### API Structure
- RESTful API built with Next.js Route Handlers
- Authentication via JWT tokens
- Main collection endpoint: `/api/send` (handles analytics events)
- Admin endpoints under `/api/admin/`
- User/team management under `/api/me/`, `/api/users/`, `/api/teams/`
- Website analytics under `/api/websites/[websiteId]/`
### Tracker System
- JavaScript tracker at `/public/script.js` (built from `src/tracker/`)
- Configurable via data attributes on script tag
- Supports event tracking, page views, custom events
- Can be hosted externally via `TRACKER_SCRIPT_URL` env var
- Multiple script names supported via `TRACKER_SCRIPT_NAME` env var
### Environment Configuration
Required environment variables:
- `DATABASE_URL` - PostgreSQL connection string
- `APP_SECRET` - JWT secret key
Optional environment variables:
- `CLICKHOUSE_URL` - ClickHouse connection for analytics
- `KAFKA_URL` - Kafka connection for event streaming
- `REDIS_URL` - Redis connection for caching
- `CLOUD_MODE` - Enable cloud-specific features
- `COLLECT_API_ENDPOINT` - Custom endpoint for tracker
- `TRACKER_SCRIPT_NAME` - Alternative script names
- `DISABLE_UI` - Disable web interface (API-only mode)
## Development Notes
### Database Setup
1. Set `DATABASE_URL` in `.env` file
2. Run `pnpm build` to create database tables and admin user (admin/umami)
3. For ClickHouse analytics, also set `CLICKHOUSE_URL`
### Testing
- Unit tests: `pnpm test` (Jest with `src/lib/__tests__/`)
- E2E tests: `pnpm cypress-run` (requires running application)
- Test database: Uses separate test database configuration
### Code Style
- Uses Biome for formatting and linting (configured in `biome.json`)
- Line width: 100 characters
- Single quotes for strings
- Trailing commas in multiline objects/arrays
- TypeScript strict mode enabled
### Build Process
The `pnpm build` command runs multiple sequential steps:
1. `check-env` - Validate environment variables
2. `build-db` - Build Prisma client and database schema
3. `check-db` - Verify database connection
4. `build-tracker` - Build analytics tracker script
5. `build-geo` - Build GeoIP database
6. `build-app` - Build Next.js application
### Docker Deployment
- Docker image includes standalone Next.js output
- Uses multi-stage build for production optimization
- Includes all necessary dependencies
- Health check endpoint at `/api/heartbeat`
### Internationalization
- Uses `react-intl` for translations
- Messages extracted from `src/components/messages.ts`
- Compiled to `public/intl/messages/`
- Default locale: `en-US`, configurable via `DEFAULT_LOCALE` env var
## Common Tasks
### Adding a New API Endpoint
1. Create file in `src/app/api/[path]/route.ts`
2. Use `parseRequest()` helper for request validation
3. Use `runQuery()` for database operations
4. Return appropriate HTTP responses using helpers from `src/lib/response.ts`
### Adding a New Component
1. Create component in `src/components/` (organized by feature)
2. Use CSS Modules for styling (`*.module.css`)
3. Export TypeScript interfaces/types
4. Add to appropriate barrel file (`index.ts`) if needed
### Database Schema Changes
1. Update `prisma/schema.prisma`
2. Create migration: `prisma migrate dev --name description`
3. Update Prisma client: `pnpm build-prisma-client`
4. Update TypeScript types as needed
### Adding Tracker Features
1. Modify `src/tracker/index.js`
2. Rebuild tracker: `pnpm build-tracker`
3. Test with local development server
## Troubleshooting
### Database Connection Issues
- Verify `DATABASE_URL` format: `postgresql://user:pass@host:port/db`
- Check database is running and accessible
- Run `pnpm check-db` to test connection
### Build Failures
- Ensure all environment variables are set
- Check Node.js version (requires 18.18+)
- Clear `.next` cache if build issues persist
### Tracker Not Working
- Verify script is loaded: check browser console
- Check CORS headers if hosted on different domain
- Test with `/api/send` endpoint directly

View File

@@ -1,16 +1,20 @@
ARG NODE_IMAGE_VERSION="22-alpine" ARG NODE_IMAGE_VERSION="22-alpine"
# Install dependencies only when needed # Install dependencies only when needed
FROM node:${NODE_IMAGE_VERSION} AS deps FROM hub.diyla.com/node:${NODE_IMAGE_VERSION} AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
COPY package.json pnpm-lock.yaml ./ COPY package.json ./
# 设置 npm registry 为 npmmirror 镜像源
RUN npm config set registry https://registry.npmmirror.com
RUN npm install -g pnpm RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile # 清理 pnpm store 并安装依赖
RUN pnpm store prune || true
RUN pnpm install
# Rebuild the source code only when needed # Rebuild the source code only when needed
FROM node:${NODE_IMAGE_VERSION} AS builder FROM hub.diyla.com/node:${NODE_IMAGE_VERSION} AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
@@ -22,10 +26,14 @@ ENV BASE_PATH=$BASE_PATH
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_URL="postgresql://user:pass@localhost:5432/dummy" ENV DATABASE_URL="postgresql://user:pass@localhost:5432/dummy"
# 设置 npm registry 为 npmmirror 镜像源
RUN npm config set registry https://registry.npmmirror.com
# 设置 pnpm 配置
RUN pnpm config set registry https://registry.npmmirror.com || true
RUN npm run build-docker RUN npm run build-docker
# Production image, copy all the files and run next # Production image, copy all the files and run next
FROM node:${NODE_IMAGE_VERSION} AS runner FROM hub.diyla.com/node:${NODE_IMAGE_VERSION} AS runner
WORKDIR /app WORKDIR /app
ARG PRISMA_VERSION="6.19.0" ARG PRISMA_VERSION="6.19.0"
@@ -37,10 +45,17 @@ ENV NODE_OPTIONS=$NODE_OPTIONS
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 nextjs
# 设置 npm registry 为 npmmirror 镜像源
RUN npm config set registry https://registry.npmmirror.com
RUN set -x \ RUN set -x \
&& apk add --no-cache curl \ && apk add --no-cache curl \
&& npm install -g pnpm && npm install -g pnpm
# 设置 pnpm 配置
RUN pnpm config set registry https://registry.npmmirror.com || true
# Script dependencies # Script dependencies
RUN pnpm --allow-build='@prisma/engines' add npm-run-all dotenv chalk semver \ RUN pnpm --allow-build='@prisma/engines' add npm-run-all dotenv chalk semver \
prisma@${PRISMA_VERSION} \ prisma@${PRISMA_VERSION} \

View File

@@ -1,7 +1,9 @@
--- ---
services: services:
umami: umami:
image: ghcr.io/umami-software/umami:latest build:
context: .
dockerfile: Dockerfile
ports: ports:
- "3000:3000" - "3000:3000"
environment: environment:
@@ -18,7 +20,7 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
db: db:
image: postgres:15-alpine image: hub.diyla.com/postgres:15-alpine
environment: environment:
POSTGRES_DB: umami POSTGRES_DB: umami
POSTGRES_USER: umami POSTGRES_USER: umami

15102
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ if (!url) {
`https://download.maxmind.com/app/geoip_download` + `https://download.maxmind.com/app/geoip_download` +
`?edition_id=${db}&license_key=${process.env.MAXMIND_LICENSE_KEY}&suffix=tar.gz`; `?edition_id=${db}&license_key=${process.env.MAXMIND_LICENSE_KEY}&suffix=tar.gz`;
} else { } else {
url = `https://raw.githubusercontent.com/GitSquared/node-geolite2-redist/master/redist/${db}.tar.gz`; url = `https://hub.diyla.com/https://raw.githubusercontent.com/GitSquared/node-geolite2-redist/master/redist/${db}.tar.gz`;
} }
} }

View File

@@ -10,7 +10,7 @@ const dest = path.resolve(process.cwd(), 'public/intl/country');
const files = fs.readdirSync(src); const files = fs.readdirSync(src);
const getUrl = locale => const getUrl = locale =>
`https://raw.githubusercontent.com/umpirsky/country-list/master/data/${locale}/country.json`; `https://hub.diyla.com/https://raw.githubusercontent.com/umpirsky/country-list/master/data/${locale}/country.json`;
const asyncForEach = async (array, callback) => { const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) { for (let index = 0; index < array.length; index++) {

View File

@@ -10,7 +10,7 @@ const dest = path.resolve(process.cwd(), 'public/intl/language');
const files = fs.readdirSync(src); const files = fs.readdirSync(src);
const getUrl = locale => const getUrl = locale =>
`https://raw.githubusercontent.com/umpirsky/language-list/master/data/${locale}/language.json`; `https://hub.diyla.com/https://raw.githubusercontent.com/umpirsky/language-list/master/data/${locale}/language.json`;
const asyncForEach = async (array, callback) => { const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) { for (let index = 0; index < array.length; index++) {