Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:29:15 +08:00
commit be476a3fea
76 changed files with 12812 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,586 @@
# Technical Reference
Complete configuration files and detailed technical information for Grey Haven coding standards.
## TypeScript/React Configuration Files
### .prettierrc
Complete Prettier configuration from cvi-template:
```json
{
"tabWidth": 2,
"semi": true,
"printWidth": 90,
"singleQuote": false,
"endOfLine": "lf",
"trailingComma": "all",
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
}
```
**Field Explanations:**
- `tabWidth: 2` - Use 2 spaces for indentation (NOT 4)
- `semi: true` - Always add semicolons at the end of statements
- `printWidth: 90` - Wrap lines at 90 characters (NOT 80 or 120)
- `singleQuote: false` - Use double quotes for strings
- `endOfLine: "lf"` - Use Unix-style line endings (\\n)
- `trailingComma: "all"` - Add trailing commas wherever possible
- `prettier-plugin-organize-imports` - Auto-organize imports by type
- `prettier-plugin-tailwindcss` - Auto-sort Tailwind CSS classes
### .eslintrc
Complete ESLint configuration from cvi-template:
```json
{
"root": true,
"env": { "browser": true, "es2020": true },
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended"
],
"parser": "@typescript-eslint/parser",
"plugins": ["react-refresh"],
"rules": {
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"no-unused-vars": "off",
"react-refresh/only-export-components": "off"
}
}
```
**Rule Explanations:**
- `react-hooks/exhaustive-deps: "off"` - Don't enforce exhaustive deps in useEffect (manage manually)
- `@typescript-eslint/no-explicit-any: "off"` - Allow `any` type for flexibility
- `@typescript-eslint/no-unused-vars: "off"` - Don't error on unused variables (clean up manually)
- `no-unused-vars: "off"` - Same as above for non-TypeScript files
- `react-refresh/only-export-components: "off"` - Allow exporting non-components from module
**Philosophy:** Grey Haven takes a pragmatic approach over pedantic enforcement. The codebase values developer velocity and allows `any` types, unused variables, and manual dependency management when appropriate.
### tsconfig.json
Complete TypeScript configuration from cvi-template:
```json
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"esModuleInterop": true,
"isolatedModules": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
},
"noEmit": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", ".wrangler", ".output"]
}
```
**Key Settings:**
- `strict: true` - Enable all strict type checking options
- `target: "ES2022"` - Compile to ES2022 JavaScript
- `module: "ESNext"` - Use ESNext module syntax
- `moduleResolution: "Bundler"` - Use bundler resolution for Vite/TanStack
- `jsx: "react-jsx"` - Use React 17+ JSX transform
- `paths: { "~/*": ["./src/*"] }` - Path alias for imports (e.g., `import { foo } from "~/lib/utils"`)
- `noEmit: true` - Don't emit compiled files (Vite handles this)
### package.json Scripts
Standard scripts from cvi-template:
```json
{
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start",
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"prepare": "husky"
}
}
```
## Python/FastAPI Configuration Files
### pyproject.toml (Ruff)
Complete Ruff configuration from cvi-backend-template:
```toml
[tool.ruff]
fix-only = true
show-fixes = true
indent-width = 4
line-length = 130 # NOT 80 or 88!
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"
[tool.ruff.lint.isort]
known-first-party = ["app"]
```
**Field Explanations:**
- `line-length = 130` - Wrap lines at 130 characters (CRITICAL: NOT 80 or 88!)
- `indent-width = 4` - Use 4 spaces for indentation
- `fix-only = true` - Auto-fix issues without showing unfixable errors
- `show-fixes = true` - Show what was fixed
- `select = [...]` - Enable specific linter rules:
- `E`, `W` - pycodestyle style enforcement
- `F` - pyflakes error detection
- `I` - isort import sorting
- `B` - flake8-bugbear bug detection
- `C4` - flake8-comprehensions list/dict comprehension improvements
- `UP` - pyupgrade automatic Python version upgrades
### pyproject.toml (MyPy)
Complete MyPy configuration from cvi-backend-template:
```toml
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true
ignore_missing_imports = false
strict_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
```
**Key Settings:**
- `disallow_untyped_defs: true` - **CRITICAL**: Require type hints on all function definitions
- `python_version = "3.11"` - Target Python 3.11+ (projects use 3.12+)
- `strict_optional: true` - Strict checking of Optional types
- `warn_return_any: true` - Warn when returning Any from typed functions
- `warn_unused_configs: true` - Warn about unused mypy config
**Philosophy:** Type hints are **required** in Grey Haven Python projects. Unlike TypeScript's relaxed rules, Python enforces strict typing.
### pyproject.toml (Pytest)
Complete pytest configuration from cvi-backend-template:
```toml
[tool.pytest.ini_options]
pythonpath = ["."]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
markers = [
"unit: Unit tests (fast, isolated, mocked)",
"integration: Integration tests (database, external services)",
"e2e: End-to-end tests (full system)",
"benchmark: Performance benchmark tests",
"slow: Tests that take > 1 second",
]
addopts = [
"-ra", # Show summary of all test outcomes
"--strict-markers", # Require markers to be registered
"--strict-config", # Raise on config warnings
"--tb=short", # Shorter traceback format
]
[tool.coverage.run]
source = ["app"]
omit = ["tests/*", "*/migrations/*"]
[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
```
**Marker Explanations:**
- `@pytest.mark.unit` - Fast, isolated tests with mocked dependencies
- `@pytest.mark.integration` - Tests with database or external services
- `@pytest.mark.e2e` - Full system end-to-end tests
- `@pytest.mark.benchmark` - Performance measurement tests
- `@pytest.mark.slow` - Tests taking over 1 second
**Coverage:** Aim for >80% code coverage across the project.
## Project Structure Details
### TypeScript/React Structure (cvi-template)
```plaintext
cvi-template/
├── src/
│ ├── routes/ # TanStack Router file-based routing
│ │ ├── __root.tsx # Root layout
│ │ ├── index.tsx # Homepage (/)
│ │ ├── _authenticated/ # Protected routes (requires auth)
│ │ │ └── dashboard.tsx # /dashboard
│ │ └── settings/
│ │ ├── index.tsx # /settings
│ │ └── profile.tsx # /settings/profile
│ │
│ ├── lib/
│ │ ├── components/ # React components
│ │ │ ├── ui/ # Shadcn UI components (button, card, etc.)
│ │ │ ├── auth/ # Authentication components
│ │ │ └── layout/ # Layout components (header, sidebar)
│ │ │
│ │ ├── server/ # Server-side code
│ │ │ ├── schema/ # Drizzle database schemas (snake_case!)
│ │ │ ├── functions/ # TanStack Start server functions
│ │ │ ├── auth.ts # Better-auth configuration
│ │ │ └── db.ts # Database connection with RLS
│ │ │
│ │ ├── config/ # Configuration files
│ │ │ └── env.ts # Environment validation (@t3-oss/env-core)
│ │ │
│ │ ├── hooks/ # Custom React hooks (use-* naming)
│ │ ├── utils/ # Utility functions
│ │ └── types/ # TypeScript type definitions
│ │
│ ├── clients/ # API client code
│ ├── data/ # Zod schemas for data validation
│ ├── middleware/ # Route middleware and guards
│ ├── services/ # Business logic services
│ ├── workers/ # Cloudflare Workers code
│ ├── index.css # Global styles (Tailwind)
│ └── router.tsx # Router configuration
├── public/ # Static assets
├── tests/ # Test files
├── .prettierrc # Prettier config
├── .eslintrc # ESLint config
├── tsconfig.json # TypeScript config
├── drizzle.config.ts # Drizzle ORM config
├── vite.config.ts # Vite bundler config
├── wrangler.jsonc # Cloudflare Workers config
└── package.json # Dependencies and scripts
```
### Python/FastAPI Structure (cvi-backend-template)
```plaintext
cvi-backend-template/
├── app/
│ ├── config/ # Application configuration
│ │ └── settings.py # Pydantic settings with env vars
│ │
│ ├── db/ # Database layer
│ │ ├── models/ # SQLModel database models (snake_case!)
│ │ │ ├── account.py # Account model
│ │ │ ├── tenant.py # Tenant model (multi-tenant)
│ │ │ └── user.py # User model
│ │ │
│ │ ├── repositories/ # Repository pattern (data access)
│ │ │ ├── base.py # Base repository
│ │ │ ├── account_repository.py
│ │ │ ├── tenant_repository.py
│ │ │ └── user_repository.py
│ │ │
│ │ ├── db_types.py # Custom database types (UTCDateTime)
│ │ └── session.py # Database session management
│ │
│ ├── routers/ # FastAPI routers (endpoints)
│ │ ├── accounts.py # /accounts endpoints
│ │ ├── tenants.py # /tenants endpoints
│ │ └── users.py # /users endpoints
│ │
│ ├── services/ # Business logic layer
│ │ ├── auth_service.py # Authentication service
│ │ └── billing_service.py # Billing service
│ │
│ ├── schemas/ # Pydantic schemas (API contracts)
│ │ ├── accounts.py # Account request/response schemas
│ │ ├── tenants.py # Tenant request/response schemas
│ │ └── users.py # User request/response schemas
│ │
│ ├── utils/ # Utility functions
│ │ ├── logging.py # Logging configuration
│ │ └── security.py # Security utilities
│ │
│ ├── dependencies.py # FastAPI dependencies
│ └── main.py # FastAPI app entry point
├── tests/ # Test files
│ ├── unit/ # Unit tests (@pytest.mark.unit)
│ ├── integration/ # Integration tests (@pytest.mark.integration)
│ ├── e2e/ # E2E tests (@pytest.mark.e2e)
│ ├── conftest.py # Pytest fixtures
│ └── __init__.py
├── alembic/ # Database migrations (if using Alembic)
├── pyproject.toml # Python project config (Ruff, MyPy, pytest)
├── requirements.txt # Python dependencies
└── .env.example # Example environment variables
```
## Database Naming Standards
### Field Naming Rules
**ALWAYS use snake_case for database columns:**
```text
✅ CORRECT ❌ WRONG
created_at createdAt
tenant_id tenantId
email_address emailAddress
is_active isActive
first_name firstName
last_name lastName
phone_number phoneNumber
billing_tier billingTier
max_retries maxRetries
api_key_hash apiKeyHash
```
### Table Naming Rules
**Use lowercase plural names:**
```text
✅ CORRECT ❌ WRONG
users User, Users, user
accounts Account, ACCOUNTS
tenants Tenant, Tenants
organizations Organization
subscriptions Subscription
```
### Index Naming Rules
**Use descriptive index names:**
```text
Format: {table}_{column}_idx
✅ CORRECT ❌ WRONG
user_email_idx idx_1, email_index
user_tenant_id_idx tenant_idx
organization_slug_idx slug
```
### Foreign Key Naming Rules
**Reference the parent table:**
```text
Format: {parent_table}_{singular}_id
✅ CORRECT ❌ WRONG
tenant_id tenant
account_id acc_id, accountId
organization_id org_id, orgId
user_id userId, uid
```
## Multi-Tenant Architecture
### Tenant Isolation Levels
Grey Haven projects use **row-level** tenant isolation:
1. **Every table** includes a `tenant_id` or `account_id` field
2. **Every query** filters by tenant ID
3. **Row Level Security (RLS)** policies enforce tenant boundaries
4. **Repository pattern** centralizes tenant filtering
### RLS Policy Pattern
Standard RLS policy structure:
```typescript
// TypeScript/Drizzle RLS helper
export const inSameTenant = (tenantIdCol: AnyPgColumn, query: SQL): SQL =>
sql`${isSameTenant(tenantIdCol)} and (${query})`;
// Apply to table
pgPolicy("table_name_select", {
for: "select",
to: "public",
using: inSameTenant(table.tenantId, sql`true`),
});
```
```sql
-- SQL RLS Policy
CREATE POLICY tenant_isolation_policy ON users
USING (tenant_id = current_setting('app.tenant_id')::text);
```
### Repository Tenant Filtering
Every repository method filters by tenant:
```python
# Python repository pattern
async def get_by_id(self, id: UUID, tenant_id: UUID) -> Optional[Model]:
"""Get record by ID with tenant isolation."""
result = await self.session.execute(
select(self.model)
.where(self.model.id == id)
.where(self.model.tenant_id == tenant_id) # Tenant filter!
)
return result.scalar_one_or_none()
```
## Import Organization
### TypeScript Import Order
Auto-organized by `prettier-plugin-organize-imports`:
```typescript
// 1. External libraries
import { useState, useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
// 2. Internal modules (path alias)
import { Button } from "~/lib/components/ui/button";
import { useAuth } from "~/lib/hooks/use-auth";
import { env } from "~/lib/config/env";
// 3. Relative imports
import { helpers } from "./helpers";
```
### Python Import Order
Auto-organized by Ruff `isort`:
```python
# 1. Standard library imports
import os
from datetime import datetime
from typing import Optional
# 2. Third-party imports
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import select
from pydantic import BaseModel
# 3. Local imports (app.*)
from app.db.models.user import User
from app.db.repositories.user_repository import UserRepository
from app.schemas.user import UserCreate, UserResponse
```
## Line Length Reasoning
### TypeScript: 90 Characters
**Why 90?**
- Comfortable reading width on modern displays
- Allows two editor panes side-by-side
- Balances readability with code density
- TailwindCSS classes can be long - 90 gives room
- Standard 80 is too restrictive for modern development
### Python: 130 Characters
**Why 130?**
- Type hints can be verbose: `Annotated[AccountRepository, Depends(get_account_repository)]`
- Pydantic field definitions: `Field(default=None, description="Long description here")`
- Allows descriptive variable names without constant wrapping
- FastAPI decorators are long: `@router.post("/endpoint", response_model=ResponseSchema)`
- PEP 8's 79 is outdated for modern development
## Pre-commit Hooks
Both templates use pre-commit hooks for code quality:
### TypeScript Pre-commit
```bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Run linting
npm run lint
# Run formatting check
npm run format:check
# Run type checking
npx tsc --noEmit
```
### Python Pre-commit
```bash
#!/usr/bin/env sh
# Activate virtual environment
source .venv/bin/activate
# Run Ruff formatter
ruff format .
# Run Ruff linter
ruff check --fix .
# Run MyPy type checking
mypy app/
# Run tests
pytest -m "not slow"
```
## Additional Resources
- **Frontend Template**: [cvi-template](https://github.com/greyhaven-ai/cvi-template) - TanStack Start + React 19 + Drizzle
- **Backend Template**: [cvi-backend-template](https://github.com/greyhaven-ai/cvi-backend-template) - FastAPI + SQLModel + PostgreSQL
- **Prettier Docs**: https://prettier.io/docs/en/configuration.html
- **ESLint Docs**: https://eslint.org/docs/latest/use/configure/
- **Ruff Docs**: https://docs.astral.sh/ruff/
- **Drizzle ORM**: https://orm.drizzle.team/docs/overview
- **TanStack Start**: https://tanstack.com/router/latest/docs/framework/react/start/overview

199
skills/code-style/SKILL.md Normal file
View File

@@ -0,0 +1,199 @@
---
name: grey-haven-code-style
description: "Apply Grey Haven Studio's TypeScript/React and Python/FastAPI coding standards from production templates. Use when writing code, reviewing PRs, fixing linting errors, formatting files, or when the user mentions 'code standards', 'Grey Haven style', 'linting', 'Prettier', 'ESLint', 'Ruff', 'formatting rules', or 'coding conventions'. Includes exact Prettier/ESLint/Ruff configs, naming conventions, project structure, and multi-tenant database patterns."
---
# Grey Haven Code Style Standards
**Actual coding standards from Grey Haven Studio production templates.**
Follow these exactly when working on Grey Haven codebases. This skill provides navigation to detailed examples, reference configs, and templates.
## Supporting Documentation
- **[EXAMPLES.md](EXAMPLES.md)** - Copy-paste code examples for TypeScript and Python
- **[REFERENCE.md](REFERENCE.md)** - Complete config files and detailed rule explanations
- **[templates/](templates/)** - Ready-to-use starter files
- **[checklists/](checklists/)** - Code review checklists
## Quick Reference
### TypeScript/React (Frontend)
Based on `cvi-template` - TanStack Start + React 19
**Key Settings:**
- **Line width:** 90 characters
- **Tab width:** 2 spaces
- **Quotes:** Double quotes
- **Semicolons:** Required
- **Trailing commas:** Always
- **ESLint:** Pragmatic (allows `any`, unused vars)
- **Path alias:** `~/` maps to `./src/*`
**Naming Conventions:**
- Variables/Functions: `camelCase` (`getUserData`, `isAuthenticated`)
- Components: `PascalCase` (`UserProfile`, `AuthProvider`)
- Constants: `UPPER_SNAKE_CASE` (`API_BASE_URL`, `MAX_RETRIES`)
- Types/Interfaces: `PascalCase` (`User`, `AuthConfig`)
- **Database fields:** `snake_case` (`user_id`, `created_at`, `tenant_id`) ⚠️ CRITICAL
**Project Structure:**
```plaintext
src/
├── routes/ # File-based routing (TanStack Router)
├── lib/
│ ├── components/ # UI components (grouped by feature)
│ ├── server/ # Server functions and DB schema
│ ├── config/ # Environment validation
│ ├── hooks/ # Custom React hooks (use-* naming)
│ ├── utils/ # Utility functions
│ └── types/ # TypeScript definitions
└── public/ # Static assets
```
### Python/FastAPI (Backend)
Based on `cvi-backend-template` - FastAPI + SQLModel
**Key Settings:**
- **Line length:** 130 characters
- **Indent:** 4 spaces
- **Type hints:** Required on all functions
- **Auto-fix:** Ruff fixes issues automatically
**Naming Conventions:**
- Functions/Variables: `snake_case` (`get_user_data`, `is_authenticated`)
- Classes: `PascalCase` (`UserRepository`, `AuthService`)
- Constants: `UPPER_SNAKE_CASE` (`API_BASE_URL`, `MAX_RETRIES`)
- **Database fields:** `snake_case` (`user_id`, `created_at`, `tenant_id`) ⚠️ CRITICAL
- Boolean fields: Prefix with `is_` or `has_` (`is_active`, `has_access`)
**Project Structure:**
```plaintext
app/
├── config/ # Application settings
├── db/
│ ├── models/ # SQLModel entities
│ └── repositories/ # Repository pattern (tenant isolation)
├── routers/ # FastAPI endpoints
├── services/ # Business logic
├── schemas/ # Pydantic models (API contracts)
└── utils/ # Utilities
```
## Database Field Convention (CRITICAL)
**ALWAYS use `snake_case` for database column names** - this is non-negotiable in Grey Haven projects.
**Correct:**
```typescript
// TypeScript - Drizzle schema
export const users = pgTable("users", {
id: uuid("id").primaryKey(),
created_at: timestamp("created_at").defaultNow(),
tenant_id: uuid("tenant_id").notNull(),
email_address: text("email_address").notNull(),
is_active: boolean("is_active").default(true),
});
```
```python
# Python - SQLModel
class User(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
created_at: datetime = Field(default_factory=datetime.utcnow)
tenant_id: UUID = Field(foreign_key="tenants.id", index=True)
email_address: str = Field(unique=True, index=True)
is_active: bool = Field(default=True)
```
**Wrong:**
```typescript
// DON'T use camelCase in database schemas
export const users = pgTable("users", {
id: uuid("id"),
createdAt: timestamp("createdAt"), // WRONG!
tenantId: uuid("tenantId"), // WRONG!
emailAddress: text("emailAddress"), // WRONG!
});
```
**See [EXAMPLES.md](EXAMPLES.md#database-schemas) for complete examples.**
## Multi-Tenant Architecture
**Every database table must include tenant isolation:**
- **Field name:** `tenant_id` (snake_case in DB) or `tenantId` (camelCase in TypeScript code)
- **Type:** UUID foreign key to tenants table
- **Index:** Always indexed for query performance
- **RLS:** Use Row Level Security policies for tenant isolation
- **Repository pattern:** All queries filter by `tenant_id`
**See [EXAMPLES.md](EXAMPLES.md#multi-tenant-patterns) for implementation patterns.**
## Virtual Environment (Python Projects)
**⚠️ ALWAYS activate virtual environment before running Python commands:**
```bash
source .venv/bin/activate
```
Required for:
- Running tests (`pytest`)
- Running pre-commit hooks
- Using task commands (`task test`, `task format`)
- Any Python script execution
## When to Apply This Skill
Use this skill when:
- ✅ Writing new TypeScript/React or Python/FastAPI code
- ✅ Reviewing code in pull requests
- ✅ Fixing linting or formatting errors
- ✅ Setting up new projects from templates
- ✅ Configuring Prettier, ESLint, or Ruff
- ✅ Creating database schemas
- ✅ Implementing multi-tenant features
- ✅ User mentions: "code standards", "linting rules", "Grey Haven style", "formatting"
## Template References
These standards come from actual Grey Haven production templates:
- **Frontend:** `cvi-template` (TanStack Start + React 19 + Drizzle)
- **Backend:** `cvi-backend-template` (FastAPI + SQLModel + PostgreSQL)
When in doubt, reference these templates for patterns and configurations.
## Critical Reminders
1. **Line lengths:** TypeScript=90, Python=130 (NOT 80/88)
2. **Database fields:** ALWAYS `snake_case` (both TypeScript and Python schemas)
3. **`any` type:** ALLOWED in Grey Haven TypeScript (pragmatic approach)
4. **Double quotes:** TypeScript uses double quotes (`singleQuote: false`)
5. **Type hints:** REQUIRED in Python (`disallow_untyped_defs: true`)
6. **Virtual env:** MUST activate before Python commands
7. **Multi-tenant:** Every table has `tenant_id`/`tenantId`
8. **Path aliases:** Use `~/` for TypeScript imports from `src/`
9. **Trailing commas:** ALWAYS in TypeScript (`trailingComma: "all"`)
10. **Pre-commit hooks:** Run before every commit (both projects)
## Next Steps
- **Need examples?** See [EXAMPLES.md](EXAMPLES.md) for copy-paste code
- **Need configs?** See [REFERENCE.md](REFERENCE.md) for complete config files
- **Need templates?** See [templates/](templates/) for starter files
- **Reviewing code?** Use [checklists/](checklists/) for systematic reviews

View File

@@ -0,0 +1,141 @@
# Python/FastAPI Code Review Checklist
Use this checklist when reviewing Python/FastAPI code or creating pull requests for Grey Haven projects.
## Formatting & Style
- [ ] **Line length**: Code lines do not exceed 130 characters (CRITICAL!)
- [ ] **Indentation**: Uses 4 spaces (not tabs or 2 spaces)
- [ ] **Quotes**: Uses double quotes for strings (Ruff default)
- [ ] **Line endings**: Uses Unix-style line endings (\\n)
- [ ] **Ruff formatted**: Code is formatted with Ruff (`ruff format .`)
- [ ] **Ruff linting**: No Ruff linting errors (`ruff check .`)
## Type Hints
- [ ] **Function signatures**: ALL functions have type hints (CRITICAL!)
- Parameters: `def get_user(user_id: str) -> Optional[User]:`
- Return types: Always include return type annotation
- [ ] **MyPy passing**: `mypy app/` has no type errors
- [ ] **Optional types**: Uses `Optional[T]` or `T | None` for nullable values
- [ ] **Generic types**: Uses proper generic types (`list[str]`, `dict[str, Any]`)
- [ ] **Type imports**: Imports types from `typing` module
## Database Models (SQLModel)
- [ ] **snake_case fields**: ALL database column names use snake_case (CRITICAL!)
-`created_at`, `tenant_id`, `email_address`, `is_active`
-`createdAt`, `tenantId`, `emailAddress`, `isActive`
- [ ] **Multi-tenant**: Models include `tenant_id` field
- [ ] **Field descriptions**: All fields have `description` parameter
- [ ] **Indexes**: Frequently queried fields have `index=True`
- [ ] **Constraints**: Foreign keys, unique constraints properly defined
- [ ] **Timestamps**: Uses UTC datetime (`UTCDateTime` type if available)
- [ ] **Table names**: Uses `__tablename__` with lowercase plural names
## Pydantic Schemas
- [ ] **Schema hierarchy**: Follows Base/Create/Update/Response pattern
- [ ] **Validators**: Uses `@field_validator` for custom validation
- [ ] **ConfigDict**: Response schemas have `model_config = ConfigDict(from_attributes=True)`
- [ ] **Field constraints**: Uses appropriate constraints (`ge`, `le`, `max_length`, etc.)
- [ ] **Optional fields**: Update schemas use `| None` for optional fields
- [ ] **Descriptions**: All fields have descriptions in `Field(..., description="...")`
## FastAPI Endpoints
- [ ] **Type hints**: ALL endpoint functions fully typed with `Annotated`
- [ ] **Docstrings**: Endpoints have comprehensive docstrings (Args, Returns, Raises)
- [ ] **Status codes**: Uses appropriate HTTP status codes from `status` module
- [ ] **Response models**: Endpoints specify `response_model`
- [ ] **Dependencies**: Uses Depends() for repository and auth dependencies
- [ ] **Error handling**: Raises HTTPException with proper status codes and messages
- [ ] **Router prefix**: Router has appropriate prefix and tags
## Repository Pattern
- [ ] **Tenant isolation**: ALL queries filter by `tenant_id` (CRITICAL!)
- [ ] **Type hints**: Repository methods fully typed
- [ ] **Async/await**: Uses async/await for database operations
- [ ] **Session management**: Properly commits and refreshes after changes
- [ ] **Error handling**: Handles database errors appropriately
- [ ] **CRUD methods**: Implements standard create, read, update, delete methods
## Multi-Tenant Architecture
- [ ] **Tenant filtering**: All queries include tenant_id filter
- [ ] **Repository methods**: Accept `tenant_id` parameter
- [ ] **Validation**: Validates user has access to requested tenant
- [ ] **Isolation**: No cross-tenant data leakage possible
- [ ] **Foreign keys**: Multi-tenant relationships properly enforced
## Imports Organization
- [ ] **Import order**: Follows Ruff isort rules:
1. Standard library imports
2. Third-party imports
3. Local imports (app.*)
- [ ] **Absolute imports**: Uses absolute imports (not relative)
- [ ] **Grouped imports**: Related imports grouped together
- [ ] **No unused imports**: All imports are used
## Testing (Pytest)
- [ ] **Tests exist**: Endpoints/functions have corresponding tests
- [ ] **Test markers**: Uses pytest markers (@pytest.mark.unit, @pytest.mark.integration)
- [ ] **Fixtures**: Uses pytest fixtures for setup
- [ ] **Async tests**: Async tests decorated with `@pytest.mark.asyncio`
- [ ] **Mocking**: Uses AsyncMock/MagicMock for external dependencies
- [ ] **Coverage**: Maintains or improves test coverage (aim for >80%)
- [ ] **Assertions**: Tests have clear, specific assertions
## Security
- [ ] **Input validation**: Uses Pydantic schemas for input validation
- [ ] **SQL injection**: Uses parameterized queries (SQLModel handles this)
- [ ] **Authentication**: Endpoints require authentication via dependencies
- [ ] **Authorization**: Validates user has permission for actions
- [ ] **Secrets**: Uses environment variables for secrets (never hardcode)
- [ ] **Rate limiting**: Critical endpoints have rate limiting
## Error Handling
- [ ] **HTTPException**: Raises HTTPException with proper status codes
- [ ] **Error messages**: Error messages are descriptive and user-friendly
- [ ] **Logging**: Errors are logged appropriately
- [ ] **Validation errors**: Pydantic validation errors return 422
- [ ] **Not found**: Returns 404 for missing resources
## Performance
- [ ] **Database queries**: Queries are efficient (no N+1 problems)
- [ ] **Indexes**: Frequently filtered columns have indexes
- [ ] **Pagination**: List endpoints implement pagination (limit/offset)
- [ ] **Async operations**: Uses async/await for I/O operations
- [ ] **Connection pooling**: Database uses connection pooling
## Documentation
- [ ] **Docstrings**: All functions have comprehensive docstrings
- [ ] **OpenAPI docs**: FastAPI auto-docs are accurate and complete
- [ ] **Type annotations**: Type hints serve as documentation
- [ ] **README updated**: README reflects any new features/changes
- [ ] **API examples**: Complex endpoints have usage examples
## Pre-commit Checks
- [ ] **Virtual env active**: Ran commands with `source .venv/bin/activate`
- [ ] **Ruff formatting**: `ruff format .` applied
- [ ] **Ruff linting**: `ruff check --fix .` passing
- [ ] **MyPy**: `mypy app/` passing with no type errors
- [ ] **Tests passing**: `pytest` passes all tests
- [ ] **Coverage**: Test coverage meets threshold (>80%)
- [ ] **Pre-commit hooks**: All pre-commit hooks pass
## API Design
- [ ] **RESTful**: Endpoints follow REST principles
- [ ] **Naming**: Endpoints use clear, descriptive names
- [ ] **Versioning**: API versioned appropriately (`/v1/`)
- [ ] **Consistency**: Similar endpoints have consistent patterns
- [ ] **CRUD complete**: Resource has full CRUD operations if needed

View File

@@ -0,0 +1,113 @@
# TypeScript/React Code Review Checklist
Use this checklist when reviewing TypeScript/React code or creating pull requests for Grey Haven projects.
## Formatting & Style
- [ ] **Line width**: Code lines do not exceed 90 characters
- [ ] **Indentation**: Uses 2 spaces (not tabs or 4 spaces)
- [ ] **Semicolons**: All statements end with semicolons
- [ ] **Quotes**: Uses double quotes for strings (not single quotes)
- [ ] **Trailing commas**: Has trailing commas in objects, arrays, function parameters
- [ ] **Prettier formatted**: Code is formatted with Prettier (`npm run format`)
- [ ] **ESLint passing**: No ESLint errors (`npm run lint`)
## TypeScript
- [ ] **Type safety**: No `any` types used unnecessarily (but allowed when appropriate)
- [ ] **Type annotations**: Complex types have proper interfaces/types defined
- [ ] **Imports organized**: Imports are auto-sorted (external → internal → relative)
- [ ] **Path aliases**: Uses `~/` path alias for src imports (not `../..`)
- [ ] **tsconfig compliance**: Follows strict TypeScript configuration
## Database Schema (Drizzle)
- [ ] **snake_case fields**: ALL database column names use snake_case (CRITICAL!)
-`created_at`, `tenant_id`, `email_address`
-`createdAt`, `tenantId`, `emailAddress`
- [ ] **Multi-tenant**: Tables include `tenant_id` or `tenantId` field
- [ ] **Indexes**: Frequently queried fields have indexes
- [ ] **RLS policies**: Row Level Security policies defined for multi-tenant isolation
- [ ] **Foreign keys**: Relationships use proper foreign key constraints
## React Components
- [ ] **Component structure**: Follows Grey Haven component pattern:
1. Imports (auto-sorted)
2. Types/Interfaces
3. Component definition
4. State management (hooks first)
5. Queries/Mutations
6. Effects
7. Event handlers
8. Conditional renders
9. Main render
- [ ] **Naming**: Components use PascalCase (`UserProfile.tsx`)
- [ ] **Props typed**: Component props have TypeScript interfaces
- [ ] **Hooks named**: Custom hooks start with `use-` prefix
- [ ] **Default export**: Route components export default
## TanStack Query
- [ ] **Query keys**: Uses descriptive, unique query keys
- [ ] **staleTime**: Sets appropriate staleTime (default: 60000ms / 1 minute)
- [ ] **Error handling**: Handles loading and error states
- [ ] **Mutations**: Uses useMutation for data updates
- [ ] **Invalidation**: Invalidates queries after mutations
## Environment Variables
- [ ] **Validation**: Environment variables validated with @t3-oss/env-core and Zod
- [ ] **VITE_ prefix**: Client variables prefixed with `VITE_`
- [ ] **Types**: All env variables have proper Zod schemas
- [ ] **Documentation**: Env variables have JSDoc comments
## Multi-Tenant Architecture
- [ ] **Tenant isolation**: All queries filter by `tenant_id` / `tenantId`
- [ ] **RLS enabled**: Row Level Security policies enforce tenant boundaries
- [ ] **Context usage**: Uses tenant context from auth provider
- [ ] **API calls**: Includes tenant ID in API requests
## Testing
- [ ] **Tests exist**: Components/functions have corresponding tests
- [ ] **Coverage**: Maintains or improves test coverage (aim for >80%)
- [ ] **Test structure**: Tests follow Arrange-Act-Assert pattern
- [ ] **Mocking**: External dependencies are properly mocked
## Security
- [ ] **Input validation**: User inputs are validated (Zod schemas)
- [ ] **XSS prevention**: No dangerouslySetInnerHTML without sanitization
- [ ] **API security**: API endpoints require authentication
- [ ] **Secrets**: No secrets or API keys in code (use env variables)
## Accessibility
- [ ] **Semantic HTML**: Uses appropriate HTML elements
- [ ] **ARIA labels**: Interactive elements have accessible labels
- [ ] **Keyboard nav**: Interactive elements are keyboard accessible
- [ ] **Color contrast**: Text has sufficient color contrast
## Performance
- [ ] **Code splitting**: Large components use lazy loading if appropriate
- [ ] **Memoization**: Expensive calculations use useMemo/useCallback
- [ ] **Query optimization**: Database queries are efficient (no N+1)
- [ ] **Bundle size**: No unnecessary dependencies added
## Documentation
- [ ] **JSDoc comments**: Complex functions have JSDoc comments
- [ ] **README updated**: README reflects any new features/changes
- [ ] **Type exports**: Exported types are documented
- [ ] **Examples**: Complex patterns have usage examples
## Pre-commit Checks
- [ ] **Build passes**: `npm run build` completes without errors
- [ ] **Linting passes**: `npm run lint` has no errors
- [ ] **Type checking**: `npx tsc --noEmit` has no errors
- [ ] **Tests passing**: `npm test` passes all tests
- [ ] **Pre-commit hooks**: Husky pre-commit hooks all pass

View File

@@ -0,0 +1,18 @@
{
"root": true,
"env": { "browser": true, "es2020": true },
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended"
],
"parser": "@typescript-eslint/parser",
"plugins": ["react-refresh"],
"rules": {
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"no-unused-vars": "off",
"react-refresh/only-export-components": "off"
}
}

View File

@@ -0,0 +1,9 @@
{
"tabWidth": 2,
"semi": true,
"printWidth": 90,
"singleQuote": false,
"endOfLine": "lf",
"trailingComma": "all",
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
}

View File

@@ -0,0 +1,186 @@
"""Example FastAPI Router Template.
Copy and adapt this for new Grey Haven API endpoints.
"""
from datetime import datetime
from typing import Annotated
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field
# Replace with your actual imports
from app.db.models.example import ExampleDB
from app.db.repositories.example_repository import ExampleRepository
from app.dependencies import get_current_user, get_example_repository
from app.schemas.example import ExampleCreate, ExampleResponse, ExampleUpdate
# 1. Create router with tags and dependencies
router = APIRouter(
prefix="/examples",
tags=["examples"],
dependencies=[Depends(get_current_user)], # Require authentication
)
# 2. POST endpoint - Create resource
@router.post("/", response_model=ExampleResponse, status_code=status.HTTP_201_CREATED)
async def create_example(
data: ExampleCreate,
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
current_user: Annotated[dict, Depends(get_current_user)],
) -> ExampleResponse:
"""
Create a new example resource.
Args:
data: Example creation data
repo: Example repository dependency
current_user: Currently authenticated user
Returns:
ExampleResponse: Created example
Raises:
HTTPException: If creation fails
"""
# Create resource with tenant isolation
example = await repo.create(data, tenant_id=current_user["tenant_id"])
return ExampleResponse.model_validate(example)
# 3. GET endpoint - Retrieve single resource
@router.get("/{example_id}", response_model=ExampleResponse)
async def get_example(
example_id: UUID,
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
current_user: Annotated[dict, Depends(get_current_user)],
) -> ExampleResponse:
"""
Get example by ID with tenant isolation.
Args:
example_id: Example UUID
repo: Example repository dependency
current_user: Currently authenticated user
Returns:
ExampleResponse: Example data
Raises:
HTTPException: If not found
"""
# Get with tenant isolation
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
if not example:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
)
return ExampleResponse.model_validate(example)
# 4. GET endpoint - List resources with pagination
@router.get("/", response_model=list[ExampleResponse])
async def list_examples(
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
current_user: Annotated[dict, Depends(get_current_user)],
limit: int = Query(100, ge=1, le=1000, description="Max items to return"),
offset: int = Query(0, ge=0, description="Number of items to skip"),
is_active: bool | None = Query(None, description="Filter by active status"),
) -> list[ExampleResponse]:
"""
List examples with pagination and filtering.
Args:
repo: Example repository dependency
current_user: Currently authenticated user
limit: Maximum number of items to return
offset: Number of items to skip
is_active: Optional filter by active status
Returns:
list[ExampleResponse]: List of examples
"""
# List with tenant isolation
examples = await repo.list_by_tenant(
tenant_id=current_user["tenant_id"],
limit=limit,
offset=offset,
is_active=is_active,
)
return [ExampleResponse.model_validate(e) for e in examples]
# 5. PATCH endpoint - Update resource
@router.patch("/{example_id}", response_model=ExampleResponse)
async def update_example(
example_id: UUID,
data: ExampleUpdate,
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
current_user: Annotated[dict, Depends(get_current_user)],
) -> ExampleResponse:
"""
Update example by ID with tenant isolation.
Args:
example_id: Example UUID
data: Update data (partial fields)
repo: Example repository dependency
current_user: Currently authenticated user
Returns:
ExampleResponse: Updated example
Raises:
HTTPException: If not found
"""
# Get existing example with tenant isolation
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
if not example:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
)
# Update fields (exclude_unset to only update provided fields)
update_dict = data.model_dump(exclude_unset=True)
for field, value in update_dict.items():
setattr(example, field, value)
# Save updates
updated = await repo.update(example)
return ExampleResponse.model_validate(updated)
# 6. DELETE endpoint - Delete resource
@router.delete("/{example_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_example(
example_id: UUID,
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
current_user: Annotated[dict, Depends(get_current_user)],
) -> None:
"""
Delete example by ID with tenant isolation.
Args:
example_id: Example UUID
repo: Example repository dependency
current_user: Currently authenticated user
Raises:
HTTPException: If not found
"""
# Get existing example with tenant isolation
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
if not example:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
)
# Delete
await repo.delete(example_id, tenant_id=current_user["tenant_id"])

