316 lines
No EOL
8.5 KiB
Markdown
316 lines
No EOL
8.5 KiB
Markdown
# 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 |