Skip to content

openfoodfacts_proxy.infrastructure.database

[docs] module openfoodfacts_proxy.infrastructure.database

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
from typing import Any

from pymongo.database import Database


def select_requested_fields(document: dict[str, Any], fields: str | None) -> dict[str, Any]:
    """Return only the requested top-level fields from a document."""
    if not fields:
        return document

    requested_fields = [field.strip() for field in fields.split(",") if field.strip()]
    return {field: document[field] for field in requested_fields if field in document}


def get_product_by_barcode(db: Database, barcode: str, *, fields: str | None = None) -> dict[str, Any] | None:
    """Fetch a single product by its barcode (code field) from local MongoDB."""
    product = db.products.find_one({"code": barcode}, {"_id": 0})
    if product is None:
        return None
    return select_requested_fields(product, fields)


def search_products(
    db: Database,
    *,
    code: str | None = None,
    product_name: str | None = None,
    brands: str | None = None,
    categories_tags: str | None = None,
    nutrition_grades: str | None = None,
    page: int = 1,
    page_size: int = 24,
    sort_by: str | None = None,
    fields: str | None = None,
) -> dict[str, Any]:
    """Search products in local MongoDB with basic filtering.

    Returns a response structure compatible with the OFF API search response.
    """
    query: dict[str, Any] = {}

    if code:
        # Support comma-separated codes
        codes = [c.strip() for c in code.split(",")]
        if len(codes) == 1:
            query["code"] = codes[0]
        else:
            query["code"] = {"$in": codes}

    if product_name:
        query["product_name"] = {"$regex": product_name, "$options": "i"}

    if brands:
        normalized_brand = brands.lower().replace(" ", "-")
        query["$or"] = [
            {"brands_tags": {"$regex": normalized_brand}},
            {"brands": {"$regex": brands, "$options": "i"}},
        ]

    if categories_tags:
        normalized_category = categories_tags.lower().replace(" ", "-")
        category_condition = {
            "$or": [
                {"categories_tags": {"$in": [categories_tags, normalized_category, f"en:{normalized_category}"]}},
                {"categories": {"$regex": categories_tags, "$options": "i"}},
                {"categories_tags_en": {"$in": [categories_tags]}},
            ]
        }
        if "$and" in query:
            query["$and"].append(category_condition)
        elif "$or" in query:
            existing_or = query.pop("$or")
            query["$and"] = [{"$or": existing_or}, category_condition]
        else:
            query.update(category_condition)

    if nutrition_grades:
        query["nutrition_grades"] = nutrition_grades

    skip = (page - 1) * page_size

    cursor = db.products.find(query, {"_id": 0})
    if sort_by:
        direction = -1 if sort_by.startswith("-") else 1
        sort_field = sort_by[1:] if sort_by.startswith("-") else sort_by
        cursor = cursor.sort(sort_field, direction)
    cursor = cursor.skip(skip).limit(page_size)
    products = [select_requested_fields(product, fields) for product in cursor]

    count = db.products.count_documents(query)

    return {
        "count": count,
        "page": page,
        "page_count": (count + page_size - 1) // page_size if page_size > 0 else 0,
        "page_size": page_size,
        "products": products,
    }


def ensure_indexes(db: Database) -> None:
    """Create indexes on the products collection for efficient querying."""
    db.products.create_index("code", unique=True, background=True)
    db.products.create_index("product_name", background=True)
    db.products.create_index("brands_tags", background=True)
    db.products.create_index("categories_tags", background=True)
    db.reference_cache.create_index("key", unique=True, background=True)
    db.reference_cache.create_index("expires_at", background=True)
    db.reference_snapshots.create_index("key", unique=True, background=True)
    db.reference_snapshots.create_index("expires_at", background=True)
    db.taxonomies.create_index("key", unique=True, background=True)
    db.taxonomies.create_index("expires_at", background=True)