View File

@@ -0,0 +1,126 @@
"""Example SQLModel Database Model Template.
Copy and adapt this for new Grey Haven database models.
"""
from __future__ import annotations
from datetime import datetime
from uuid import UUID, uuid4
from sqlalchemy import Column as SAColumn
from sqlmodel import JSON, Column, Field, SQLModel
def utc_now() -> datetime:
"""Return current UTC datetime."""
from datetime import UTC
return datetime.now(UTC)
class ExampleDB(SQLModel, table=True): # type: ignore[call-arg]
"""
Example database model with multi-tenant support.
This model demonstrates Grey Haven's database conventions:
- snake_case field names
- Multi-tenant isolation with tenant_id
- UTC timestamps
- Proper indexes
- Comprehensive docstrings
"""
__tablename__ = "examples"
# Primary identification
id: UUID = Field(
default_factory=uuid4, primary_key=True, description="Unique example identifier"
)
# Multi-tenant field (CRITICAL - always include!)
tenant_id: UUID = Field(
foreign_key="tenants.id", index=True, description="Owning tenant identifier"
)
# Example fields - all snake_case!
name: str = Field(index=True, max_length=255, description="Example name")
description: str | None = Field(
default=None, max_length=1000, description="Optional description"
)
# Relationships (foreign keys)
owner_id: UUID | None = Field(
default=None, foreign_key="users.id", index=True, description="Owner user ID"
)
# Status flags
is_active: bool = Field(default=True, description="Whether example is active")
is_archived: bool = Field(default=False, description="Whether example is archived")
# JSON metadata field
metadata: dict | None = Field(
default=None,
sa_column=Column(JSON),
description="Flexible JSON metadata storage",
)
# Numerical fields
priority: int = Field(
default=0, ge=0, le=10, description="Priority level (0-10)"
)
max_retries: int = Field(
default=3, ge=0, description="Maximum number of retry attempts"
)
# Timestamps (UTC)
created_at: datetime = Field(
default_factory=utc_now, description="Creation timestamp (UTC)"
)
updated_at: datetime = Field(
default_factory=utc_now, description="Last update timestamp (UTC)"
)
archived_at: datetime | None = Field(
default=None, description="Archive timestamp (UTC)"
)
# Uncomment if using custom UTCDateTime type
# from app.db.db_types import UTCDateTime
# created_at: datetime = Field(
# default_factory=utc_now,
# sa_column=SAColumn(UTCDateTime, nullable=False)
# )
# updated_at: datetime = Field(
# default_factory=utc_now,
# sa_column=SAColumn(UTCDateTime, nullable=False, onupdate=utc_now)
# )
# Pydantic schemas for API (in separate schemas file)
# class ExampleBase(BaseModel):
# """Base example schema with shared fields."""
# name: str = Field(..., max_length=255)
# description: str | None = None
# is_active: bool = True
# priority: int = Field(default=0, ge=0, le=10)
#
# class ExampleCreate(ExampleBase):
# """Schema for creating an example."""
# tenant_id: UUID
#
# class ExampleUpdate(BaseModel):
# """Schema for updating an example (all fields optional)."""
# name: str | None = None
# description: str | None = None
# is_active: bool | None = None
# priority: int | None = Field(None, ge=0, le=10)
#
# class ExampleResponse(ExampleBase):
# """Example response schema."""
# id: UUID
# tenant_id: UUID
# owner_id: UUID | None
# created_at: datetime
# updated_at: datetime
#
# model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,73 @@
# Grey Haven Studio - Ruff Configuration Template
# Copy this to your project root as pyproject.toml or ruff.toml
[tool.ruff]
# CRITICAL: Line length is 130, not 80 or 88!
line-length = 130
indent-width = 4
# Auto-fix issues without showing unfixable errors
fix-only = true
show-fixes = true
# Python version
target-version = "py312"
[tool.ruff.lint]
# Enable specific linter rules
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort (import sorting)
"B", # flake8-bugbear (bug detection)
"C4", # flake8-comprehensions
"UP", # pyupgrade (automatic upgrades)
]
# Ignore specific rules if needed
ignore = []
[tool.ruff.format]
# Use double quotes for strings
quote-style = "double"
# Use spaces for indentation
indent-style = "space"
# Use Unix-style line endings
line-ending = "lf"
[tool.ruff.lint.isort]
# Configure import sorting
known-first-party = ["app"]
section-order = [
"future",
"standard-library",
"third-party",
"first-party",
"local-folder",
]
# MyPy Configuration (add to pyproject.toml)
# [tool.mypy]
# python_version = "3.12"
# warn_return_any = true
# warn_unused_configs = true
# disallow_untyped_defs = true # REQUIRED: Type hints on all functions!
# check_untyped_defs = true
# ignore_missing_imports = false
# strict_optional = true
# warn_redundant_casts = true
# warn_unused_ignores = true
# Pytest Configuration (add to pyproject.toml)
# [tool.pytest.ini_options]
# pythonpath = ["."]
# asyncio_mode = "auto"
# testpaths = ["tests"]
# markers = [
# "unit: Unit tests",
# "integration: Integration tests",
# "e2e: End-to-end tests",
# ]

