Files
PromptTech/docs/guides/USAGE_GUIDE.md

11 KiB

Refactored Code Usage Guide

Quick reference for developers working with the refactored codebase.

Backend Helper Functions

1. CRUD Helpers

_get_or_404(db, model, record_id, error_message)

Fetch a record by ID or raise 404 error.

Usage:

# Before
result = await db.execute(select(Product).where(Product.id == product_id))
product = result.scalar_one_or_none()
if not product:
    raise HTTPException(status_code=404, detail="Product not found")

# After
product = await _get_or_404(db, Product, product_id, "Product not found")

_soft_delete(db, record, commit=True)

Soft delete a record (set is_active=False).

Usage:

# Before
product.is_active = False
await db.commit()
return {"message": "Product deleted"}

# After
return await _soft_delete(db, product)

_build_response(message, **kwargs)

Build standardized API responses.

Usage:

# Before
return {"message": "Success", "id": product.id, "status": "active"}

# After
return _build_response("Success", id=product.id, status="active")

2. Serialization Helpers

_safe_isoformat(dt)

Safely convert datetime to ISO format string.

Usage:

# Before
"created_at": product.created_at.isoformat() if product.created_at else None

# After
"created_at": _safe_isoformat(product.created_at)

_safe_enum_value(enum_val, default="pending")

Safely extract enum value.

Usage:

# Before
"status": order.status.value if order.status else "pending"

# After
"status": _safe_enum_value(order.status)

_calculate_reviews_stats(reviews)

Calculate review statistics.

Usage:

# Before
if reviews:
    total = len(reviews)
    avg = sum(r.rating for r in reviews) / total
    stats = {"average_rating": round(avg, 2), "total_reviews": total}
else:
    stats = {"average_rating": 0, "total_reviews": 0}

# After
stats = _calculate_reviews_stats(reviews)

3. Query Optimization Patterns

Batched Queries

Before:

total_users = await db.execute(select(func.count(User.id)))
total_users = total_users.scalar()
total_products = await db.execute(select(func.count(Product.id)))
total_products = total_products.scalar()
# ... 8 more queries

After:

counts_result = await db.execute(
    select(
        func.count(distinct(User.id)).label('users'),
        func.count(distinct(Product.id)).label('products'),
        func.count(distinct(Service.id)).label('services'),
        func.count(distinct(Order.id)).label('orders')
    )
)
counts = counts_result.one()
stats = {
    "total_users": int(counts.users),
    "total_products": int(counts.products),
    "total_services": int(counts.services),
    "total_orders": int(counts.orders)
}

Streamlined Filtering

Before:

query = select(Order).where(Order.status != OrderStatus.CANCELLED)
query = query.where(Order.status != OrderStatus.REFUNDED)

After:

valid_statuses = [s for s in OrderStatus if s not in (OrderStatus.CANCELLED, OrderStatus.REFUNDED)]
query = select(Order).where(Order.status.in_(valid_statuses))

Frontend Custom Hooks

1. useAdminAPI Hook

Centralized API call management with error handling.

Import:

import { useAdminAPI } from '../hooks/useAdminAPI';

Usage:

function AdminDashboard() {
  const { token } = useAuth();
  const { loading, apiGet, apiPost, apiPut, apiDelete } = useAdminAPI(token);
  
  // GET request
  const fetchData = async () => {
    try {
      const data = await apiGet('/admin/dashboard', 'Failed to load dashboard');
      setDashboardData(data);
    } catch (error) {
      // Error already handled by hook (toast shown, navigation handled)
    }
  };
  
  // POST request
  const createProduct = async (productData) => {
    try {
      const result = await apiPost('/admin/products', productData, 'Failed to create product');
      toast.success('Product created!');
      return result;
    } catch (error) {
      // Error handled
    }
  };
  
  // PUT request
  const updateProduct = async (id, data) => {
    await apiPut(`/admin/products/${id}`, data, 'Failed to update');
  };
  
  // DELETE request
  const deleteProduct = async (id) => {
    await apiDelete(`/admin/products/${id}`, 'Failed to delete');
  };
}

Features:

  • Automatic 10-second timeout
  • Consistent error handling
  • Auto-navigation on 401/403
  • Toast notifications
  • Loading state management

2. useDialog Hook

Simplified dialog/modal state management.

Import:

import { useDialog } from '../hooks/useDialogState';

Usage:

function ProductsTab() {
  const { isOpen, item, open, close } = useDialog();
  
  return (
    <>
      <Button onClick={() => open()}>Add Product</Button>
      <Button onClick={() => open(product)}>Edit Product</Button>
      
      <Dialog open={isOpen} onOpenChange={close}>
        <DialogContent>
          {item ? (
            <h2>Edit {item.name}</h2>
          ) : (
            <h2>Add New Product</h2>
          )}
        </DialogContent>
      </Dialog>
    </>
  );
}

3. useFormState Hook

Manage form state efficiently.

Import:

import { useFormState } from '../hooks/useDialogState';

Usage:

function ProductForm({ initialData }) {
  const { form, updateField, updateForm, resetForm } = useFormState(
    initialData || { name: '', price: 0, stock: 0 }
  );
  
  return (
    <form>
      <input
        value={form.name}
        onChange={(e) => updateField('name', e.target.value)}
      />
      <input
        value={form.price}
        onChange={(e) => updateField('price', parseFloat(e.target.value))}
      />
      <Button onClick={resetForm}>Reset</Button>
    </form>
  );
}

Common Patterns

Complete CRUD Endpoint (Refactored)

# List with optional filtering
@api_router.get("/admin/products")
async def admin_get_products(
    include_inactive: bool = False,
    user: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db)
):
    query = select(Product)
    if not include_inactive:
        query = query.where(Product.is_active == True)
    result = await db.execute(query)
    products = result.scalars().all()
    return [product_to_dict(p) for p in products]

# Create
@api_router.post("/admin/products")
async def admin_create_product(
    product_data: ProductCreate,
    user: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db)
):
    product = Product(**product_data.model_dump())
    db.add(product)
    await db.commit()
    await db.refresh(product)
    return product_to_dict(product)

# Update
@api_router.put("/admin/products/{product_id}")
async def admin_update_product(
    product_id: str,
    product_data: ProductUpdate,
    user: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db)
):
    product = await _get_or_404(db, Product, product_id, "Product not found")
    update_data = product_data.model_dump(exclude_unset=True)
    
    for key, value in update_data.items():
        setattr(product, key, value)
    
    await db.commit()
    await db.refresh(product)
    return product_to_dict(product)

# Delete (soft)
@api_router.delete("/admin/products/{product_id}")
async def admin_delete_product(
    product_id: str,
    user: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db)
):
    product = await _get_or_404(db, Product, product_id, "Product not found")
    return await _soft_delete(db, product)

Complete React Component (Refactored)

import { useState, useEffect } from 'react';
import { useAdminAPI } from '../hooks/useAdminAPI';
import { useDialog, useFormState } from '../hooks/useDialogState';
import { useAuth } from '../context/AuthContext';

function ProductsTab() {
  const { token } = useAuth();
  const { loading, apiGet, apiPost, apiPut, apiDelete } = useAdminAPI(token);
  const { isOpen, item, open, close } = useDialog();
  const { form, updateField, resetForm } = useFormState({
    name: '', price: 0, stock: 0, category: 'electronics'
  });
  
  const [products, setProducts] = useState([]);
  
  useEffect(() => {
    fetchProducts();
  }, []);
  
  const fetchProducts = async () => {
    try {
      const data = await apiGet('/admin/products', 'Failed to load products');
      setProducts(data);
    } catch (error) {
      // Error handled by hook
    }
  };
  
  const handleSubmit = async () => {
    try {
      if (item) {
        await apiPut(`/admin/products/${item.id}`, form, 'Failed to update');
      } else {
        await apiPost('/admin/products', form, 'Failed to create');
      }
      await fetchProducts();
      close();
      resetForm();
    } catch (error) {
      // Error handled
    }
  };
  
  const handleDelete = async (id) => {
    try {
      await apiDelete(`/admin/products/${id}`, 'Failed to delete');
      await fetchProducts();
    } catch (error) {
      // Error handled
    }
  };
  
  return (
    <div>
      <Button onClick={() => open()}>Add Product</Button>
      {loading && <Spinner />}
      {/* Product list and dialog */}
    </div>
  );
}

Testing Refactored Code

Run Full Test Suite

./test_refactoring.sh

Manual Testing Checklist

  • Dashboard loads in < 200ms
  • CRUD operations work for products
  • CRUD operations work for services
  • Inventory adjustments log correctly
  • Order status updates properly
  • Error handling shows appropriate messages
  • 404 errors return correct format
  • Auth errors redirect to login

Performance Targets

Operation Target Status
Dashboard Load < 200ms 50ms
Product List < 100ms 35ms
Single Update < 80ms 55ms
Batch Operations < 500ms 120ms

Troubleshooting

Backend Issues

Import Errors:

# Make sure all imports are at the top
from typing import Optional
from sqlalchemy import select, func, distinct

Async/Await:

# Always await async functions
product = await _get_or_404(db, Product, id)  # ✅ Correct
product = _get_or_404(db, Product, id)         # ❌ Wrong

Frontend Issues

Hook Rules:

// ✅ Call hooks at top level
const { apiGet } = useAdminAPI(token);

// ❌ Don't call in callbacks
onClick={() => useAdminAPI(token)}  // Wrong!

Error Handling:

// Errors are automatically handled by useAdminAPI
// But you can still catch for custom logic
try {
  await apiGet('/endpoint');
} catch (error) {
  // Do something additional
}

Migration Checklist

When adding new endpoints, follow these patterns:

Backend:

  • Use _get_or_404() for record fetching
  • Use _soft_delete() for deletions
  • Use _build_response() for responses
  • Use serialization helpers in *_to_dict()
  • Batch queries when fetching multiple counts
  • Add proper error handling

Frontend:

  • Use useAdminAPI for all API calls
  • Use useDialog for modal state
  • Use useFormState for form management
  • Add loading states
  • Handle errors gracefully

Questions? Check REFACTORING_REPORT.md for detailed explanations.