> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enferno.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Development

> Development guidelines and best practices for Enferno

## Project Structure

```
enferno/
├── enferno/                # Main application package
│   ├── app.py             # Application factory
│   ├── settings.py        # Configuration
│   ├── extensions.py      # Flask extensions
│   ├── commands.py        # CLI commands
│   ├── portal/            # Blueprint: Protected routes
│   ├── public/            # Blueprint: Public routes
│   ├── user/              # Blueprint: User management
│   ├── tasks/             # Background tasks (optional Celery)
│   ├── utils/             # Utility functions
│   ├── static/            # Static assets (Vue, Vuetify, CSS)
│   └── templates/         # Jinja2 templates
├── docs/                  # Documentation
├── instance/             # SQLite database
├── pyproject.toml        # Dependencies and project config
├── setup.sh              # Setup script
├── run.py                # Entry point
├── AGENTS.md             # AI coding instructions
└── docker-compose.yml    # Docker configuration
```

## Blueprints

Enferno uses a three-blueprint architecture for better organization and security:

### 1. Portal Blueprint (`portal/`)

Usually contains protected routes that require authentication. Enferno uses a pattern of protecting all routes in this blueprint automatically using `before_request`:

```python theme={null}
from flask import Blueprint
from flask_security import auth_required

portal = Blueprint('portal', __name__)

# Protect all routes in this blueprint automatically
@portal.before_request
@auth_required()
def before_request():
    pass

@portal.route('/dashboard')
def dashboard():
    return render_template('portal/dashboard.html')

@portal.route('/settings')
def settings():
    return render_template('portal/settings.html')
```

This pattern ensures that all routes within the portal blueprint require authentication without needing to decorate each route individually.

### 2. User Blueprint (`user/`)

Handles user management, authentication, and profile-related routes:

```python theme={null}
from flask import Blueprint
from flask_security import auth_required

user = Blueprint('user', __name__)

@user.route('/profile')
@auth_required()
def profile():
    return render_template('user/profile.html')
```

### 3. Public Blueprint (`public/`)

Contains routes that are publicly accessible without authentication:

```python theme={null}
from flask import Blueprint

public = Blueprint('public', __name__)

@public.route('/')
def index():
    return render_template('public/index.html')
```

## Database Operations

Enferno uses SQLAlchemy 2.x with the Flask-SQLAlchemy extension. The `db` instance is available from `enferno.extensions`.

### Model Definition

```python theme={null}
from enferno.extensions import db
from datetime import datetime

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(80), nullable=False)
    content = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship('User', back_populates='posts')
```

### Database Operations

```python theme={null}
from enferno.extensions import db
from enferno.models import Post
from sqlalchemy import select

# Create
post = Post(title='New Post', content='Content here')
db.session.add(post)
db.session.commit()

# Simple queries
stmt = db.session.select(Post)  # Select all posts
posts = db.session.scalars(stmt).all()

post = db.session.get(Post, 1)  # Get by ID

# Filtered query
stmt = db.session.select(Post).where(Post.title.like('%python%'))
python_posts = db.session.scalars(stmt).all()

# Ordered query with join
stmt = (
    db.session.select(Post)
    .join(Post.user)
    .order_by(Post.created_at.desc())
)
recent_posts = db.session.scalars(stmt).all()

# Update
post = db.session.get(Post, 1)
post.title = 'Updated Title'
db.session.commit()

# Delete
db.session.delete(post)
db.session.commit()
```

## Background Tasks (Optional)

Celery is available when you need async job processing. First, install the full extras:

```bash theme={null}
uv sync --extra full    # Adds Redis + Celery
```

Define tasks in `enferno/tasks/__init__.py`:

```python theme={null}
from enferno.tasks import celery

@celery.task
def send_email(user_id, subject, message):
    from enferno.extensions import db
    from enferno.user.models import User

    user = db.session.get(User, user_id)
    # Send email...
    return True
```

Call tasks asynchronously:

```python theme={null}
from enferno.tasks import send_email
send_email.delay(user.id, 'Welcome', 'Welcome to Enferno!')
```

Run Celery worker:

```bash theme={null}
uv run celery -A enferno.tasks worker --loglevel=info
```

## API Development

Add API endpoints to your blueprints:

```python theme={null}
from flask import Blueprint, jsonify
from enferno.extensions import db

api = Blueprint('api', __name__)

@api.route('/posts')
def get_posts():
    query = db.select(Post).order_by(Post.created_at.desc())
    posts = db.session.scalars(query).all()
    return jsonify([post.to_dict() for post in posts])
```

## Development Server

```bash theme={null}
uv run flask run              # Start server
uv run ruff check --fix .     # Lint and auto-fix
uv run ruff format .          # Format code
```

## Security Best Practices

1. **Input Validation**
   ```python theme={null}
   from flask_wtf import FlaskForm
   from wtforms import StringField
   from wtforms.validators import DataRequired, Length

   class PostForm(FlaskForm):
       title = StringField('Title', validators=[
           DataRequired(),
           Length(max=80)
       ])
   ```

2. **CSRF Protection**
   ```python theme={null}
   from flask_wtf.csrf import CSRFProtect
   csrf = CSRFProtect(app)
   ```

3. **Authentication**
   ```python theme={null}
   from flask_security import auth_required

   @app.route('/protected')
   @auth_required()
   def protected_route():
       return 'Protected content'
   ```

## Debugging

Enferno includes several debugging tools:

1. **Flask Debug Toolbar**
   ```python theme={null}
   from flask_debugtoolbar import DebugToolbarExtension
   toolbar = DebugToolbarExtension(app)
   ```

2. **Logging**
   ```python theme={null}
   import logging
   logging.basicConfig(level=logging.DEBUG)
   logger = logging.getLogger(__name__)
   ```

3. **Database Debugging**
   ```python theme={null}
   # Enable SQLAlchemy query logging
   logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
   ```