View File

@@ -0,0 +1,85 @@
// Example React Component Template
// Copy and adapt this for new Grey Haven components
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Button } from "~/lib/components/ui/button";
import { Card } from "~/lib/components/ui/card";
import { queryClient } from "~/lib/query-client";
// 1. Define your types/interfaces
interface MyComponentProps {
id: string;
onUpdate?: (data: MyData) => void;
}
interface MyData {
id: string;
name: string;
created_at: Date; // snake_case for database fields
is_active: boolean;
}
// 2. Component (default export for routes)
export default function MyComponent({ id, onUpdate }: MyComponentProps) {
// 3. State management
const [isEditing, setIsEditing] = useState(false);
// 4. Queries with TanStack Query
const { data, isLoading, error } = useQuery(
{
queryKey: ["myData", id],
queryFn: async () => {
// Replace with your API call
const response = await fetch(`/api/data/${id}`);
return response.json();
},
staleTime: 60000, // 1 minute - Grey Haven default
},
queryClient,
);
// 5. Event handlers
const handleSave = async () => {
// Replace with your save logic
console.log("Saving...");
setIsEditing(false);
onUpdate?.(data);
};
// 6. Conditional renders
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error loading data</div>;
}
if (!data) {
return <div>No data found</div>;
}
// 7. Main render
return (
<Card className="p-6">
<h2 className="mb-4 text-2xl font-bold">{data.name}</h2>
{isEditing ? (
<div className="space-y-4">
{/* Edit mode UI */}
<Button onClick={handleSave}>Save</Button>
<Button variant="outline" onClick={() => setIsEditing(false)}>
Cancel
</Button>
</div>
) : (
<div className="space-y-2">
{/* View mode UI */}
<p>Status: {data.is_active ? "Active" : "Inactive"}</p>
<Button onClick={() => setIsEditing(true)}>Edit</Button>
</div>
)}
</Card>
);
}

