linkbay-catalog 0.1.0
Product management system for e-commerce. Part of LinkBay CMS ecosystem.
License: MIT
Requires Python: >=3.8
Keywords: linkbay, catalog, ecommerce, product, cms, fastapi, sqlalchemy

# LinkBay Catalog

**Version**: 0.1.0

Libreria leggera per gestione catalogo prodotti e-commerce. Fornisce servizi, router FastAPI e operazioni bulk senza modelli hardcoded. Segue pattern dependency injection e service layer dell'ecosistema LinkBay CMS.

## Principali capacità

- Servizi dinamici (ProductService, CategoryService, InventoryService, VariantService)
- Router FastAPI automatici con CRUD completo
- Operazioni bulk (CSV import/export, aggiornamenti massa)
- Gestione inventario con prenotazioni e locking
- Supporto categorie annidate con query ottimizzate
- Integrazione multi-tenant e ricerca semantica

## Prima di iniziare

Questa libreria è **ideale** se:
- Costruisci un e-commerce con FastAPI e SQLAlchemy
- Vuoi servizi pronti per prodotti, categorie, inventario e varianti
- Hai bisogno di operazioni bulk e router CRUD automatici
- Lavori in ambiente multi-tenant

Questa libreria **NON è adatta** se:
- Cerchi soluzione all-in-one con UI (questa è solo backend)
- Vuoi modelli database predefiniti (devi definire i tuoi)
- Non usi SQLAlchemy o FastAPI

## Indice

