From ce1171a57481ccff0cec20892c658cfb3f9ca19a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 9 Dec 2025 16:52:29 +0000 Subject: [PATCH] Add Augment Enterprise Rules This commit adds enterprise-wide Augment Code rules to standardize development practices across the organization. Rules included: - agent_tool_calls.md - Agent tool call tracking and rules file usage reporting - backend-rules.md - Python 3 best practices and type hints - coding-standards.md - Code style, formatting, and automated linters - docs.md - Documentation update requirements for code changes - documentation-rules.md - Comprehensive docstrings and documentation standards - frontend-rules.md - TypeScript strict mode and React best practices - logging-rules.md - Log levels and structured logging standards - rest-api-rules.md - REST API best practices with OpenAPI documentation - security-rules.md - Input validation and security best practices These rules will help Augment Agent provide better, more consistent code suggestions aligned with our organizational standards. --- .augment/rules/agent_tool_calls.md | 6 + .augment/rules/backend-rules.md | 227 +++++++++++++++ .augment/rules/coding-standards.md | 287 ++++++++++++++++++ .augment/rules/docs.md | 5 + .augment/rules/documentation-rules.md | 399 ++++++++++++++++++++++++++ .augment/rules/frontend-rules.md | 204 +++++++++++++ .augment/rules/logging-rules.md | 370 ++++++++++++++++++++++++ .augment/rules/rest-api-rules.md | 9 + .augment/rules/security-rules.md | 321 +++++++++++++++++++++ 9 files changed, 1828 insertions(+) create mode 100644 .augment/rules/agent_tool_calls.md create mode 100644 .augment/rules/backend-rules.md create mode 100644 .augment/rules/coding-standards.md create mode 100644 .augment/rules/docs.md create mode 100644 .augment/rules/documentation-rules.md create mode 100644 .augment/rules/frontend-rules.md create mode 100644 .augment/rules/logging-rules.md create mode 100644 .augment/rules/rest-api-rules.md create mode 100644 .augment/rules/security-rules.md diff --git a/.augment/rules/agent_tool_calls.md b/.augment/rules/agent_tool_calls.md new file mode 100644 index 0000000..b1e6d60 --- /dev/null +++ b/.augment/rules/agent_tool_calls.md @@ -0,0 +1,6 @@ +--- +type: "always_apply" +--- + +- Always list the tools called (if any) during the prompt processing at the end of your response. +- Always list the rules file(s) used (if any) at the end of your prompt response. \ No newline at end of file diff --git a/.augment/rules/backend-rules.md b/.augment/rules/backend-rules.md new file mode 100644 index 0000000..6de4b20 --- /dev/null +++ b/.augment/rules/backend-rules.md @@ -0,0 +1,227 @@ +# Backend Development Rules + +## Python 3 Best Practices + +### Rule 1: Always Use Type Hints +Use type hints for all function signatures, class attributes, and variables where type is not obvious. Use `typing` module for complex types. + +**Example:** +```python +# ❌ Bad +def get_user(user_id): + return db.query(user_id) + +# ✅ Good +from typing import Optional +from models import User + +def get_user(user_id: str) -> Optional[User]: + """Retrieve a user by ID. + + Args: + user_id: The unique identifier for the user + + Returns: + User object if found, None otherwise + """ + return db.query(User).filter_by(id=user_id).first() +``` + +**For complex types:** +```python +from typing import Dict, List, Union, TypedDict + +class UserData(TypedDict): + id: str + name: str + email: str + roles: List[str] + +def process_users(users: List[UserData]) -> Dict[str, Union[str, int]]: + return { + "count": len(users), + "status": "processed" + } +``` + +### Rule 2: Use Async/Await for I/O Operations +Use `async`/`await` for I/O-bound operations (database queries, API calls, file operations) to improve performance. + +**Example:** +```python +# ❌ Bad - Synchronous I/O +def fetch_user_data(user_ids: List[str]) -> List[User]: + users = [] + for user_id in user_ids: + user = requests.get(f"/api/users/{user_id}").json() + users.append(user) + return users + +# ✅ Good - Async I/O +import asyncio +import aiohttp +from typing import List + +async def fetch_user_data(user_ids: List[str]) -> List[User]: + async with aiohttp.ClientSession() as session: + tasks = [fetch_single_user(session, user_id) for user_id in user_ids] + return await asyncio.gather(*tasks) + +async def fetch_single_user(session: aiohttp.ClientSession, user_id: str) -> User: + async with session.get(f"/api/users/{user_id}") as response: + data = await response.json() + return User(**data) +``` + +### Rule 3: Use Context Managers for Resource Management +Always use context managers (`with` statement) for managing resources like files, database connections, and locks. + +**Example:** +```python +# ❌ Bad +def read_config(): + file = open('config.json') + data = json.load(file) + file.close() + return data + +# ✅ Good +from pathlib import Path +import json + +def read_config() -> dict: + config_path = Path('config.json') + with config_path.open('r') as file: + return json.load(file) + +# ✅ Good - Custom context manager +from contextlib import asynccontextmanager +from typing import AsyncGenerator + +@asynccontextmanager +async def get_db_session() -> AsyncGenerator[Session, None]: + session = SessionLocal() + try: + yield session + await session.commit() + except Exception: + await session.rollback() + raise + finally: + await session.close() +``` + +## UV Package Manager Usage + +### Rule 4: Use UV for Dependency Management +Use `uv` for fast, reliable Python package management. Define dependencies in `pyproject.toml` and use `uv.lock` for reproducible builds. + +**Example:** +```toml +# pyproject.toml +[project] +name = "my-project" +version = "0.1.0" +requires-python = ">=3.11" +dependencies = [ + "fastapi>=0.104.0", + "pydantic>=2.0.0", + "sqlalchemy>=2.0.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4.0", + "pytest-asyncio>=0.21.0", + "ruff>=0.1.0", + "mypy>=1.7.0", +] +``` + +**Commands:** +```bash +# Install dependencies +uv pip install -e . + +# Install with dev dependencies +uv pip install -e ".[dev]" + +# Add a new dependency +uv pip install package-name +# Then update pyproject.toml manually + +# Sync dependencies (install/update to match pyproject.toml) +uv pip sync +``` + +## API Design Patterns + +### Rule 5: Follow RESTful Conventions and Use Proper HTTP Status Codes +Design APIs following REST principles with proper HTTP methods and status codes. Use Pydantic models for request/response validation. + +**Example:** +```python +from fastapi import FastAPI, HTTPException, status +from pydantic import BaseModel, EmailStr +from typing import List + +app = FastAPI() + +class UserCreate(BaseModel): + name: str + email: EmailStr + +class UserResponse(BaseModel): + id: str + name: str + email: str + + class Config: + from_attributes = True + +# ✅ Good - RESTful endpoints with proper status codes +@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED) +async def create_user(user: UserCreate) -> UserResponse: + """Create a new user.""" + existing = await get_user_by_email(user.email) + if existing: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="User with this email already exists" + ) + new_user = await db.create_user(user) + return UserResponse.from_orm(new_user) + +@app.get("/users/{user_id}", response_model=UserResponse) +async def get_user(user_id: str) -> UserResponse: + """Retrieve a user by ID.""" + user = await db.get_user(user_id) + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"User {user_id} not found" + ) + return UserResponse.from_orm(user) + +@app.put("/users/{user_id}", response_model=UserResponse) +async def update_user(user_id: str, user: UserCreate) -> UserResponse: + """Update an existing user.""" + updated = await db.update_user(user_id, user) + if not updated: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"User {user_id} not found" + ) + return UserResponse.from_orm(updated) + +@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_user(user_id: str) -> None: + """Delete a user.""" + deleted = await db.delete_user(user_id) + if not deleted: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"User {user_id} not found" + ) +``` + diff --git a/.augment/rules/coding-standards.md b/.augment/rules/coding-standards.md new file mode 100644 index 0000000..e9f54ee --- /dev/null +++ b/.augment/rules/coding-standards.md @@ -0,0 +1,287 @@ +# Coding Standards + +## Code Style and Formatting + +### Rule 1: Use Automated Formatters and Linters +Always use automated code formatters and linters. Configure them in your project and run them before committing. + +**Python:** +```toml +# pyproject.toml +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "N", "W", "UP", "B", "A", "C4", "DTZ", "T10", "DJ", "EM", "ISC", "ICN", "G", "PIE", "T20", "PT", "Q", "RET", "SIM", "TID", "ARG", "ERA", "PD", "PGH", "PL", "TRY", "NPY", "RUF"] + +[tool.mypy] +python_version = "3.11" +strict = true +warn_return_any = true +warn_unused_configs = true +``` + +**TypeScript/JavaScript:** +```json +// .eslintrc.json +{ + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "prettier" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": "warn", + "@typescript-eslint/no-explicit-any": "error", + "react/react-in-jsx-scope": "off" + } +} + +// .prettierrc +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2 +} +``` + +## Naming Conventions + +### Rule 2: Follow Language-Specific Naming Conventions +Use consistent naming conventions appropriate for each language. + +**Python:** +```python +# ✅ Good +class UserRepository: # PascalCase for classes + MAX_RETRY_COUNT = 3 # UPPER_SNAKE_CASE for constants + + def __init__(self, database_url: str): # snake_case for methods and variables + self._connection = None # Leading underscore for private attributes + self.database_url = database_url + + def get_user_by_id(self, user_id: str) -> Optional[User]: # snake_case for methods + """Retrieve user by ID.""" + return self._query_database(user_id) + + def _query_database(self, query: str) -> Any: # Leading underscore for private methods + """Internal database query method.""" + pass +``` + +**TypeScript/JavaScript:** +```typescript +// ✅ Good +const MAX_RETRY_COUNT = 3; // UPPER_SNAKE_CASE for constants + +interface UserRepository { // PascalCase for interfaces and classes + getUserById(userId: string): Promise; // camelCase for methods +} + +class DatabaseUserRepository implements UserRepository { // PascalCase for classes + private connection: Connection; // camelCase for properties + + constructor(private databaseUrl: string) { // camelCase for parameters + this.connection = createConnection(databaseUrl); + } + + async getUserById(userId: string): Promise { // camelCase for methods + return this.queryDatabase(userId); + } + + private async queryDatabase(query: string): Promise { // camelCase for private methods + // Implementation + } +} +``` + +### Rule 3: Use Descriptive and Meaningful Names +Choose names that clearly describe the purpose and behavior. Avoid abbreviations unless they are widely understood. + +**Example:** +```python +# ❌ Bad +def calc(u, p): + return u * p * 0.9 + +# ✅ Good +def calculate_discounted_price(unit_price: float, quantity: int) -> float: + """Calculate the total price with a 10% discount applied. + + Args: + unit_price: Price per unit + quantity: Number of units + + Returns: + Total price after applying 10% discount + """ + DISCOUNT_RATE = 0.9 + return unit_price * quantity * DISCOUNT_RATE +``` + +## Code Organization and File Structure + +### Rule 4: Organize Code by Feature, Not by Type +Structure your codebase by feature/domain rather than by technical layer. + +**Example:** +``` +# ❌ Bad - Organized by type +src/ + controllers/ + user_controller.py + order_controller.py + models/ + user.py + order.py + services/ + user_service.py + order_service.py + +# ✅ Good - Organized by feature +src/ + users/ + __init__.py + models.py + service.py + controller.py + repository.py + orders/ + __init__.py + models.py + service.py + controller.py + repository.py + shared/ + database.py + exceptions.py +``` + +### Rule 5: Keep Files Focused and Reasonably Sized +Each file should have a single, clear responsibility. Aim for files under 300-400 lines. + +**Example:** +```python +# ✅ Good - user/service.py (focused on user business logic) +from typing import Optional +from .models import User, UserCreate +from .repository import UserRepository +from shared.exceptions import UserNotFoundError, DuplicateUserError + +class UserService: + """Service layer for user-related business logic.""" + + def __init__(self, repository: UserRepository): + self.repository = repository + + async def create_user(self, user_data: UserCreate) -> User: + """Create a new user with validation.""" + existing = await self.repository.find_by_email(user_data.email) + if existing: + raise DuplicateUserError(f"User with email {user_data.email} already exists") + + return await self.repository.create(user_data) + + async def get_user(self, user_id: str) -> User: + """Retrieve a user by ID.""" + user = await self.repository.find_by_id(user_id) + if not user: + raise UserNotFoundError(f"User {user_id} not found") + return user +``` + +## Comment and Documentation Requirements + +### Rule 6: Write Self-Documenting Code with Strategic Comments +Code should be self-explanatory through good naming and structure. Use comments to explain "why", not "what". + +**Example:** +```python +# ❌ Bad - Comments explain what the code does +def process_order(order_id: str): + # Get the order from database + order = db.get_order(order_id) + # Check if order is valid + if order.status == "pending": + # Update the status + order.status = "processing" + +# ✅ Good - Self-documenting with strategic comments +def process_order(order_id: str) -> Order: + """Process a pending order and update its status. + + Args: + order_id: Unique identifier for the order + + Returns: + Updated order object + + Raises: + OrderNotFoundError: If order doesn't exist + InvalidOrderStateError: If order is not in pending state + """ + order = get_order_or_raise(order_id) + + if not order.is_pending(): + raise InvalidOrderStateError( + f"Order {order_id} cannot be processed in {order.status} state" + ) + + # We mark as processing before payment to prevent duplicate processing + # in case of concurrent requests (see ticket #1234) + order.mark_as_processing() + + return order +``` + +**Documentation strings:** +```python +# ✅ Good - Comprehensive docstring +def calculate_shipping_cost( + weight_kg: float, + destination: str, + is_express: bool = False +) -> float: + """Calculate shipping cost based on weight and destination. + + The cost is calculated using a base rate plus weight-based charges. + Express shipping adds a 50% premium to the total cost. + + Args: + weight_kg: Package weight in kilograms (must be positive) + destination: ISO 3166-1 alpha-2 country code + is_express: Whether to use express shipping (default: False) + + Returns: + Shipping cost in USD + + Raises: + ValueError: If weight is negative or destination code is invalid + + Examples: + >>> calculate_shipping_cost(2.5, "US") + 15.50 + >>> calculate_shipping_cost(2.5, "US", is_express=True) + 23.25 + """ + if weight_kg < 0: + raise ValueError("Weight must be positive") + + if not is_valid_country_code(destination): + raise ValueError(f"Invalid country code: {destination}") + + base_rate = get_base_rate(destination) + weight_charge = weight_kg * COST_PER_KG + total = base_rate + weight_charge + + if is_express: + total *= EXPRESS_MULTIPLIER + + return round(total, 2) +``` + diff --git a/.augment/rules/docs.md b/.augment/rules/docs.md new file mode 100644 index 0000000..53a13e0 --- /dev/null +++ b/.augment/rules/docs.md @@ -0,0 +1,5 @@ +--- +type: "agent_requested" +--- + +- Whenever performing a change that needs to be reflected in the project's documentation, make sure to update the relevant documentation (`.md` files) files as well. \ No newline at end of file diff --git a/.augment/rules/documentation-rules.md b/.augment/rules/documentation-rules.md new file mode 100644 index 0000000..77b6b55 --- /dev/null +++ b/.augment/rules/documentation-rules.md @@ -0,0 +1,399 @@ +# Documentation Standards + +## Code Documentation Requirements + +### Rule 1: Write Comprehensive Docstrings for All Public APIs +Every public function, class, and module must have a docstring explaining its purpose, parameters, return values, and exceptions. + +**Python - Google Style:** +```python +def calculate_discount( + original_price: float, + discount_percentage: float, + customer_tier: str = "standard" +) -> float: + """Calculate the final price after applying discount and tier benefits. + + This function applies a percentage discount to the original price and + then applies additional tier-based benefits for premium customers. + + Args: + original_price: The original price before any discounts (must be positive) + discount_percentage: Discount percentage to apply (0-100) + customer_tier: Customer tier level. Options: "standard", "premium", "vip" + Defaults to "standard" + + Returns: + The final price after all discounts have been applied, rounded to 2 decimals + + Raises: + ValueError: If original_price is negative or discount_percentage is not in 0-100 range + InvalidTierError: If customer_tier is not a valid tier level + + Examples: + >>> calculate_discount(100.0, 10.0) + 90.0 + >>> calculate_discount(100.0, 10.0, "premium") + 85.5 + + Note: + Premium customers receive an additional 5% off, VIP customers get 10% off + on top of the standard discount. + """ + if original_price < 0: + raise ValueError("Price cannot be negative") + + if not 0 <= discount_percentage <= 100: + raise ValueError("Discount percentage must be between 0 and 100") + + tier_multipliers = { + "standard": 1.0, + "premium": 0.95, + "vip": 0.90 + } + + if customer_tier not in tier_multipliers: + raise InvalidTierError(f"Invalid tier: {customer_tier}") + + discounted = original_price * (1 - discount_percentage / 100) + final_price = discounted * tier_multipliers[customer_tier] + + return round(final_price, 2) +``` + +**TypeScript - JSDoc:** +```typescript +/** + * Fetches user data from the API with retry logic. + * + * This function attempts to fetch user data and will retry up to 3 times + * with exponential backoff if the request fails. + * + * @param userId - The unique identifier for the user + * @param options - Optional configuration for the request + * @param options.includeProfile - Whether to include full profile data + * @param options.timeout - Request timeout in milliseconds (default: 5000) + * + * @returns Promise resolving to the user data + * + * @throws {UserNotFoundError} When the user doesn't exist + * @throws {NetworkError} When network request fails after all retries + * @throws {ValidationError} When userId format is invalid + * + * @example + * ```typescript + * const user = await fetchUserData('user-123'); + * console.log(user.name); + * ``` + * + * @example + * ```typescript + * const user = await fetchUserData('user-123', { + * includeProfile: true, + * timeout: 10000 + * }); + * ``` + */ +async function fetchUserData( + userId: string, + options?: { + includeProfile?: boolean; + timeout?: number; + } +): Promise { + validateUserId(userId); + + const config = { + includeProfile: options?.includeProfile ?? false, + timeout: options?.timeout ?? 5000, + }; + + return retryWithBackoff(() => apiClient.getUser(userId, config)); +} +``` + +### Rule 2: Maintain Inline Comments for Complex Logic +Use inline comments to explain complex algorithms, business rules, or non-obvious decisions. + +**Example:** +```python +def calculate_shipping_date(order_date: datetime, destination: str) -> datetime: + """Calculate estimated shipping date based on destination.""" + + # Business rule: Orders placed after 2 PM are processed next business day + cutoff_time = order_date.replace(hour=14, minute=0, second=0) + processing_start = order_date if order_date < cutoff_time else order_date + timedelta(days=1) + + # Skip weekends for processing start date + while processing_start.weekday() >= 5: # 5 = Saturday, 6 = Sunday + processing_start += timedelta(days=1) + + # Shipping duration varies by destination zone + # Zone A (local): 1-2 days, Zone B (regional): 3-5 days, Zone C (international): 7-14 days + zone = get_shipping_zone(destination) + shipping_days = ZONE_SHIPPING_DAYS[zone] + + estimated_date = processing_start + timedelta(days=shipping_days) + + # Add buffer for customs clearance on international shipments + if zone == "C": + estimated_date += timedelta(days=2) # Average customs delay + + return estimated_date +``` + +## Project-Level Documentation + +### Rule 3: Maintain Comprehensive README Files +Every project must have a README.md with essential information for developers. + +**Example README Structure:** +```markdown +# Project Name + +Brief description of what this project does and its purpose. + +## Features + +- Feature 1: Description +- Feature 2: Description +- Feature 3: Description + +## Prerequisites + +- Python 3.11+ +- Node.js 18+ +- PostgreSQL 14+ + +## Installation + +### Using uv (recommended) + +\`\`\`bash +# Clone the repository +git clone https://github.com/org/project.git +cd project + +# Install dependencies +uv pip install -e ".[dev]" + +# Set up environment variables +cp .env.example .env +# Edit .env with your configuration +\`\`\` + +### Using npm + +\`\`\`bash +npm install +\`\`\` + +## Configuration + +Create a `.env` file with the following variables: + +\`\`\`bash +DATABASE_URL=postgresql://user:pass@localhost/dbname +API_KEY=your-api-key +SECRET_KEY=your-secret-key +DEBUG=false +\`\`\` + +## Usage + +### Running the Development Server + +\`\`\`bash +# Backend +uvicorn main:app --reload + +# Frontend +npm run dev +\`\`\` + +### Running Tests + +\`\`\`bash +# Run all tests +pytest + +# Run with coverage +pytest --cov=src --cov-report=html + +# Run specific test file +pytest tests/test_users.py +\`\`\` + +## Project Structure + +\`\`\` +project/ +├── src/ +│ ├── users/ # User management module +│ ├── orders/ # Order processing module +│ └── shared/ # Shared utilities +├── tests/ # Test files +├── docs/ # Additional documentation +└── scripts/ # Utility scripts +\`\`\` + +## API Documentation + +API documentation is available at `http://localhost:8000/docs` when running the development server. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. + +## License + +MIT License - see [LICENSE](LICENSE) file for details. +``` + +## API Documentation Standards + +### Rule 4: Use OpenAPI/Swagger for REST APIs +Document all REST APIs using OpenAPI specification with detailed descriptions and examples. + +**Example:** +```python +from fastapi import FastAPI, HTTPException, status +from pydantic import BaseModel, Field + +app = FastAPI( + title="User Management API", + description="API for managing user accounts and profiles", + version="1.0.0", + contact={ + "name": "API Support", + "email": "api-support@example.com", + }, + license_info={ + "name": "MIT", + }, +) + +class UserCreate(BaseModel): + """Schema for creating a new user.""" + username: str = Field( + ..., + min_length=3, + max_length=50, + description="Unique username for the account", + example="john_doe" + ) + email: str = Field( + ..., + description="User's email address", + example="john@example.com" + ) + full_name: str = Field( + ..., + description="User's full name", + example="John Doe" + ) + +class UserResponse(BaseModel): + """Schema for user response.""" + id: str = Field(..., description="Unique user identifier") + username: str = Field(..., description="Username") + email: str = Field(..., description="Email address") + full_name: str = Field(..., description="Full name") + created_at: str = Field(..., description="Account creation timestamp") + +@app.post( + "/users", + response_model=UserResponse, + status_code=status.HTTP_201_CREATED, + summary="Create a new user", + description="Create a new user account with the provided information", + responses={ + 201: { + "description": "User created successfully", + "content": { + "application/json": { + "example": { + "id": "usr_123abc", + "username": "john_doe", + "email": "john@example.com", + "full_name": "John Doe", + "created_at": "2024-01-15T10:30:00Z" + } + } + } + }, + 409: {"description": "User with this email or username already exists"}, + 422: {"description": "Validation error in request body"} + }, + tags=["Users"] +) +async def create_user(user: UserCreate) -> UserResponse: + """ + Create a new user account. + + This endpoint creates a new user with the provided information. + Username and email must be unique across the system. + """ + # Implementation + pass +``` + +## Changelog and Versioning + +### Rule 5: Maintain a CHANGELOG and Follow Semantic Versioning +Keep a CHANGELOG.md file and follow semantic versioning (MAJOR.MINOR.PATCH). + +**Example CHANGELOG.md:** +```markdown +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- New feature in development + +## [2.1.0] - 2024-01-15 + +### Added +- User profile picture upload functionality +- Email verification for new accounts +- Rate limiting on authentication endpoints + +### Changed +- Improved error messages for validation failures +- Updated dependencies to latest versions + +### Fixed +- Fixed bug where password reset tokens expired too quickly +- Corrected timezone handling in date calculations + +### Security +- Implemented additional XSS protection in user-generated content + +## [2.0.0] - 2023-12-01 + +### Added +- Complete API redesign with RESTful endpoints +- JWT-based authentication system +- Comprehensive API documentation + +### Changed +- **BREAKING**: Changed authentication from session-based to JWT +- **BREAKING**: Renamed `/api/v1/user` to `/api/v2/users` + +### Removed +- **BREAKING**: Removed deprecated `/auth/login-legacy` endpoint + +## [1.0.0] - 2023-10-01 + +### Added +- Initial release +- User registration and authentication +- Basic profile management +``` + diff --git a/.augment/rules/frontend-rules.md b/.augment/rules/frontend-rules.md new file mode 100644 index 0000000..211f1e9 --- /dev/null +++ b/.augment/rules/frontend-rules.md @@ -0,0 +1,204 @@ +# Frontend Development Rules + +## TypeScript Best Practices + +### Rule 1: Enable Strict Mode and Avoid `any` +Always use TypeScript in strict mode and avoid using `any` type. Use `unknown` for truly unknown types and narrow them with type guards. + +**Example:** +```typescript +// ❌ Bad +function processData(data: any) { + return data.value; +} + +// ✅ Good +function processData(data: unknown) { + if (typeof data === 'object' && data !== null && 'value' in data) { + return (data as { value: string }).value; + } + throw new Error('Invalid data structure'); +} +``` + +**Configuration:** +```json +// tsconfig.json +{ + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true + } +} +``` + +### Rule 2: Use Explicit Type Annotations for Public APIs +Always provide explicit type annotations for function parameters, return types, and exported interfaces. + +**Example:** +```typescript +// ❌ Bad +export function calculateTotal(items) { + return items.reduce((sum, item) => sum + item.price, 0); +} + +// ✅ Good +interface Item { + price: number; + name: string; +} + +export function calculateTotal(items: Item[]): number { + return items.reduce((sum, item) => sum + item.price, 0); +} +``` + +## ReactJS Conventions + +### Rule 3: Use Functional Components with TypeScript Interfaces +Prefer functional components with proper TypeScript interfaces for props. Use `React.FC` sparingly and prefer explicit typing. + +**Example:** +```typescript +// ❌ Bad +const UserCard = (props) => { + return
{props.name}
; +}; + +// ✅ Good +interface UserCardProps { + name: string; + email: string; + onEdit?: (userId: string) => void; +} + +const UserCard = ({ name, email, onEdit }: UserCardProps) => { + return ( +
+

