Skip to content

backend

Mile Shi edited this page May 26, 2025 · 1 revision

Backend Development Guide

This guide covers backend development for the Intelligent IDE project, built with FastAPI and modern Python practices.

Backend Overview

The backend is a FastAPI-based REST API server that provides:

  • User authentication and management
  • Course creation and management
  • File storage and organization
  • Real-time collaboration features
  • Assignment and homework systems
  • Chat functionality

Technology Stack

  • Framework: FastAPI 0.104+
  • Language: Python 3.11+
  • Database: PostgreSQL 13+
  • Cache: Redis 6+
  • ORM: SQLAlchemy 2.0+
  • Validation: Pydantic 2.0+
  • Authentication: JWT tokens
  • Containerization: Docker

Project Structure

Based on the actual backend structure:

backend/intellide/
├── main.py                    # FastAPI application entry point
├── config.py                  # Configuration management
├── cache/                     # Redis cache implementation
│   ├── __init__.py
│   ├── cache.py              # Cache operations
│   └── startup.py            # Cache initialization
├── database/                  # Database layer
│   ├── __init__.py
│   ├── database.py           # Database connection
│   ├── model.py              # SQLAlchemy models
│   └── startup.py            # Database initialization
├── docker/                    # Docker management
│   ├── __init__.py
│   └── startup.py            # Docker service startup
├── routers/                   # API route handlers
│   ├── __init__.py
│   ├── course.py             # Course management
│   ├── course_chat.py        # Chat functionality
│   ├── course_directory.py   # File management
│   ├── course_directory_entry.py  # File entries
│   ├── course_student.py     # Student enrollment
│   ├── surprise.py           # Special features
│   └── user.py               # User management
├── storage/                   # File storage
│   ├── __init__.py
│   ├── startup.py            # Storage initialization
│   └── storage.py            # Storage operations
├── tests/                     # Test suite
│   ├── __init__.py
│   ├── conftest.py           # Test configuration
│   ├── pytest.ini           # Pytest settings
│   ├── test_course.py        # Course tests
│   ├── test_user.py          # User tests
│   └── utils.py              # Test utilities
└── utils/                     # Utility functions
    ├── __init__.py
    ├── auth.py               # Authentication helpers
    ├── email.py              # Email services
    ├── path.py               # Path utilities
    ├── response.py           # Response formatting
    └── websocket.py          # WebSocket utilities

Getting Started

Prerequisites

  1. Python 3.11+: Ensure Python is installed
  2. Docker: Required for PostgreSQL and Redis
  3. pip: For package management

Setup Instructions

  1. Navigate to Backend Directory

    cd backend/deploy
  2. Install Dependencies

    pip install -r requirements.txt
  3. Configure Docker

    • Enable Docker daemon exposure on localhost:2375
    • Start Docker Desktop
  4. Start Services

    # macOS/Linux
    sh backend.sh
    
    # Windows
    backend.bat
  5. Verify Installation

API Development

Creating New Endpoints

1. Define Data Models

Create Pydantic models for request/response validation:

# In routers/your_feature.py
from pydantic import BaseModel
from typing import Optional

class FeatureCreate(BaseModel):
    name: str
    description: Optional[str] = None
    
class FeatureResponse(BaseModel):
    id: int
    name: str
    description: Optional[str]
    created_at: datetime
    
    class Config:
        from_attributes = True

2. Create Database Models

Define SQLAlchemy models in database/model.py:

# Add to database/model.py
class Feature(Base):
    __tablename__ = "features"
    
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    description = Column(String, nullable=True)
    created_at = Column(DateTime, default=datetime.utcnow)
    
    # Add relationships if needed
    user_id = Column(Integer, ForeignKey("users.id"))
    user = relationship("User", back_populates="features")

3. Implement Route Handler

Create API endpoints in routers/:

# routers/your_feature.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from database.database import get_db
from utils.auth import get_current_user

