mirror of https://github.com/usememos/memos.git
580 lines
17 KiB
Markdown
580 lines
17 KiB
Markdown
# Memos Codebase Guide for AI Agents
|
|
|
|
This document provides comprehensive guidance for AI agents working with the Memos codebase. It covers architecture, workflows, conventions, and key patterns.
|
|
|
|
## Project Overview
|
|
|
|
Memos is a self-hosted knowledge management platform built with:
|
|
- **Backend:** Go 1.25 with gRPC + Connect RPC
|
|
- **Frontend:** React 18.3 + TypeScript + Vite 7
|
|
- **Databases:** SQLite (default), MySQL, PostgreSQL
|
|
- **Protocol:** Protocol Buffers (v2) with buf for code generation
|
|
- **API Layer:** Dual protocol - Connect RPC (browsers) + gRPC-Gateway (REST)
|
|
|
|
## Architecture
|
|
|
|
### Backend Architecture
|
|
|
|
```
|
|
cmd/memos/ # Entry point
|
|
└── main.go # Cobra CLI, profile setup, server initialization
|
|
|
|
server/
|
|
├── server.go # Echo HTTP server, healthz, background runners
|
|
├── auth/ # Authentication (JWT, PAT, session)
|
|
├── router/
|
|
│ ├── api/v1/ # gRPC service implementations
|
|
│ │ ├── v1.go # Service registration, gateway & Connect setup
|
|
│ │ ├── acl_config.go # Public endpoints whitelist
|
|
│ │ ├── connect_services.go # Connect RPC handlers
|
|
│ │ ├── connect_interceptors.go # Auth, logging, recovery
|
|
│ │ └── *_service.go # Individual services (memo, user, etc.)
|
|
│ ├── frontend/ # Static file serving (SPA)
|
|
│ ├── fileserver/ # Native HTTP file serving for media
|
|
│ └── rss/ # RSS feed generation
|
|
└── runner/
|
|
├── memopayload/ # Memo payload processing (tags, links, tasks)
|
|
└── s3presign/ # S3 presigned URL management
|
|
|
|
store/ # Data layer with caching
|
|
├── driver.go # Driver interface (database operations)
|
|
├── store.go # Store wrapper with cache layer
|
|
├── cache.go # In-memory caching (instance settings, users)
|
|
├── migrator.go # Database migrations
|
|
├── db/
|
|
│ ├── db.go # Driver factory
|
|
│ ├── sqlite/ # SQLite implementation
|
|
│ ├── mysql/ # MySQL implementation
|
|
│ └── postgres/ # PostgreSQL implementation
|
|
└── migration/ # SQL migration files (embedded)
|
|
|
|
proto/ # Protocol Buffer definitions
|
|
├── api/v1/ # API v1 service definitions
|
|
└── gen/ # Generated Go & TypeScript code
|
|
```
|
|
|
|
### Frontend Architecture
|
|
|
|
```
|
|
web/
|
|
├── src/
|
|
│ ├── components/ # React components
|
|
│ ├── contexts/ # React Context (client state)
|
|
│ │ ├── AuthContext.tsx # Current user, auth state
|
|
│ │ ├── ViewContext.tsx # Layout, sort order
|
|
│ │ └── MemoFilterContext.tsx # Filters, shortcuts
|
|
│ ├── hooks/ # React Query hooks (server state)
|
|
│ │ ├── useMemoQueries.ts # Memo CRUD, pagination
|
|
│ │ ├── useUserQueries.ts # User operations
|
|
│ │ ├── useAttachmentQueries.ts # Attachment operations
|
|
│ │ └── ...
|
|
│ ├── lib/ # Utilities
|
|
│ │ ├── query-client.ts # React Query v5 client
|
|
│ │ └── connect.ts # Connect RPC client setup
|
|
│ ├── pages/ # Page components
|
|
│ └── types/proto/ # Generated TypeScript from .proto
|
|
├── package.json # Dependencies
|
|
└── vite.config.mts # Vite config with dev proxy
|
|
|
|
plugin/ # Backend plugins
|
|
├── scheduler/ # Cron jobs
|
|
├── email/ # Email delivery
|
|
├── filter/ # CEL filter expressions
|
|
├── webhook/ # Webhook dispatch
|
|
├── markdown/ # Markdown parsing & rendering
|
|
├── httpgetter/ # HTTP fetching (metadata, images)
|
|
└── storage/s3/ # S3 storage backend
|
|
```
|
|
|
|
## Key Architectural Patterns
|
|
|
|
### 1. API Layer: Dual Protocol
|
|
|
|
**Connect RPC (Browser Clients):**
|
|
- Protocol: `connectrpc.com/connect`
|
|
- Base path: `/memos.api.v1.*`
|
|
- Interceptor chain: Metadata → Logging → Recovery → Auth
|
|
- Returns type-safe responses to React frontend
|
|
- See: `server/router/api/v1/connect_interceptors.go:177-227`
|
|
|
|
**gRPC-Gateway (REST API):**
|
|
- Protocol: Standard HTTP/JSON
|
|
- Base path: `/api/v1/*`
|
|
- Uses same service implementations as Connect
|
|
- Useful for external tools, CLI clients
|
|
- See: `server/router/api/v1/v1.go:52-96`
|
|
|
|
**Authentication:**
|
|
- JWT Access Tokens (V2): Stateless, 15-min expiration, verified via `AuthenticateByAccessTokenV2`
|
|
- Personal Access Tokens (PAT): Stateful, long-lived, validated against database
|
|
- Both use `Authorization: Bearer <token>` header
|
|
- See: `server/auth/authenticator.go:17-166`
|
|
|
|
### 2. Store Layer: Interface Pattern
|
|
|
|
All database operations go through the `Driver` interface:
|
|
```go
|
|
type Driver interface {
|
|
GetDB() *sql.DB
|
|
Close() error
|
|
|
|
IsInitialized(ctx context.Context) (bool, error)
|
|
|
|
CreateMemo(ctx context.Context, create *Memo) (*Memo, error)
|
|
ListMemos(ctx context.Context, find *FindMemo) ([]*Memo, error)
|
|
UpdateMemo(ctx context.Context, update *UpdateMemo) error
|
|
DeleteMemo(ctx context.Context, delete *DeleteMemo) error
|
|
|
|
// ... similar methods for all resources
|
|
}
|
|
```
|
|
|
|
**Three Implementations:**
|
|
- `store/db/sqlite/` - SQLite (modernc.org/sqlite)
|
|
- `store/db/mysql/` - MySQL (go-sql-driver/mysql)
|
|
- `store/db/postgres/` - PostgreSQL (lib/pq)
|
|
|
|
**Caching Strategy:**
|
|
- Store wrapper maintains in-memory caches for:
|
|
- Instance settings (`instanceSettingCache`)
|
|
- Users (`userCache`)
|
|
- User settings (`userSettingCache`)
|
|
- Config: Default TTL 10 min, cleanup interval 5 min, max 1000 items
|
|
- See: `store/store.go:10-57`
|
|
|
|
### 3. Frontend State Management
|
|
|
|
**React Query v5 (Server State):**
|
|
- All API calls go through custom hooks in `web/src/hooks/`
|
|
- Query keys organized by resource: `memoKeys`, `userKeys`, `attachmentKeys`
|
|
- Default staleTime: 30s, gcTime: 5min
|
|
- Automatic refetch on window focus, reconnect
|
|
- See: `web/src/lib/query-client.ts`
|
|
|
|
**React Context (Client State):**
|
|
- `AuthContext`: Current user, auth initialization, logout
|
|
- `ViewContext`: Layout mode (LIST/MASONRY), sort order
|
|
- `MemoFilterContext`: Active filters, shortcut selection, URL sync
|
|
|
|
### 4. Database Migration System
|
|
|
|
**Migration Flow:**
|
|
1. `preMigrate`: Check if DB exists. If not, apply `LATEST.sql`
|
|
2. `checkMinimumUpgradeVersion`: Reject pre-0.22 installations
|
|
3. `applyMigrations`: Apply incremental migrations in single transaction
|
|
4. Demo mode: Seed with demo data
|
|
|
|
**Schema Versioning:**
|
|
- Stored in `instance_setting` table (key: `bb.general.version`)
|
|
- Format: `major.minor.patch`
|
|
- Migration files: `store/migration/{driver}/{version}/NN__description.sql`
|
|
- See: `store/migrator.go:21-414`
|
|
|
|
### 5. Protocol Buffer Code Generation
|
|
|
|
**Definition Location:** `proto/api/v1/*.proto`
|
|
|
|
**Regeneration:**
|
|
```bash
|
|
cd proto && buf generate
|
|
```
|
|
|
|
**Generated Outputs:**
|
|
- Go: `proto/gen/api/v1/` (used by backend services)
|
|
- TypeScript: `web/src/types/proto/api/v1/` (used by frontend)
|
|
|
|
**Linting:** `proto/buf.yaml` - BASIC lint rules, FILE breaking changes
|
|
|
|
## Development Commands
|
|
|
|
### Backend
|
|
|
|
```bash
|
|
# Start dev server
|
|
go run ./cmd/memos --mode dev --port 8081
|
|
|
|
# Run all tests
|
|
go test ./...
|
|
|
|
# Run tests for specific package
|
|
go test ./store/...
|
|
go test ./server/router/api/v1/test/...
|
|
|
|
# Lint (golangci-lint)
|
|
golangci-lint run
|
|
|
|
# Format imports
|
|
goimports -w .
|
|
|
|
# Run with MySQL/Postgres
|
|
DRIVER=mysql go run ./cmd/memos
|
|
DRIVER=postgres go run ./cmd/memos
|
|
```
|
|
|
|
### Frontend
|
|
|
|
```bash
|
|
# Install dependencies
|
|
cd web && pnpm install
|
|
|
|
# Start dev server (proxies API to localhost:8081)
|
|
pnpm dev
|
|
|
|
# Type checking
|
|
pnpm lint
|
|
|
|
# Auto-fix lint issues
|
|
pnpm lint:fix
|
|
|
|
# Format code
|
|
pnpm format
|
|
|
|
# Build for production
|
|
pnpm build
|
|
|
|
# Build and copy to backend
|
|
pnpm release
|
|
```
|
|
|
|
### Protocol Buffers
|
|
|
|
```bash
|
|
# Regenerate Go and TypeScript from .proto files
|
|
cd proto && buf generate
|
|
|
|
# Lint proto files
|
|
cd proto && buf lint
|
|
|
|
# Check for breaking changes
|
|
cd proto && buf breaking --against .git#main
|
|
```
|
|
|
|
## Key Workflows
|
|
|
|
### Adding a New API Endpoint
|
|
|
|
1. **Define in Protocol Buffer:**
|
|
- Edit `proto/api/v1/*_service.proto`
|
|
- Add request/response messages
|
|
- Add RPC method to service
|
|
|
|
2. **Regenerate Code:**
|
|
```bash
|
|
cd proto && buf generate
|
|
```
|
|
|
|
3. **Implement Service (Backend):**
|
|
- Add method to `server/router/api/v1/*_service.go`
|
|
- Follow existing patterns: fetch user, validate, call store
|
|
- Add Connect wrapper to `server/router/api/v1/connect_services.go` (optional, same implementation)
|
|
|
|
4. **If Public Endpoint:**
|
|
- Add to `server/router/api/v1/acl_config.go:11-34`
|
|
|
|
5. **Create Frontend Hook (if needed):**
|
|
- Add query/mutation to `web/src/hooks/use*Queries.ts`
|
|
- Use existing query key factories
|
|
|
|
### Database Schema Changes
|
|
|
|
1. **Create Migration Files:**
|
|
```
|
|
store/migration/sqlite/0.28/1__add_new_column.sql
|
|
store/migration/mysql/0.28/1__add_new_column.sql
|
|
store/migration/postgres/0.28/1__add_new_column.sql
|
|
```
|
|
|
|
2. **Update LATEST.sql:**
|
|
- Add change to `store/migration/{driver}/LATEST.sql`
|
|
|
|
3. **Update Store Interface (if new table/model):**
|
|
- Add methods to `store/driver.go:8-71`
|
|
- Implement in `store/db/{driver}/*.go`
|
|
|
|
4. **Test Migration:**
|
|
- Run `go test ./store/test/...` to verify
|
|
|
|
### Adding a New Frontend Page
|
|
|
|
1. **Create Page Component:**
|
|
- Add to `web/src/pages/NewPage.tsx`
|
|
- Use existing hooks for data fetching
|
|
|
|
2. **Add Route:**
|
|
- Edit `web/src/App.tsx` (or router configuration)
|
|
|
|
3. **Use React Query:**
|
|
```typescript
|
|
import { useMemos } from "@/hooks/useMemoQueries";
|
|
const { data, isLoading } = useMemos({ filter: "..." });
|
|
```
|
|
|
|
4. **Use Context for Client State:**
|
|
```typescript
|
|
import { useView } from "@/contexts/ViewContext";
|
|
const { layout, toggleSortOrder } = useView();
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Backend Tests
|
|
|
|
**Test Pattern:**
|
|
```go
|
|
func TestMemoCreation(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := test.NewTestingStore(ctx, t)
|
|
|
|
// Create test user
|
|
user, _ := createTestUser(ctx, store, t)
|
|
|
|
// Execute operation
|
|
memo, err := store.CreateMemo(ctx, &store.Memo{
|
|
CreatorID: user.ID,
|
|
Content: "Test memo",
|
|
// ...
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, memo)
|
|
}
|
|
```
|
|
|
|
**Test Utilities:**
|
|
- `store/test/store.go:22-35` - `NewTestingStore()` creates isolated DB
|
|
- `store/test/store.go:37-77` - `resetTestingDB()` cleans tables
|
|
- Test DB determined by `DRIVER` env var (default: sqlite)
|
|
|
|
**Running Tests:**
|
|
```bash
|
|
# All tests
|
|
go test ./...
|
|
|
|
# Specific package
|
|
go test ./store/...
|
|
go test ./server/router/api/v1/test/...
|
|
|
|
# With coverage
|
|
go test -cover ./...
|
|
```
|
|
|
|
### Frontend Testing
|
|
|
|
**TypeScript Checking:**
|
|
```bash
|
|
cd web && pnpm lint
|
|
```
|
|
|
|
**No Automated Tests:**
|
|
- Frontend relies on TypeScript checking and manual validation
|
|
- React Query DevTools available in dev mode (bottom-left)
|
|
|
|
## Code Conventions
|
|
|
|
### Go
|
|
|
|
**Error Handling:**
|
|
- Use `github.com/pkg/errors` for wrapping: `errors.Wrap(err, "context")`
|
|
- Return structured gRPC errors: `status.Errorf(codes.NotFound, "message")`
|
|
|
|
**Naming:**
|
|
- Package names: lowercase, single word (e.g., `store`, `server`)
|
|
- Interfaces: `Driver`, `Store`, `Service`
|
|
- Methods: PascalCase for exported, camelCase for internal
|
|
|
|
**Comments:**
|
|
- Public exported functions must have comments (godot enforces)
|
|
- Use `//` for single-line, `/* */` for multi-line
|
|
|
|
**Imports:**
|
|
- Grouped: stdlib, third-party, local
|
|
- Sorted alphabetically within groups
|
|
- Use `goimports -w .` to format
|
|
|
|
### TypeScript/React
|
|
|
|
**Components:**
|
|
- Functional components with hooks
|
|
- Use `useMemo`, `useCallback` for optimization
|
|
- Props interfaces: `interface Props { ... }`
|
|
|
|
**State Management:**
|
|
- Server state: React Query hooks
|
|
- Client state: React Context
|
|
- Avoid direct useState for server data
|
|
|
|
**Styling:**
|
|
- Tailwind CSS v4 via `@tailwindcss/vite`
|
|
- Use `clsx` and `tailwind-merge` for conditional classes
|
|
|
|
**Imports:**
|
|
- Absolute imports with `@/` alias
|
|
- Group: React, third-party, local
|
|
- Auto-organized by Biome
|
|
|
|
## Important Files Reference
|
|
|
|
### Backend Entry Points
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `cmd/memos/main.go` | Server entry point, CLI setup |
|
|
| `server/server.go` | Echo server initialization, background runners |
|
|
| `store/store.go` | Store wrapper with caching |
|
|
| `store/driver.go` | Database driver interface |
|
|
|
|
### API Layer
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `server/router/api/v1/v1.go` | Service registration, gateway setup |
|
|
| `server/router/api/v1/acl_config.go` | Public endpoints whitelist |
|
|
| `server/router/api/v1/connect_interceptors.go` | Connect interceptors |
|
|
| `server/auth/authenticator.go` | Authentication logic |
|
|
|
|
### Frontend Core
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `web/src/lib/query-client.ts` | React Query client configuration |
|
|
| `web/src/contexts/AuthContext.tsx` | User authentication state |
|
|
| `web/src/contexts/ViewContext.tsx` | UI preferences |
|
|
| `web/src/contexts/MemoFilterContext.tsx` | Filter state |
|
|
| `web/src/hooks/useMemoQueries.ts` | Memo queries/mutations |
|
|
|
|
### Data Layer
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `store/memo.go` | Memo model definitions, store methods |
|
|
| `store/user.go` | User model definitions |
|
|
| `store/attachment.go` | Attachment model definitions |
|
|
| `store/migrator.go` | Migration logic |
|
|
| `store/db/db.go` | Driver factory |
|
|
| `store/db/sqlite/sqlite.go` | SQLite driver implementation |
|
|
|
|
## Configuration
|
|
|
|
### Backend Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|----------|----------|-------------|
|
|
| `MEMOS_MODE` | `dev` | Mode: `dev`, `prod`, `demo` |
|
|
| `MEMOS_PORT` | `8081` | HTTP port |
|
|
| `MEMOS_ADDR` | `` | Bind address (empty = all) |
|
|
| `MEMOS_DATA` | `~/.memos` | Data directory |
|
|
| `MEMOS_DRIVER` | `sqlite` | Database: `sqlite`, `mysql`, `postgres` |
|
|
| `MEMOS_DSN` | `` | Database connection string |
|
|
| `MEMOS_INSTANCE_URL` | `` | Instance base URL |
|
|
|
|
### Frontend Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|----------|----------|-------------|
|
|
| `DEV_PROXY_SERVER` | `http://localhost:8081` | Backend proxy target |
|
|
|
|
## CI/CD
|
|
|
|
### GitHub Workflows
|
|
|
|
**Backend Tests** (`.github/workflows/backend-tests.yml`):
|
|
- Runs on `go.mod`, `go.sum`, `**.go` changes
|
|
- Steps: verify `go mod tidy`, golangci-lint, all tests
|
|
|
|
**Frontend Tests** (`.github/workflows/frontend-tests.yml`):
|
|
- Runs on `web/**` changes
|
|
- Steps: pnpm install, lint, build
|
|
|
|
**Proto Lint** (`.github/workflows/proto-linter.yml`):
|
|
- Runs on `.proto` changes
|
|
- Steps: buf lint, buf breaking check
|
|
|
|
### Linting Configuration
|
|
|
|
**Go** (`.golangci.yaml`):
|
|
- Linters: revive, govet, staticcheck, misspell, gocritic, etc.
|
|
- Formatter: goimports
|
|
- Forbidden: `fmt.Errorf`, `ioutil.ReadDir`
|
|
|
|
**TypeScript** (`web/biome.json`):
|
|
- Linting: Biome (ESLint replacement)
|
|
- Formatting: Biome (Prettier replacement)
|
|
- Line width: 140 characters
|
|
- Semicolons: always
|
|
|
|
## Common Tasks
|
|
|
|
### Debugging Database Issues
|
|
|
|
1. Check connection string in logs
|
|
2. Verify `store/db/{driver}/migration/` files exist
|
|
3. Check schema version: `SELECT * FROM instance_setting WHERE key = 'bb.general.version'`
|
|
4. Test migration: `go test ./store/test/... -v`
|
|
|
|
### Debugging API Issues
|
|
|
|
1. Check Connect interceptor logs: `server/router/api/v1/connect_interceptors.go:79-105`
|
|
2. Verify endpoint is in `acl_config.go` if public
|
|
3. Check authentication via `auth/authenticator.go:133-165`
|
|
4. Test with curl: `curl -H "Authorization: Bearer <token>" http://localhost:8081/api/v1/...`
|
|
|
|
### Debugging Frontend State
|
|
|
|
1. Open React Query DevTools (bottom-left in dev)
|
|
2. Inspect query cache, mutations, refetch behavior
|
|
3. Check Context state via React DevTools
|
|
4. Verify filter state in MemoFilterContext
|
|
|
|
### Running Tests Against Multiple Databases
|
|
|
|
```bash
|
|
# SQLite (default)
|
|
DRIVER=sqlite go test ./...
|
|
|
|
# MySQL (requires running MySQL server)
|
|
DRIVER=mysql DSN="user:pass@tcp(localhost:3306)/memos" go test ./...
|
|
|
|
# PostgreSQL (requires running PostgreSQL server)
|
|
DRIVER=postgres DSN="postgres://user:pass@localhost:5432/memos" go test ./...
|
|
```
|
|
|
|
## Plugin System
|
|
|
|
Backend supports pluggable components in `plugin/`:
|
|
|
|
| Plugin | Purpose |
|
|
|--------|----------|
|
|
| `scheduler` | Cron-based job scheduling |
|
|
| `email` | SMTP email delivery |
|
|
| `filter` | CEL expression filtering |
|
|
| `webhook` | HTTP webhook dispatch |
|
|
| `markdown` | Markdown parsing (goldmark) |
|
|
| `httpgetter` | HTTP content fetching |
|
|
| `storage/s3` | S3-compatible storage |
|
|
|
|
Each plugin has its own README with usage examples.
|
|
|
|
## Performance Considerations
|
|
|
|
### Backend
|
|
|
|
- Database queries use pagination (`limit`, `offset`)
|
|
- In-memory caching reduces DB hits for frequently accessed data
|
|
- WAL journal mode for SQLite (reduces locking)
|
|
- Thumbnail generation limited to 3 concurrent operations
|
|
|
|
### Frontend
|
|
|
|
- React Query reduces redundant API calls
|
|
- Infinite queries for large lists (pagination)
|
|
- Manual chunks: `utils-vendor`, `mermaid-vendor`, `leaflet-vendor`
|
|
- Lazy loading for heavy components
|
|
|
|
## Security Notes
|
|
|
|
- JWT secrets must be kept secret (`MEMOS_MODE=prod` generates random secret)
|
|
- Personal Access Tokens stored as SHA-256 hashes in database
|
|
- CSRF protection via SameSite cookies
|
|
- CORS enabled for all origins (configure for production)
|
|
- Input validation at service layer
|
|
- SQL injection prevention via parameterized queries
|