{name}

+

{email}

+ {onEdit && } +
+ ); +}; +``` + +### Rule 4: Follow Hooks Rules and Custom Hook Patterns +Always follow React Hooks rules. Extract complex logic into custom hooks with clear naming (use `use` prefix). + +**Example:** +```typescript +// ✅ Good - Custom Hook +function useUserData(userId: string) { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + + async function fetchUser() { + try { + const data = await api.getUser(userId); + if (!cancelled) { + setUser(data); + } + } catch (err) { + if (!cancelled) { + setError(err as Error); + } + } finally { + if (!cancelled) { + setLoading(false); + } + } + } + + fetchUser(); + return () => { cancelled = true; }; + }, [userId]); + + return { user, loading, error }; +} +``` + +## CSS Standards + +### Rule 5: Use BEM Naming Convention and CSS Modules +Use BEM (Block Element Modifier) naming convention with CSS Modules for component-scoped styles. + +**Example:** +```css +/* UserCard.module.css */ +.userCard { + padding: 1rem; + border: 1px solid #ddd; +} + +.userCard__header { + font-size: 1.5rem; + font-weight: bold; +} + +.userCard__email { + color: #666; +} + +.userCard--highlighted { + border-color: #007bff; + background-color: #f0f8ff; +} +``` + +```typescript +// UserCard.tsx +import styles from './UserCard.module.css'; + +const UserCard = ({ name, email, highlighted }: UserCardProps) => { + const cardClass = highlighted + ? `${styles.userCard} ${styles['userCard--highlighted']}` + : styles.userCard; + + return ( +
+