View File

@@ -0,0 +1,92 @@
// Example TanStack Start Server Function Template
// Copy and adapt this for new Grey Haven server functions
import { createServerFn } from "@tanstack/start";
import { db } from "~/lib/server/db";
import { users } from "~/lib/server/schema/users";
import { eq } from "drizzle-orm";
import { z } from "zod";
// 1. Define input schema with Zod
const getUserInputSchema = z.object({
userId: z.string().uuid(),
tenantId: z.string(), // Always include tenant_id for multi-tenant isolation
});
// 2. Define output type
interface UserOutput {
id: string;
name: string;
email: string;
created_at: Date;
tenant_id: string;
}
// 3. Create server function
export const getUser = createServerFn("GET", async (input: unknown): Promise<UserOutput> => {
// Validate input
const { userId, tenantId } = getUserInputSchema.parse(input);
// Query database with tenant isolation
const user = await db.query.users.findFirst({
where: eq(users.id, userId) && eq(users.tenant_id, tenantId), // Tenant filtering!
});
if (!user) {
throw new Error("User not found");
}
// Return typed result
return {
id: user.id,
name: user.name,
email: user.email,
created_at: user.created_at,
tenant_id: user.tenant_id,
};
});
// 4. Mutation example
const updateUserInputSchema = z.object({
userId: z.string().uuid(),
tenantId: z.string(),
name: z.string().min(1),
email: z.string().email(),
});
export const updateUser = createServerFn(
"POST",
async (input: unknown): Promise<UserOutput> => {
// Validate input
const { userId, tenantId, name, email } = updateUserInputSchema.parse(input);
// Update with tenant isolation
const [updatedUser] = await db
.update(users)
.set({ name, email, updated_at: new Date() })
.where(eq(users.id, userId) && eq(users.tenant_id, tenantId)) // Tenant filtering!
.returning();
if (!updatedUser) {
throw new Error("User not found or update failed");
}
return {
id: updatedUser.id,
name: updatedUser.name,
email: updatedUser.email,
created_at: updatedUser.created_at,
tenant_id: updatedUser.tenant_id,
};
},
);
// 5. Usage in client components
// import { useQuery } from "@tanstack/react-query";
// import { getUser } from "~/lib/server/functions/users";
//
// const { data: user } = useQuery({
// queryKey: ["user", userId],
// queryFn: () => getUser({ userId, tenantId }),
// staleTime: 60000,
// });