kanban-app/docs/usage_rules_backend.md

316 lines
8.5 KiB
Markdown
Raw Normal View History

# Backend Development Rules for AI/LLM
This document provides guidelines and rules that AI/LLM assistants should follow when writing or modifying backend code for the Crafting Shop application.
## Architecture Principles
### Application Factory Pattern
- **ALWAYS** use the application factory pattern via `create_app()` in `backend/app/__init__.py`
- **NEVER** initialize Flask extensions globally - create them at module level, initialize in `create_app()`
- **NEVER** create multiple SQLAlchemy instances - use the `db` extension from `app/__init__.py`
```python
# ✅ CORRECT
from app import db
class User(db.Model):
pass
# ❌ WRONG
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
```
### Import Patterns
- Import `db` and other extensions from `app` module, not `flask_sqlalchemy` directly
- Import models from the `app.models` package (e.g., `from app.models import User`)
- All routes should import `db` from `app`
```python
# ✅ CORRECT
from app import db
from app.models import User, Product, Order
# ❌ WRONG
from flask_sqlalchemy import SQLAlchemy
from app.models.user import User # Don't import from individual files
```
## Code Style
### Formatting
- Use double quotes for strings and dictionary keys
- Follow PEP 8 style guide
- Use meaningful variable and function names
- Maximum line length: 100 characters
### Type Hints
- Add type hints to function signatures where appropriate
- Use Python 3.11+ type hinting features
## Model Rules
### Database Models
- **ALL** models must import `db` from `app`: `from app import db`
- **ALL** models must inherit from `db.Model`
- **NEVER** create your own `db = SQLAlchemy()` instance
- Use `__tablename__` explicitly
- Use `to_dict()` method for serialization
- Use `__repr__()` for debugging
```python
# ✅ CORRECT
from app import db
from datetime import datetime
class Product(db.Model):
__tablename__ = "products"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False, index=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {"id": self.id, "name": self.name}
```
### Relationships
- Use `back_populates` for bidirectional relationships
- Use `lazy="dynamic"` for one-to-many collections when appropriate
- Define foreign keys explicitly with `db.ForeignKey()`
## Route/API Rules
### Blueprint Usage
- **ALL** routes must be defined in blueprints
- Register blueprints in `create_app()` in `backend/app/__init__.py`
- Group related routes by functionality
### Request Handling
- Use `request.get_json()` for JSON payloads
- Always validate input data
- Return proper HTTP status codes
- Return JSON responses with `jsonify()`
```python
# ✅ CORRECT
from flask import Blueprint, request, jsonify
from app import db
api_bp = Blueprint("api", __name__)
@api_bp.route("/products", methods=["POST"])
@jwt_required()
def create_product():
data = request.get_json()
if not data or not data.get("name"):
return jsonify({"error": "Name is required"}), 400
product = Product(name=data["name"])
db.session.add(product)
db.session.commit()
return jsonify(product.to_dict()), 201
```
### Database Operations
- **NEVER** access `db` through `current_app.extensions['sqlalchemy'].db`
- **ALWAYS** import `db` from `app`
- Use `db.session.add()` and `db.session.commit()` for transactions
- Use `db.session.flush()` when you need the ID before commit
### Error Handling
- Handle common errors (404, 400, 401, 403, 500)
- Return JSON error responses with consistent format
- Use Flask error handlers where appropriate
```python
# ✅ CORRECT
@app.errorhandler(404)
def not_found(error):
return jsonify({"error": "Not found"}), 404
```
### Authentication
- Use `@jwt_required()` decorator for protected routes
- Use `get_jwt_identity()` to get current user ID
- Verify user permissions in routes
```python
# ✅ CORRECT
from flask_jwt_extended import jwt_required, get_jwt_identity
@api_bp.route("/orders", methods=["POST"])
@jwt_required()
def create_order():
user_id = get_jwt_identity()
# ... rest of code
```
## Configuration
### Environment Variables
- **ALL** configuration must use environment variables via `os.environ.get()`
- Provide sensible defaults in `app/config.py`
- Use `.env.example` for documentation
### CORS
- Configure CORS in `create_app()` using the `cors` extension
- Use `CORS_ORIGINS` environment variable
```python
# ✅ CORRECT
cors.init_app(app, resources={r"/api/*": {"origins": app.config.get('CORS_ORIGINS', '*')}})
```
## Service Layer Rules
### Business Logic
- Place complex business logic in `backend/app/services/`
- Services should be stateless functions or classes
- Services should import `db` from `app`
```python
# ✅ CORRECT
from app import db
from app.models import Product
def create_product_service(data):
product = Product(**data)
db.session.add(product)
db.session.commit()
return product
```
## Testing Rules
### Test Structure
- Use pytest framework
- Place tests in `backend/tests/`
- Use fixtures for common setup
### Database in Tests
- Use in-memory SQLite for tests
- Clean up database between tests
- Use `pytest.fixture` for database setup
## Security Rules
### Password Handling
- **NEVER** store plain text passwords
- Use `werkzeug.security` for password hashing
- Use `set_password()` and `check_password()` methods
### SQL Injection Prevention
- **ALWAYS** use SQLAlchemy ORM, never raw SQL
- Use parameterized queries if raw SQL is absolutely necessary
### Input Validation
- Validate all user inputs
- Sanitize data before database operations
- Use Flask-WTF for form validation
## File Structure for New Features
When adding new features, follow this structure:
```
backend/app/
├── models/
│ └── feature_name.py # Database models
├── routes/
│ └── feature_name.py # API routes
├── services/
│ └── feature_name.py # Business logic
└── utils/
└── feature_name.py # Utility functions
```
## Common Patterns
### Creating a New Model
```python
from app import db
from datetime import datetime
class NewModel(db.Model):
__tablename__ = "new_model"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200), nullable=False, index=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"created_at": self.created_at.isoformat() if self.created_at else None
}
```
### Creating New Routes
```python
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from app import db
from app.models import NewModel
new_bp = Blueprint("new", __name__)
@new_bp.route("/resources", methods=["GET"])
def get_resources():
resources = NewModel.query.all()
return jsonify([r.to_dict() for r in resources]), 200
@new_bp.route("/resources", methods=["POST"])
@jwt_required()
def create_resource():
data = request.get_json()
resource = NewModel(**data)
db.session.add(resource)
db.session.commit()
return jsonify(resource.to_dict()), 201
```
### Registering New Blueprints
In `backend/app/__init__.py`:
```python
# Import models
from app.models.new_model import NewModel
# Register blueprint
from app.routes.feature_name import new_bp
app.register_blueprint(new_bp, url_prefix="/api/new")
```
## DO NOT DO List
**NEVER** create `db = SQLAlchemy()` in model files
**NEVER** access `db` via `current_app.extensions['sqlalchemy'].db`
**NEVER** initialize Flask extensions before `create_app()` is called
**NEVER** use raw SQL queries without proper escaping
**NEVER** store secrets in code (use environment variables)
**NEVER** return plain Python objects (use `jsonify()`)
**NEVER** use single quotes for dictionary keys
**NEVER** commit transactions without proper error handling
**NEVER** ignore CORS configuration
**NEVER** skip input validation
## Checklist Before Committing
- [ ] All models import `db` from `app`
- [ ] All routes import `db` from `app`
- [ ] No `db = SQLAlchemy()` in model files
- [ ] All routes are in blueprints
- [ ] Proper error handling in place
- [ ] Environment variables used for configuration
- [ ] Type hints added to functions
- [ ] Tests written for new functionality
- [ ] Documentation updated
- [ ] Code follows PEP 8 style guide