{name}

+

{email}

+
+ ); +}; +``` + +### Rule 6: Implement Mobile-First Responsive Design +Always design mobile-first using responsive breakpoints. Use CSS custom properties for theming. + +**Example:** +```css +/* Mobile-first approach */ +:root { + --spacing-unit: 0.5rem; + --max-width: 1200px; +} + +.container { + padding: var(--spacing-unit); + width: 100%; +} + +/* Tablet */ +@media (min-width: 768px) { + .container { + padding: calc(var(--spacing-unit) * 2); + } +} + +/* Desktop */ +@media (min-width: 1024px) { + .container { + max-width: var(--max-width); + margin: 0 auto; + padding: calc(var(--spacing-unit) * 3); + } +} +``` + diff --git a/.augment/rules/logging-rules.md b/.augment/rules/logging-rules.md new file mode 100644 index 0000000..254d416 --- /dev/null +++ b/.augment/rules/logging-rules.md @@ -0,0 +1,370 @@ +# Logging Standards + +## Log Levels and Usage + +### Rule 1: Use Appropriate Log Levels +Use the correct log level for each message. Follow this hierarchy: DEBUG < INFO < WARNING < ERROR < CRITICAL. + +**Log Level Guidelines:** + +- **DEBUG**: Detailed diagnostic information for troubleshooting during development +- **INFO**: General informational messages about application flow and state +- **WARNING**: Indication of potential issues or deprecated features +- **ERROR**: Error events that might still allow the application to continue +- **CRITICAL**: Severe errors that may cause the application to abort + +**Example - Python:** +```python +import logging +from typing import Optional + +logger = logging.getLogger(__name__) + +def process_payment(order_id: str, amount: float) -> bool: + """Process a payment for an order.""" + + # DEBUG: Detailed information for debugging + logger.debug(f"Starting payment processing for order {order_id}, amount: ${amount}") + + try: + # INFO: Normal application flow + logger.info(f"Processing payment for order {order_id}") + + payment_method = get_payment_method(order_id) + logger.debug(f"Payment method retrieved: {payment_method.type}") + + # WARNING: Potential issue but not critical + if amount > 10000: + logger.warning( + f"Large payment amount detected for order {order_id}: ${amount}", + extra={"order_id": order_id, "amount": amount} + ) + + result = payment_gateway.charge(payment_method, amount) + + if result.success: + # INFO: Successful operation + logger.info( + f"Payment successful for order {order_id}", + extra={ + "order_id": order_id, + "amount": amount, + "transaction_id": result.transaction_id + } + ) + return True + else: + # ERROR: Operation failed but application continues + logger.error( + f"Payment failed for order {order_id}: {result.error_message}", + extra={ + "order_id": order_id, + "amount": amount, + "error_code": result.error_code + } + ) + return False + + except PaymentGatewayUnavailable as e: + # CRITICAL: Service unavailable, major issue + logger.critical( + f"Payment gateway unavailable: {str(e)}", + extra={"order_id": order_id, "service": "payment_gateway"}, + exc_info=True + ) + raise + except Exception as e: + # ERROR: Unexpected error + logger.error( + f"Unexpected error processing payment for order {order_id}", + extra={"order_id": order_id}, + exc_info=True + ) + raise +``` + +**Example - TypeScript:** +```typescript +import { logger } from './logger'; + +async function processPayment(orderId: string, amount: number): Promise { + // DEBUG: Detailed diagnostic information + logger.debug('Starting payment processing', { orderId, amount }); + + try { + // INFO: Normal flow + logger.info('Processing payment', { orderId }); + + const paymentMethod = await getPaymentMethod(orderId); + logger.debug('Payment method retrieved', { type: paymentMethod.type }); + + // WARNING: Potential issue + if (amount > 10000) { + logger.warn('Large payment amount detected', { orderId, amount }); + } + + const result = await paymentGateway.charge(paymentMethod, amount); + + if (result.success) { + // INFO: Success + logger.info('Payment successful', { + orderId, + amount, + transactionId: result.transactionId, + }); + return true; + } else { + // ERROR: Failed operation + logger.error('Payment failed', { + orderId, + amount, + errorCode: result.errorCode, + errorMessage: result.errorMessage, + }); + return false; + } + } catch (error) { + // CRITICAL or ERROR depending on severity + if (error instanceof PaymentGatewayUnavailable) { + logger.critical('Payment gateway unavailable', { + orderId, + service: 'payment_gateway', + error: error.message, + }); + } else { + logger.error('Unexpected error processing payment', { + orderId, + error: error instanceof Error ? error.message : String(error), + }); + } + throw error; + } +} +``` + +## Structured Logging Format + +### Rule 2: Use Structured Logging with Consistent Fields +Always use structured logging (JSON format) with consistent field names for easy parsing and analysis. + +**Example - Python Configuration:** +```python +import logging +import json +from datetime import datetime +from typing import Any, Dict + +class StructuredFormatter(logging.Formatter): + """Custom formatter for structured JSON logging.""" + + def format(self, record: logging.LogRecord) -> str: + log_data: Dict[str, Any] = { + "timestamp": datetime.utcnow().isoformat() + "Z", + "level": record.levelname, + "logger": record.name, + "message": record.getMessage(), + "module": record.module, + "function": record.funcName, + "line": record.lineno, + } + + # Add extra fields if present + if hasattr(record, "extra"): + log_data.update(record.extra) + + # Add exception info if present + if record.exc_info: + log_data["exception"] = self.formatException(record.exc_info) + + # Add trace ID if present (for distributed tracing) + if hasattr(record, "trace_id"): + log_data["trace_id"] = record.trace_id + + return json.dumps(log_data) + +# Configure logging +def setup_logging(level: str = "INFO"): + """Set up structured logging configuration.""" + handler = logging.StreamHandler() + handler.setFormatter(StructuredFormatter()) + + logging.basicConfig( + level=getattr(logging, level.upper()), + handlers=[handler] + ) + +# Usage +setup_logging("INFO") +logger = logging.getLogger(__name__) + +logger.info( + "User logged in", + extra={ + "user_id": "usr_123", + "ip_address": "192.168.1.1", + "user_agent": "Mozilla/5.0...", + } +) +``` + +**Example Output:** +```json +{ + "timestamp": "2024-01-15T10:30:45.123456Z", + "level": "INFO", + "logger": "app.auth", + "message": "User logged in", + "module": "auth", + "function": "login", + "line": 42, + "user_id": "usr_123", + "ip_address": "192.168.1.1", + "user_agent": "Mozilla/5.0..." +} +``` + +## What to Log and What Not to Log + +### Rule 3: Log Important Events but Never Log Sensitive Data +Log business-critical events and errors, but never log passwords, tokens, credit cards, or other sensitive information. + +**Example:** +```python +# ❌ Bad - Logging sensitive data +logger.info(f"User login attempt: {username}, password: {password}") +logger.debug(f"API request with token: {api_token}") +logger.info(f"Processing payment for card: {credit_card_number}") + +# ✅ Good - Logging without sensitive data +logger.info( + "User login attempt", + extra={ + "username": username, + "ip_address": request.ip, + "user_agent": request.headers.get("User-Agent") + } +) + +logger.debug( + "API request authenticated", + extra={ + "token_prefix": api_token[:8] + "...", # Only log prefix + "user_id": user_id, + "endpoint": request.path + } +) + +logger.info( + "Processing payment", + extra={ + "order_id": order_id, + "amount": amount, + "card_last_four": credit_card_number[-4:], # Only last 4 digits + "card_type": card_type + } +) +``` + +**What to Log:** +- Application startup and shutdown +- User authentication events (login, logout, failed attempts) +- Business transactions (orders, payments, transfers) +- External API calls and responses (without sensitive data) +- Database connection issues +- Configuration changes +- Performance metrics +- Error conditions and exceptions + +**What NOT to Log:** +- Passwords (plain text or hashed) +- API keys, tokens, secrets +- Credit card numbers (except last 4 digits) +- Social security numbers +- Personal health information +- Session IDs +- Encryption keys +- Full request/response bodies containing sensitive data + +## Log Aggregation and Monitoring + +### Rule 4: Implement Centralized Logging and Monitoring +Use centralized logging systems and set up alerts for critical errors. + +**Example - Python with Correlation IDs:** +```python +import uuid +from contextvars import ContextVar +from typing import Optional + +# Context variable for request correlation ID +correlation_id_var: ContextVar[Optional[str]] = ContextVar("correlation_id", default=None) + +class CorrelationIdFilter(logging.Filter): + """Add correlation ID to all log records.""" + + def filter(self, record: logging.LogRecord) -> bool: + correlation_id = correlation_id_var.get() + record.correlation_id = correlation_id or "no-correlation-id" + return True + +# Middleware to set correlation ID +async def correlation_id_middleware(request: Request, call_next): + """Middleware to add correlation ID to each request.""" + correlation_id = request.headers.get("X-Correlation-ID") or str(uuid.uuid4()) + correlation_id_var.set(correlation_id) + + response = await call_next(request) + response.headers["X-Correlation-ID"] = correlation_id + + return response + +# Add filter to logger +logger = logging.getLogger(__name__) +logger.addFilter(CorrelationIdFilter()) + +# Now all logs will include correlation_id +logger.info("Processing request") # Will include correlation_id in structured output +``` + +### Rule 5: Configure Log Rotation and Retention +Implement log rotation to prevent disk space issues and define retention policies. + +**Example - Python:** +```python +from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler +import logging + +# Size-based rotation +size_handler = RotatingFileHandler( + filename="app.log", + maxBytes=10 * 1024 * 1024, # 10 MB + backupCount=5, # Keep 5 backup files + encoding="utf-8" +) + +# Time-based rotation +time_handler = TimedRotatingFileHandler( + filename="app.log", + when="midnight", # Rotate at midnight + interval=1, # Every day + backupCount=30, # Keep 30 days of logs + encoding="utf-8" +) + +logger = logging.getLogger(__name__) +logger.addHandler(size_handler) +logger.addHandler(time_handler) +``` + +**Example - Docker/Kubernetes:** +```yaml +# docker-compose.yml +services: + app: + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" +``` + diff --git a/.augment/rules/rest-api-rules.md b/.augment/rules/rest-api-rules.md new file mode 100644 index 0000000..6dfa201 --- /dev/null +++ b/.augment/rules/rest-api-rules.md @@ -0,0 +1,9 @@ +--- +type: "agent_requested" +description: "REST API Best Practices" +--- + +## REST API Best Practices + - When implementing API endpoints always add OpenAPI/Swagger documentation with detailed descriptions and examples. + - Add recommended security and rate limiting to all API endpoints. + - Avoid long running requests - prefer asynchronous processing and polling for results. \ No newline at end of file diff --git a/.augment/rules/security-rules.md b/.augment/rules/security-rules.md new file mode 100644 index 0000000..7215e89 --- /dev/null +++ b/.augment/rules/security-rules.md @@ -0,0 +1,321 @@ +# Security Best Practices + +## Input Validation and Sanitization + +### Rule 1: Validate and Sanitize All User Input +Never trust user input. Always validate, sanitize, and use parameterized queries to prevent injection attacks. + +**Example - SQL Injection Prevention:** +```python +# ❌ Bad - Vulnerable to SQL injection +def get_user(username: str): + query = f"SELECT * FROM users WHERE username = '{username}'" + return db.execute(query) + +# ✅ Good - Using parameterized queries +def get_user(username: str) -> Optional[User]: + query = "SELECT * FROM users WHERE username = :username" + return db.execute(query, {"username": username}).first() + +# ✅ Good - Using ORM +from sqlalchemy import select + +def get_user(username: str) -> Optional[User]: + stmt = select(User).where(User.username == username) + return db.session.execute(stmt).scalar_one_or_none() +``` + +**Example - Input Validation:** +```python +from pydantic import BaseModel, EmailStr, validator, Field +from typing import Optional +import re + +class UserRegistration(BaseModel): + username: str = Field(..., min_length=3, max_length=50) + email: EmailStr + password: str = Field(..., min_length=12) + phone: Optional[str] = None + + @validator('username') + def validate_username(cls, v): + # Only allow alphanumeric and underscores + if not re.match(r'^[a-zA-Z0-9_]+$', v): + raise ValueError('Username must contain only letters, numbers, and underscores') + return v + + @validator('password') + def validate_password_strength(cls, v): + # Require at least one uppercase, lowercase, digit, and special char + if not re.search(r'[A-Z]', v): + raise ValueError('Password must contain at least one uppercase letter') + if not re.search(r'[a-z]', v): + raise ValueError('Password must contain at least one lowercase letter') + if not re.search(r'\d', v): + raise ValueError('Password must contain at least one digit') + if not re.search(r'[!@#$%^&*(),.?":{}|<>]', v): + raise ValueError('Password must contain at least one special character') + return v + + @validator('phone') + def validate_phone(cls, v): + if v is not None: + # Remove common formatting characters + cleaned = re.sub(r'[\s\-\(\)]', '', v) + if not re.match(r'^\+?[1-9]\d{9,14}$', cleaned): + raise ValueError('Invalid phone number format') + return v +``` + +**Example - XSS Prevention:** +```typescript +// ❌ Bad - Vulnerable to XSS +function displayUserComment(comment: string) { + document.getElementById('comment').innerHTML = comment; +} + +// ✅ Good - Using textContent +function displayUserComment(comment: string) { + const element = document.getElementById('comment'); + if (element) { + element.textContent = comment; + } +} + +// ✅ Good - Using React (auto-escapes) +const CommentDisplay = ({ comment }: { comment: string }) => { + return
{comment}
; +}; + +// ✅ Good - If HTML is needed, use sanitization library +import DOMPurify from 'dompurify'; + +function displaySanitizedHTML(html: string) { + const clean = DOMPurify.sanitize(html, { + ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'], + ALLOWED_ATTR: ['href'] + }); + return
; +} +``` + +## Authentication and Authorization + +### Rule 2: Implement Proper Authentication and Authorization +Use established authentication mechanisms and always verify authorization before granting access to resources. + +**Example - JWT Authentication:** +```python +from datetime import datetime, timedelta +from typing import Optional +import jwt +from passlib.context import CryptContext + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +SECRET_KEY = os.getenv("JWT_SECRET_KEY") # Never hardcode! +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +def verify_password(plain_password: str, hashed_password: str) -> bool: + """Verify a password against its hash.""" + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password: str) -> str: + """Hash a password for storage.""" + return pwd_context.hash(password) + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: + """Create a JWT access token.""" + to_encode = data.copy() + expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + +def verify_token(token: str) -> Optional[dict]: + """Verify and decode a JWT token.""" + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + return payload + except jwt.ExpiredSignatureError: + return None + except jwt.JWTError: + return None +``` + +**Example - Authorization Decorator:** +```python +from functools import wraps +from fastapi import HTTPException, status, Depends +from typing import List + +def require_roles(allowed_roles: List[str]): + """Decorator to check if user has required roles.""" + def decorator(func): + @wraps(func) + async def wrapper(*args, current_user: User = Depends(get_current_user), **kwargs): + if not any(role in current_user.roles for role in allowed_roles): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Insufficient permissions" + ) + return await func(*args, current_user=current_user, **kwargs) + return wrapper + return decorator + +# Usage +@app.delete("/users/{user_id}") +@require_roles(["admin", "user_manager"]) +async def delete_user(user_id: str, current_user: User = Depends(get_current_user)): + """Delete a user (admin only).""" + await user_service.delete_user(user_id) +``` + +## Secrets Management + +### Rule 3: Never Hardcode Secrets - Use Environment Variables or Secret Managers +Never commit secrets to version control. Use environment variables or dedicated secret management services. + +**Example:** +```python +# ❌ Bad - Hardcoded secrets +DATABASE_URL = "postgresql://admin:password123@localhost/mydb" +API_KEY = "sk_live_abc123xyz789" + +# ✅ Good - Using environment variables +import os +from typing import Optional + +def get_env_variable(var_name: str, default: Optional[str] = None) -> str: + """Get environment variable or raise error if not found.""" + value = os.getenv(var_name, default) + if value is None: + raise ValueError(f"Required environment variable {var_name} is not set") + return value + +DATABASE_URL = get_env_variable("DATABASE_URL") +API_KEY = get_env_variable("API_KEY") +SECRET_KEY = get_env_variable("SECRET_KEY") + +# ✅ Good - Using pydantic settings +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + database_url: str + api_key: str + secret_key: str + debug: bool = False + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + +settings = Settings() +``` + +**Example - .env file (never commit this!):** +```bash +# .env (add to .gitignore!) +DATABASE_URL=postgresql://user:pass@localhost/db +API_KEY=sk_live_abc123xyz789 +SECRET_KEY=your-secret-key-here +DEBUG=false +``` + +**Example - .gitignore:** +``` +# Environment variables +.env +.env.local +.env.*.local + +# Secrets +secrets/ +*.key +*.pem +credentials.json +``` + +## Dependency Security + +### Rule 4: Regularly Scan and Update Dependencies +Keep dependencies up to date and scan for known vulnerabilities regularly. + +**Example - Python:** +```bash +# Install security scanning tools +uv pip install safety pip-audit + +# Scan for known vulnerabilities +safety check +pip-audit + +# Update dependencies +uv pip install --upgrade package-name + +# Use dependabot or renovate for automated updates +``` + +**Example - GitHub Dependabot configuration:** +```yaml +# .github/dependabot.yml +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + reviewers: + - "security-team" + labels: + - "dependencies" + - "security" +``` + +**Example - Node.js:** +```bash +# Audit dependencies +npm audit +npm audit fix + +# Use specific versions in package.json +{ + "dependencies": { + "express": "4.18.2", # Exact version + "lodash": "~4.17.21" # Patch updates only + } +} +``` + +### Rule 5: Implement Rate Limiting and Request Validation +Protect APIs from abuse with rate limiting and request size limits. + +**Example:** +```python +from fastapi import FastAPI, Request, HTTPException +from slowapi import Limiter, _rate_limit_exceeded_handler +from slowapi.util import get_remote_address +from slowapi.errors import RateLimitExceeded + +limiter = Limiter(key_func=get_remote_address) +app = FastAPI() +app.state.limiter = limiter +app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) + +@app.post("/api/login") +@limiter.limit("5/minute") # Max 5 login attempts per minute +async def login(request: Request, credentials: LoginCredentials): + """Login endpoint with rate limiting.""" + user = await authenticate_user(credentials.username, credentials.password) + if not user: + raise HTTPException(status_code=401, detail="Invalid credentials") + return create_access_token({"sub": user.id}) + +@app.post("/api/data") +@limiter.limit("100/hour") # Max 100 requests per hour +async def create_data(request: Request, data: DataModel): + """Create data with rate limiting.""" + return await data_service.create(data) +``` +