router = APIRouter(
    prefix="/api/feature",
    tags=["features"]
)

@router.post("/", response_model=FeatureResponse)
async def create_feature(
    feature: FeatureCreate,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    # Implementation logic
    db_feature = Feature(**feature.dict(), user_id=current_user.id)
    db.add(db_feature)
    db.commit()
    db.refresh(db_feature)
    return db_feature

@router.get("/{feature_id}", response_model=FeatureResponse)
async def get_feature(
    feature_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    feature = db.query(Feature).filter(Feature.id == feature_id).first()
    if not feature:
        raise HTTPException(status_code=404, detail="Feature not found")
    return feature

4. Register Router

Add the router to main.py:

# In main.py
from routers import your_feature

app.include_router(your_feature.router)

Authentication and Authorization

JWT Token Authentication

The system uses JWT tokens for authentication:

# utils/auth.py implementation
from jose import JWTError, jwt
from datetime import datetime, timedelta

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(hours=24)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db)
):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=401, detail="Invalid token")
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")
    
    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise HTTPException(status_code=401, detail="User not found")
    return user

Using Authentication in Routes

@router.get("/protected")
async def protected_route(
    current_user: User = Depends(get_current_user)
):
    return {"message": f"Hello {current_user.username}"}

Database Operations

Database Connection

# database/database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "postgresql://user:password@localhost/dbname"

engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

CRUD Operations

# Example CRUD operations
def create_course(db: Session, course: CourseCreate, user_id: int):
    db_course = Course(**course.dict(), creator_id=user_id)
    db.add(db_course)
    db.commit()
    db.refresh(db_course)
    return db_course

def get_courses(db: Session, skip: int = 0, limit: int = 100):
    return db.query(Course).offset(skip).limit(limit).all()

def get_course(db: Session, course_id: int):
    return db.query(Course).filter(Course.id == course_id).first()

def update_course(db: Session, course_id: int, course: CourseUpdate):
    db.query(Course).filter(Course.id == course_id).update(course.dict())
    db.commit()
    return db.query(Course).filter(Course.id == course_id).first()

Caching with Redis

Cache Implementation

# cache/cache.py
import redis
import json
from typing import Any, Optional

class CacheService:
    def __init__(self):
        self.redis_client = redis.Redis(host='localhost', port=6379, db=0)
    
    def set(self, key: str, value: Any, expire: int = 3600):
        """Set a value in cache with expiration."""
        serialized = json.dumps(value)
        self.redis_client.setex(key, expire, serialized)
    
    def get(self, key: str) -> Optional[Any]:
        """Get a value from cache."""
        value = self.redis_client.get(key)
        if value:
            return json.loads(value)
        return None
    
    def delete(self, key: str):
        """Delete a key from cache."""
        self.redis_client.delete(key)

Using Cache in Routes

from cache.cache import CacheService

cache = CacheService()

@router.get("/courses/{course_id}")
async def get_course_cached(course_id: int, db: Session = Depends(get_db)):
    # Check cache first
    cached_course = cache.get(f"course:{course_id}")
    if cached_course:
        return cached_course
    
    # If not in cache, query database
    course = db.query(Course).filter(Course.id == course_id).first()
    if course:
        # Cache the result
        cache.set(f"course:{course_id}", course.dict(), expire=1800)
    
    return course

File Storage

File Upload Handler

# storage/storage.py
import os
import uuid
from fastapi import UploadFile

class StorageService:
    def __init__(self, base_path: str = "./storage"):
        self.base_path = base_path
        os.makedirs(base_path, exist_ok=True)
    
    async def save_file(self, file: UploadFile, subdir: str = "") -> str:
        """Save uploaded file and return file path."""
        file_id = str(uuid.uuid4())
        dir_path = os.path.join(self.base_path, subdir)
        os.makedirs(dir_path, exist_ok=True)
        
        file_path = os.path.join(dir_path, file_id)
        
        with open(file_path, "wb") as f:
            content = await file.read()
            f.write(content)
        
        return file_id
    
    def get_file_path(self, file_id: str, subdir: str = "") -> str:
        """Get full path for a file."""
        return os.path.join(self.base_path, subdir, file_id)

