diff --git a/app/api/products/__init__.py b/app/api/products/__init__.py index 057f7eb..475ce29 100644 --- a/app/api/products/__init__.py +++ b/app/api/products/__init__.py @@ -1,2 +1,2 @@ -from .products import router -from .reset import router as reset_router +from .products import router as products_router +from .seed import router as seed_router diff --git a/app/api/products/products.py b/app/api/products/products.py index e44f9a4..aae963e 100644 --- a/app/api/products/products.py +++ b/app/api/products/products.py @@ -1,43 +1,27 @@ + from app import __version__ from fastapi import APIRouter -from fastapi import status import os, time -import psycopg2 -from dotenv import load_dotenv -from app import __version__ +from app.api.db import get_db_connection router = APIRouter() @router.get("/products") def root() -> dict: - """Return a structured welcome message for the API root, including product data.""" - load_dotenv() - conn = psycopg2.connect( - host=os.getenv('DB_HOST'), - port=os.getenv('DB_PORT', '5432'), - dbname=os.getenv('DB_NAME'), - user=os.getenv('DB_USER'), - password=os.getenv('DB_PASSWORD') - ) + """Return all products with full CSV-based schema.""" + conn_gen = get_db_connection() + conn = next(conn_gen) cur = conn.cursor() - cur.execute('SELECT id, name, description, price, in_stock, created_at FROM product;') - products = [ - { - "id": row[0], - "name": row[1], - "description": row[2], - "price": float(row[3]), - "in_stock": row[4], - "created_at": row[5].isoformat() if row[5] else None - } - for row in cur.fetchall() - ] + cur.execute('SELECT * FROM product;') + if cur.description is None: + products = [] + else: + columns = [desc[0] for desc in cur.description] + products = [dict(zip(columns, row)) for row in cur.fetchall()] cur.close() conn.close() - load_dotenv() base_url = os.getenv("BASE_URL", "http://localhost:8000") - epoch = int(time.time() * 1000) meta = { "severity": "success", diff --git a/app/api/products/reset.py b/app/api/products/reset.py deleted file mode 100644 index b1bfa74..0000000 --- a/app/api/products/reset.py +++ /dev/null @@ -1,103 +0,0 @@ -import os -import psycopg2 -from dotenv import load_dotenv -from fastapi import APIRouter, status - -router = APIRouter() - -@router.post("/products/reset", status_code=status.HTTP_200_OK) -def reset_products() -> dict: - """Delete and recreate the product table, then seed with initial data.""" - load_dotenv() - conn = psycopg2.connect( - host=os.getenv('DB_HOST'), - port=os.getenv('DB_PORT', '5432'), - dbname=os.getenv('DB_NAME'), - user=os.getenv('DB_USER'), - password=os.getenv('DB_PASSWORD') - ) - cur = conn.cursor() - # Drop and recreate table - cur.execute(''' - DROP TABLE IF EXISTS product; - CREATE TABLE product ( - id SERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - description TEXT, - price NUMERIC(10,2) NOT NULL, - in_stock BOOLEAN DEFAULT TRUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - ''') - # Seed data - seed_products = [ - ("Apple", "Fresh red apple", 0.99, True), - ("Banana", "Organic banana", 0.59, True), - ("Orange", "Juicy orange", 1.29, True), - ] - cur.executemany( - "INSERT INTO product (name, description, price, in_stock) VALUES (%s, %s, %s, %s);", - seed_products - ) - conn.commit() - cur.execute('SELECT id, name, description, price, in_stock, created_at FROM product;') - products = [ - { - "id": row[0], - "name": row[1], - "description": row[2], - "price": float(row[3]), - "in_stock": row[4], - "created_at": row[5].isoformat() if row[5] else None - } - for row in cur.fetchall() - ] - cur.close() - conn.close() - return {"message": "Product table reset and seeded.", "data": products} -import os, time -import psycopg2 -from dotenv import load_dotenv -from app import __version__ - -router = APIRouter() - -@router.get("/products") -def root() -> dict: - """Return a structured welcome message for the API root, including product data.""" - load_dotenv() - conn = psycopg2.connect( - host=os.getenv('DB_HOST'), - port=os.getenv('DB_PORT', '5432'), - dbname=os.getenv('DB_NAME'), - user=os.getenv('DB_USER'), - password=os.getenv('DB_PASSWORD') - ) - cur = conn.cursor() - cur.execute('SELECT id, name, description, price, in_stock, created_at FROM product;') - products = [ - { - "id": row[0], - "name": row[1], - "description": row[2], - "price": float(row[3]), - "in_stock": row[4], - "created_at": row[5].isoformat() if row[5] else None - } - for row in cur.fetchall() - ] - cur.close() - conn.close() - - load_dotenv() - base_url = os.getenv("BASE_URL", "http://localhost:8000") - - epoch = int(time.time() * 1000) - meta = { - "severity": "success", - "title": "Product List", - "version": __version__, - "base_url": base_url, - "time": epoch, - } - return {"meta": meta, "data": products} diff --git a/app/api/products/seed.py b/app/api/products/seed.py new file mode 100644 index 0000000..53fee19 --- /dev/null +++ b/app/api/products/seed.py @@ -0,0 +1,83 @@ +from app import __version__ +import os, time +import csv +from fastapi import APIRouter, status +from app.api.db import get_db_connection + +router = APIRouter() + +CSV_PATH = os.path.join(os.path.dirname(__file__), 'start_data.csv') +CSV_PATH = os.path.abspath(CSV_PATH) + +@router.get("/products/seed", status_code=status.HTTP_200_OK) +def seed_products() -> dict: + """Delete and recreate the product table, then seed with CSV data.""" + conn_gen = get_db_connection() + conn = next(conn_gen) + cur = conn.cursor() + # Drop and recreate table with all CSV columns + cur.execute(''' + DROP TABLE IF EXISTS product; + CREATE TABLE product ( + id SERIAL PRIMARY KEY, + Params TEXT, + item INTEGER, + title TEXT, + UOS TEXT, + Pack_Description TEXT, + Hierarchy1 TEXT, + Hierarchy2 TEXT, + Hierarchy3 TEXT, + UOP TEXT, + sSell1 NUMERIC(10,2), + sSell2 NUMERIC(10,2), + sSell3 NUMERIC(10,2), + sSell4 NUMERIC(10,2), + sSell5 NUMERIC(10,2), + pack1 INTEGER, + pack2 INTEGER, + pack3 INTEGER, + pack4 INTEGER, + pack5 INTEGER, + EAN TEXT + ); + ''') + # Read and insert CSV data + with open(CSV_PATH, newline='') as csvfile: + reader = csv.DictReader(csvfile) + rows = [row for row in reader] + for row in rows: + # Map 'desc' from CSV to 'title' for DB + row['title'] = row.pop('desc') + cur.execute( + """ + INSERT INTO product ( + Params, item, title, UOS, Pack_Description, Hierarchy1, Hierarchy2, Hierarchy3, UOP, + sSell1, sSell2, sSell3, sSell4, sSell5, pack1, pack2, pack3, pack4, pack5, EAN + ) VALUES ( + %(Params)s, %(item)s, %(title)s, %(UOS)s, %(Pack_Description)s, %(Hierarchy1)s, %(Hierarchy2)s, %(Hierarchy3)s, %(UOP)s, + %(sSell1)s, %(sSell2)s, %(sSell3)s, %(sSell4)s, %(sSell5)s, %(pack1)s, %(pack2)s, %(pack3)s, %(pack4)s, %(pack5)s, %(EAN)s + ) + """, + row + ) + conn.commit() + cur.execute('SELECT * FROM product;') + if cur.description is None: + products = [] + else: + columns = [desc[0] for desc in cur.description] + products = [dict(zip(columns, row)) for row in cur.fetchall()] + cur.close() + conn.close() + + base_url = os.getenv("BASE_URL", "http://localhost:8000") + epoch = int(time.time() * 1000) + meta = { + "severity": "success", + "title": "Product table seeded", + "version": __version__, + "base_url": base_url, + "time": epoch, + } + return {"meta": meta, "data": products} diff --git a/tests/csv/small.csv b/app/api/products/start_data.csv similarity index 100% rename from tests/csv/small.csv rename to app/api/products/start_data.csv diff --git a/app/api/routes.py b/app/api/routes.py index d89d651..ec04a6f 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -13,9 +13,9 @@ from app.api.root import router as root_router from app.api.health import router as health_router from app.api.products.products import router as products_router -from app.api.products.reset import router as reset_router +from app.api.products.seed import router as seed_router router.include_router(root_router) router.include_router(health_router) router.include_router(products_router) -router.include_router(reset_router) +router.include_router(seed_router) diff --git a/app/static/favicon.ico b/app/static/favicon.ico old mode 100644 new mode 100755 index 05442c4..8e4f671 Binary files a/app/static/favicon.ico and b/app/static/favicon.ico differ diff --git a/tests/test_routes.py b/tests/test_routes.py index b3431c9..3437436 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -25,22 +25,3 @@ def test_health_returns_ok() -> None: assert response.status_code == 200 assert response.json() == {"status": "ok"} - -def test_echo_returns_message() -> None: - """POST /echo should return the provided message.""" - response = client.post("/echo", json={"message": "hello"}) - assert response.status_code == 200 - assert response.json() == {"echo": "hello"} - - -def test_echo_empty_string() -> None: - """POST /echo with an empty string should echo back an empty string.""" - response = client.post("/echo", json={"message": ""}) - assert response.status_code == 200 - assert response.json() == {"echo": ""} - - -def test_echo_missing_body_returns_422() -> None: - """POST /echo without a body should return 422 Unprocessable Entity.""" - response = client.post("/echo", json={}) - assert response.status_code == 422