- [Installazione](#installazione)
- [Quick start](#quick-start)
- [Panoramica componenti](#panoramica-componenti)
- [Ricette d'uso](#ricette-duso)
  - [CRUD base](#1-crud-base)
  - [Gestione inventario](#2-gestione-inventario)
  - [Operazioni bulk](#3-operazioni-bulk)
  - [Router FastAPI generici](#4-router-fastapi-generici)
  - [Integrazione multi-tenant](#5-integrazione-multi-tenant)
  - [Integrazione ricerca](#6-integrazione-ricerca)
- [Modelli consigliati](#modelli-consigliati)
- [Configurazione](#configurazione)
- [Troubleshooting](#troubleshooting)
- [FAQ performance e sicurezza](#faq-performance-e-sicurezza)
- [Licenza](#licenza)

## Installazione

```bash
pip install linkbay-catalog
```

Oppure l'ultima versione dal repository:

```bash
pip install git+https://github.com/AlessioQuagliara/LinkBay-Catalog
```

| Requisito | Versione | Note |
|-----------|----------|------|
| Python | 3.8 – 3.12 | testato in CI |
| fastapi | >= 0.104.0 | per router |
| sqlalchemy | >= 2.0.0 | per modelli user |
| pydantic | >= 2.0.0 | per schemi |

## Quick start

```python
from linkbay_catalog import ProductService
from my_models import Product  # Your SQLAlchemy model

# Inizializza servizio con tuo modello
service = ProductService(Product, db_session)

# CRUD operations
product = service.create({"name": "T-Shirt", "price": 29.99, "sku": "TSH-001"})
results = service.search("t-shirt")
service.update(product.id, {"price": 24.99})
```

**Importante**: Devi definire i tuoi modelli SQLAlchemy. Vedi [Modelli consigliati](#modelli-consigliati).

## Panoramica componenti

| Componente | Ruolo | Note |
|------------|-------|------|
| `ProductService` | CRUD prodotti, search, inventory | Accetta tuo model Product |
| `CategoryService` | Gestione categorie annidate | Supporta alberi infiniti con query ottimizzate |
| `InventoryService` | Tracking stock, prenotazioni | Con pessimistic locking per concorrenza |
| `VariantService` | Gestione varianti prodotto | Combinations automatiche (size, color, material) |
| `ProductRouter` | Factory router FastAPI | Genera 9 endpoint CRUD automatici |
| `CategoryRouter` | Factory router categorie | Con tree, ancestors, move |
| `CSVHandler` | Import/Export CSV | Batch processing con validazione |
| `BulkOperations` | Update massa, prezzi | Con filtri tenant e batch size |
| `TenantIntegration` | Filtri multi-tenant | Auto-aggiunge tenant_id |
| `SearchIntegration` | Embeddings automatici | Integrazione LinkBay-Search |

## Ricette d'uso

### 1. CRUD base

```python
from linkbay_catalog import ProductService

service = ProductService(Product, db_session)

# Create
product = service.create({
    "name": "T-Shirt Premium",
    "slug": "t-shirt-premium",
    "price": 29.99,
    "sku": "TSH-001"
})

# Read
product = service.get_by_id(1)
product = service.get_by_slug("t-shirt-premium")
products = service.get_all(skip=0, limit=100)

# Update
service.update(product.id, {"price": 24.99})

# Delete
service.delete(product.id)

# Search
results = service.search("t-shirt", filters={"tenant_id": 1})
```

### 2. Gestione inventario

```python
from linkbay_catalog import InventoryService

inventory = InventoryService(Product, db_session)

# Check disponibilità
available = inventory.get_available_quantity(product_id)

# Prenota quantità (con pessimistic locking)
result = inventory.reserve_quantity(product_id, quantity=5)
# {"id": 1, "quantity": 100, "reserved": 5, "available": 95}

# Rilascia prenotazione (es: ordine cancellato)
inventory.release_quantity(product_id, quantity=5)

# Evadi ordine (decrementa quantity e reserved)
inventory.fulfill_quantity(product_id, quantity=5)

# Aggiusta inventario
inventory.adjust_quantity(product_id, quantity=100)

# Low stock alerts
low_stock = inventory.get_low_stock_items(threshold=10)
```

### 3. Operazioni bulk

```python
from linkbay_catalog import CSVHandler, BulkOperations

# Export CSV
csv_handler = CSVHandler(Product)
products = service.get_all(limit=1000)
csv_content = csv_handler.export_to_csv(
    records=products,
    fields=["name", "slug", "price", "sku"]
)

# Import CSV (con batch processing)
result = csv_handler.import_from_csv(csv_content, skip_errors=True)
print(f"Imported {result['success_count']} / {result['total_rows']}")

# Bulk create
bulk_ops = BulkOperations(Product, db_session)
products_data = [
    {"name": "Product 1", "price": 10.0, "sku": "P001"},
    {"name": "Product 2", "price": 20.0, "sku": "P002"},
]
bulk_ops.bulk_create(products_data)

# Bulk price update (+10%)
result = bulk_ops.bulk_price_update(
    adjustment=10,
    is_percentage=True,
    filters={"tenant_id": 1}
)

# Bulk tag operations
bulk_ops.bulk_tag_operation(
    operation="add",
    tags=["sale", "winter-2025"],
    filters={"category_id": 5}
)
```

### 4. Router FastAPI generici

```python
from fastapi import FastAPI, Depends
from linkbay_catalog.routers import ProductRouter
from pydantic import BaseModel

class ProductSchema(BaseModel):
    id: int
    name: str
    price: float
    
    class Config:
        from_attributes = True

class ProductCreate(BaseModel):
    name: str
    price: float

class ProductUpdate(BaseModel):
    name: str | None = None
    price: float | None = None

def get_product_service(db: Session = Depends(get_db)):
    return ProductService(Product, db)

# Crea router con factory pattern
product_router = ProductRouter(
    service_factory=get_product_service,
    entity_name="product",
    entity_schema=ProductSchema,
    entity_create_schema=ProductCreate,
    entity_update_schema=ProductUpdate,
    prefix="/api/products",
    tags=["Products"]
)

app = FastAPI()
app.include_router(product_router.get_router())
```

**Endpoint generati automaticamente**:

| Metodo | Path | Descrizione |
|--------|------|-------------|
| POST | `/api/products/` | Crea prodotto |
| GET | `/api/products/{id}` | Get per ID |
| GET | `/api/products/` | Lista con paginazione |
| PUT | `/api/products/{id}` | Aggiorna prodotto |
| DELETE | `/api/products/{id}` | Elimina prodotto |
| GET | `/api/products/search/?q=...` | Ricerca full-text |
| GET | `/api/products/slug/{slug}` | Get per slug |
| GET | `/api/products/category/{id}` | Filtra per categoria |
| GET | `/api/products/filter/price` | Filtra per range prezzo |

### 5. Integrazione multi-tenant

```python
from linkbay_catalog.integrations import TenantIntegration

# Setup tenant context
def get_current_tenant():
    return request.state.tenant_id

tenant_integration = TenantIntegration(get_current_tenant)

# Auto-aggiunge tenant_id ai filtri
filters = {"category_id": 5}
filters = tenant_integration.add_tenant_filter(filters)
# {"category_id": 5, "tenant_id": 1}

# Verifica accesso entità
if not tenant_integration.ensure_tenant_access(product):
    raise HTTPException(403, "Access denied")

# Decorator per operazioni che richiedono tenant
@tenant_integration.tenant_required
def delete_product(product_id: int, tenant_id: int = None):
    # tenant_id viene iniettato automaticamente se non presente
    pass
```

### 6. Integrazione ricerca

```python
from linkbay_catalog.integrations import SearchIntegration

# Inizializza con servizio LinkBay-Search
search_integration = SearchIntegration(your_search_service)

# Configura ProductService con auto-embeddings
service = ProductService(
    product_model=Product,
    db=db_session,
    search_integration=search_integration
)

# Embeddings generati automaticamente on create/update
product = service.create({
    "name": "Scarpe Running Nike",
    "description": "Scarpe da corsa professionali..."
})  # Embeddings salvati in background!

# Ricerca semantica
results = search_integration.search_similar(
    query="scarpe sportive per correre",
    entity_type="product",
    tenant_id=1,
    limit=10
)
```

## Modelli consigliati

Definisci i tuoi modelli SQLAlchemy. Esempio completo:

```python
from sqlalchemy import Column, Integer, String, Float, JSON, ForeignKey, Boolean
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class Product(Base):
    __tablename__ = "products"
    
    id = Column(Integer, primary_key=True)
    tenant_id = Column(Integer, index=True)  # Multi-tenancy
    name = Column(String, nullable=False)
    slug = Column(String, unique=True, index=True)
    description = Column(String)
    price = Column(Float, nullable=False)
    compare_at_price = Column(Float)
    cost = Column(Float)
    sku = Column(String, unique=True, index=True)
    barcode = Column(String)
    quantity = Column(Integer, default=0)
    reserved = Column(Integer, default=0)
    track_inventory = Column(Boolean, default=True)
    images = Column(JSON)  # ["url1", "url2"]
    tags = Column(String)  # "tag1, tag2, tag3"
    meta_title = Column(String)
    meta_description = Column(String)
    category_id = Column(Integer, ForeignKey("categories.id"))
    brand_id = Column(Integer)

class ProductVariant(Base):
    __tablename__ = "product_variants"
    
    id = Column(Integer, primary_key=True)
    product_id = Column(Integer, ForeignKey("products.id"))
    option1 = Column(String)  # Size: "S", "M", "L"
    option2 = Column(String)  # Color: "Red", "Blue"
    option3 = Column(String)  # Material: "Cotton"
    price = Column(Float)
    sku = Column(String, unique=True)
    quantity = Column(Integer, default=0)
    reserved = Column(Integer, default=0)

class Category(Base):
    __tablename__ = "categories"
    
    id = Column(Integer, primary_key=True)
    tenant_id = Column(Integer, index=True)
    name = Column(String, nullable=False)
    slug = Column(String, unique=True, index=True)
    parent_id = Column(Integer, ForeignKey("categories.id"), nullable=True)
    description = Column(String)
    image = Column(String)
```

**Note importanti**:
- Aggiungi index su `tenant_id`, `slug`, `sku` per performance
- `reserved` è per pessimistic inventory locking
- `images` come JSON array per flessibilità
- `parent_id` self-referential per albero categorie

## Configurazione

### Configurazione servizi

```python
# ProductService con tutte le opzioni
service = ProductService(
    product_model=Product,
    db=db_session,
    variant_model=ProductVariant,  # Opzionale
    search_integration=search_integration  # Opzionale
)

# InventoryService
inventory = InventoryService(
    model=Product,  # O ProductVariant
    db=db_session
)

# CategoryService
categories = CategoryService(
    category_model=Category,
    db=db_session
)
```

### Configurazione bulk operations

```python
# CSV Handler con limiti
csv_handler = CSVHandler(Product)

# Import con validazione
result = csv_handler.import_from_csv(
    csv_content=csv_data,
    field_mapping={
        "Nome": "name",
        "Prezzo": "price"
    },
    skip_errors=True  # Continua su errori
)

# Bulk operations con batch size
bulk_ops = BulkOperations(Product, db_session)
bulk_ops.bulk_create(
    records_data,
    commit=True  # Auto-commit
)
```

## Troubleshooting

| Problema | Causa | Soluzione |
|----------|-------|-----------|
| `InsufficientStockError` durante reserve | Stock non sufficiente o race condition | Usa transazioni con pessimistic locking (`with_for_update`) |
| Duplicate SKU error | SKU già esistente nel database | Genera SKU univoco o verifica esistenza prima di create |
| CSV import fails | File troppo grande o formato invalido | Max 10k righe, verifica formato CSV e encoding UTF-8 |
| Slow category tree | N+1 query problem | Implementa eager loading o usa CTE recursive |
| Slug already exists | Slug duplicato | Auto-genera slug con timestamp o suffisso numerico |
| Search returns no results | Embeddings non generati | Verifica `search_integration` configurato in ProductService |
| Tenant access denied | Prodotto appartiene ad altro tenant | Verifica filtri `tenant_id` nelle query |
| Memory error durante bulk_create | Troppi record in singola transazione | Riduci batch_size a 100-500 records |

## FAQ performance e sicurezza

**Quanto è performante bulk_create?**  
Batch di 100 records per transazione. 1000 prodotti in ~2-3 secondi su hardware medio. Usa `bulk_insert_mappings` di SQLAlchemy.

**Come gestire concorrenza inventory?**  
`InventoryService.reserve_quantity()` usa pessimistic locking (`SELECT ... FOR UPDATE`) per prevenire race conditions. Due richieste simultanee vengono serializzate.

**CSV import è sicuro contro injection?**  
Implementa sanitization contro CSV injection (celle che iniziano con `=`, `+`, `-`, `@`). Limita dimensione file a 10MB e max 10k righe.

**Come ottimizzare ricerca prodotti?**  
Aggiungi index su `slug`, `sku`, `name` nei tuoi modelli. Per cataloghi grandi (>100k prodotti), usa full-text search o Elasticsearch.

**Category tree scalabile?**  
`get_tree()` carica tutte categorie in singola query, poi costruisce albero in memoria. Per >1000 categorie, considera lazy loading o CTE recursive.

**Gestione tenant isolation?**  
Tutti i servizi accettano `tenant_id` nei filtri. Usa `TenantIntegration` per auto-aggiungere tenant_id. Nessun dato leak tra tenant.

**Price validations?**  
Implementa validazioni custom nei tuoi modelli o usa Pydantic schemas. La libreria non forza business rules (prezzo minimo, margini, etc).

**Soft delete support?**  
Non implementato di default. Aggiungi campo `deleted_at` nei tuoi modelli e filtra con `filter(Product.deleted_at == None)`.

## Licenza

MIT License - vedi [LICENSE](LICENSE)

## Supporto

- **Issues**: https://github.com/AlessioQuagliara/LinkBay-Catalog/issues
- **Email**: quagliara.alessio@gmail.com

Contribuisci, apri una issue o raccontaci come stai usando la libreria 🧡