195 lines
5.7 KiB
Markdown
195 lines
5.7 KiB
Markdown
# Coding Standards: zhealth Monorepo
|
|
|
|
## Core Principles
|
|
|
|
1. **Modularity First** — Every component should be independent and reusable.
|
|
2. **Self-Documenting Structure** — The folder tree and file names should explain purpose without needing comments.
|
|
3. **Small Files** — Max **200 lines** per file. A function belongs in a file **only if its purpose is obvious from the filename**. When in doubt, create a separate file.
|
|
|
|
---
|
|
|
|
## File & Folder Naming
|
|
|
|
| Type | Convention | Example |
|
|
|------|------------|---------|
|
|
| Folders | `snake_case` | `auth_handlers/`, `biomarker_utils/` |
|
|
| Rust files | `snake_case.rs` | `user_service.rs`, `jwt_middleware.rs` |
|
|
| TypeScript/React files | `PascalCase.tsx` for components | `Dashboard.tsx`, `BiomarkerCard.tsx` |
|
|
| TypeScript utilities | `camelCase.ts` | `apiClient.ts`, `formatDate.ts` |
|
|
| Test files | `*.test.ts` / `*_test.rs` | `auth.test.ts`, `user_service_test.rs` |
|
|
| Config files | `lowercase` with dots | `vite.config.ts`, `.env.local` |
|
|
|
|
---
|
|
|
|
## Folder Structure (Monorepo)
|
|
|
|
```
|
|
zhealth/
|
|
├── PROJECT_MGMT/ # Project planning & docs
|
|
├── backend/ # Rust API
|
|
│ ├── src/
|
|
│ │ ├── handlers/ # Route handlers (one file per resource)
|
|
│ │ ├── models/ # SeaORM entities
|
|
│ │ ├── services/ # Business logic
|
|
│ │ ├── middleware/ # Auth, logging, etc.
|
|
│ │ ├── utils/ # Shared helpers
|
|
│ │ └── main.rs
|
|
│ └── Cargo.toml
|
|
├── frontend/ # React + Vite
|
|
│ ├── src/
|
|
│ │ ├── components/ # Reusable UI components
|
|
│ │ ├── pages/ # Route-level components
|
|
│ │ ├── hooks/ # Custom React hooks
|
|
│ │ ├── services/ # API calls
|
|
│ │ ├── utils/ # Helper functions
|
|
│ │ └── App.tsx
|
|
│ └── package.json
|
|
└── shared/ # Shared types/constants (if needed)
|
|
```
|
|
|
|
---
|
|
|
|
## Makefile as Command Runner
|
|
|
|
All common tasks are executed through the root **Makefile**. Do not use raw `cargo`, `npm`, or tool-specific commands directly — always use `make`.
|
|
|
|
| Command | Purpose |
|
|
|---------|---------|
|
|
| `make lint` | Run linters (Clippy + ESLint) |
|
|
| `make typecheck` | Type checking (Rust + TypeScript) |
|
|
| `make dev` | Start dev servers (backend + frontend) |
|
|
| `make build` | Build for development |
|
|
| `make release` | Build optimized production bundle |
|
|
| `make serve` | Serve production build locally |
|
|
| `make test` | Run all tests |
|
|
| `make clean` | Clean build artifacts |
|
|
|
|
### Why Makefile?
|
|
- **Single source of truth** for all commands
|
|
- **Works across all platforms** (unlike npm scripts for Rust)
|
|
- **Self-documenting** — run `make help` to see available targets
|
|
- **Composable** — targets can depend on each other
|
|
|
|
---
|
|
|
|
## Configuration (`config.yaml`)
|
|
|
|
All backend configuration is centralized in a **`config.yaml`** file at the backend root. Never hardcode paths, ports, or environment-specific values.
|
|
|
|
### Required Config Keys
|
|
|
|
```yaml
|
|
# backend/config.yaml
|
|
|
|
server:
|
|
host: "0.0.0.0"
|
|
port: 3000
|
|
|
|
paths:
|
|
database: "./data/zhealth.db"
|
|
logs: "./logs"
|
|
|
|
logging:
|
|
level: "info" # trace | debug | info | warn | error
|
|
|
|
auth:
|
|
session_secret: "${SESSION_SECRET}" # Loaded from env
|
|
session_expiry_hours: 24
|
|
cookie_name: "zhealth_session"
|
|
|
|
ai:
|
|
provider: "gemini" # gemini | openai | anthropic
|
|
model: "gemini-3-flash-preview"
|
|
api_key: "${AI_API_KEY}"
|
|
```
|
|
|
|
### Rules
|
|
- **Secrets via env vars** — Use `${VAR_NAME}` syntax for sensitive values
|
|
- **Paths are relative** to the backend folder (or absolute if needed)
|
|
- **No hardcoding** — If it might change across environments, put it in config
|
|
|
|
---
|
|
|
|
## File Size Rules
|
|
|
|
| Rule | Action |
|
|
|------|--------|
|
|
| File > 200 lines | Split into smaller modules |
|
|
| Function > 50 lines | Extract to helper or break into sub-functions |
|
|
| Single responsibility | One file = one purpose |
|
|
|
|
### Example: Splitting a Large Handler
|
|
|
|
❌ **Bad**: `handlers/biomarkers.rs` with 400 lines
|
|
|
|
✅ **Good**:
|
|
```
|
|
handlers/
|
|
├── biomarkers/
|
|
│ ├── mod.rs # Re-exports
|
|
│ ├── create.rs # POST /biomarkers
|
|
│ ├── read.rs # GET /biomarkers
|
|
│ ├── update.rs # PUT /biomarkers/:id
|
|
│ └── delete.rs # DELETE /biomarkers/:id
|
|
```
|
|
|
|
---
|
|
|
|
## Import & Module Organization
|
|
|
|
### Rust
|
|
```rust
|
|
// 1. Standard library
|
|
use std::collections::HashMap;
|
|
|
|
// 2. External crates
|
|
use axum::{Router, Json};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
// 3. Internal modules
|
|
use crate::models::User;
|
|
use crate::services::auth_service;
|
|
```
|
|
|
|
### TypeScript
|
|
```typescript
|
|
// 1. React/Framework
|
|
import { useState, useEffect } from 'react';
|
|
|
|
// 2. External packages
|
|
import axios from 'axios';
|
|
|
|
// 3. Internal components/utils
|
|
import { BiomarkerCard } from '@/components/BiomarkerCard';
|
|
import { formatDate } from '@/utils/formatDate';
|
|
```
|
|
|
|
---
|
|
|
|
## Commit & Branch Naming
|
|
|
|
| Type | Format | Example |
|
|
|------|--------|---------|
|
|
| Branch | `type/short-description` | `feat/biomarker-crud`, `fix/auth-token-expiry` |
|
|
| Commit | `type: short description` | `feat: add biomarker create endpoint` |
|
|
|
|
**Types**: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`
|
|
|
|
---
|
|
|
|
## Code Comments
|
|
|
|
- **Avoid obvious comments** — Code should be self-explanatory.
|
|
- **Document "why", not "what"** — Explain non-obvious decisions.
|
|
- **Use doc comments** for public APIs:
|
|
- Rust: `///` for functions, `//!` for modules
|
|
- TypeScript: JSDoc `/** */` for exported functions
|
|
|
|
---
|
|
|
|
## Enforcement
|
|
|
|
- [ ] Pre-commit hooks for linting (Clippy for Rust, ESLint for TS)
|
|
- [ ] CI pipeline checks for file size violations
|
|
- [ ] PR reviews must verify modularity standards
|