File Upload Endpoint

@router.post("/upload")
async def upload_file(
    file: UploadFile = File(...),
    course_id: int = Form(...),
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    storage = StorageService()
    file_id = await storage.save_file(file, f"course_{course_id}")
    
    # Save file metadata to database
    db_file = CourseDirectoryEntry(
        course_id=course_id,
        name=file.filename,
        file_id=file_id,
        uploaded_by=current_user.id
    )
    db.add(db_file)
    db.commit()
    
    return {"file_id": file_id, "filename": file.filename}

WebSocket Implementation

WebSocket Manager

# utils/websocket.py
from typing import List
from fastapi import WebSocket

class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []
    
    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
    
    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
    
    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)
    
    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

WebSocket Endpoint

@router.websocket("/ws/course/{course_id}")
async def websocket_endpoint(
    websocket: WebSocket,
    course_id: int
):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            # Process message and broadcast to course participants
            await manager.broadcast(f"Course {course_id}: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)

Testing

Test Setup

# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from fastapi.testclient import TestClient

from main import app
from database.database import get_db, Base

SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def override_get_db():
    try:
        db = TestingSessionLocal()
        yield db
    finally:
        db.close()

app.dependency_overrides[get_db] = override_get_db

@pytest.fixture
def client():
    Base.metadata.create_all(bind=engine)
    with TestClient(app) as c:
        yield c
    Base.metadata.drop_all(bind=engine)

Writing Tests

# tests/test_user.py
def test_create_user(client):
    response = client.post(
        "/api/user/register",
        json={
            "username": "testuser",
            "email": "testuser@testdomain.local",
            "password": "testpassword",
            "role": "student"
        }
    )
    assert response.status_code == 200
    data = response.json()
    assert data["username"] == "testuser"
    assert "access_token" in data

def test_login_user(client):
    # First create a user
    client.post("/api/user/register", json={...})
    
    # Then test login
    response = client.post(
        "/api/user/login",
        json={
            "username": "testuser",
            "password": "testpassword"
        }
    )
    assert response.status_code == 200
    data = response.json()
    assert "access_token" in data

Running Tests

# Run all tests
python -m pytest

# Run specific test file
python -m pytest tests/test_user.py

# Run with coverage
python -m pytest --cov=intellide tests/

Configuration Management

Environment Configuration

# config.py
import os
from pydantic import BaseSettings

class Settings(BaseSettings):
    database_url: str = "postgresql://user:password@localhost/intellide"
    redis_url: str = "redis://localhost:6379"
    secret_key: str = "your-secret-key"
    email_host: str = "smtp.gmail.com"
    email_port: int = 587
    email_username: str = ""
    email_password: str = ""
    
    class Config:
        env_file = ".env"

settings = Settings()

Error Handling

Custom Exception Handler

from fastapi import HTTPException
from fastapi.responses import JSONResponse

@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
    return JSONResponse(
        status_code=exc.status_code,
        content={"message": exc.detail, "status": "error"}
    )

Performance Optimization

Database Query Optimization

# Use select_related for eager loading
def get_course_with_students(db: Session, course_id: int):
    return db.query(Course)\
        .options(joinedload(Course.students))\
        .filter(Course.id == course_id)\
        .first()

# Use pagination for large datasets
def get_courses_paginated(db: Session, skip: int = 0, limit: int = 20):
    return db.query(Course)\
        .offset(skip)\
        .limit(limit)\
        .all()

Response Caching

from functools import lru_cache

@lru_cache(maxsize=128)
def get_course_metadata(course_id: int):
    # Expensive computation
    return compute_metadata(course_id)

This comprehensive backend development guide provides the foundation for contributing to and extending the Intelligent IDE backend system.

Clone this wiki locally