diff --git a/app/__init__.py b/app/__init__.py index c28c7bc..c8f73cc 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,4 @@ """NX AI - FastAPI/Python/Postgres/tsvector""" # Current Version -__version__ = "1.0.8" +__version__ = "1.0.9" diff --git a/app/api/echo.py b/app/api/echo.py deleted file mode 100644 index 5f757b7..0000000 --- a/app/api/echo.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import APIRouter -from app.api.schemas import EchoRequest, EchoResponse - -router = APIRouter() - -@router.post("/echo", response_model=EchoResponse) -def echo(body: EchoRequest) -> EchoResponse: - return EchoResponse(echo=body.message) diff --git a/app/api/import_csv.py b/app/api/import_csv.py deleted file mode 100644 index 9ed4bc1..0000000 --- a/app/api/import_csv.py +++ /dev/null @@ -1,56 +0,0 @@ -from fastapi import APIRouter, UploadFile, File, HTTPException - -import csv -import psycopg2 -import os -import io -from dotenv import load_dotenv - -router = APIRouter() - -@router.post("/import-csv") -def import_csv(file: UploadFile = File(...)): - """Import products from a CSV file into the database.""" - 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() - try: - reader = csv.DictReader(io.TextIOWrapper(file.file, encoding='utf-8')) - batch = [] - batch_size = 100 - inserted = 0 - for row in reader: - batch.append(( - row.get('name'), - row.get('description'), - float(row.get('price', 0)), - int(row.get('in_stock', 0)), - )) - if len(batch) >= batch_size: - cur.executemany( - "INSERT INTO product (name, description, price, in_stock) VALUES (%s, %s, %s, %s)", - batch - ) - conn.commit() - inserted += len(batch) - batch.clear() - if batch: - cur.executemany( - "INSERT INTO product (name, description, price, in_stock) VALUES (%s, %s, %s, %s)", - batch - ) - conn.commit() - inserted += len(batch) - return {"inserted": inserted} - except Exception as e: - conn.rollback() - raise HTTPException(status_code=500, detail=str(e)) - finally: - cur.close() - conn.close() diff --git a/app/api/products/__init__.py b/app/api/products/__init__.py index 6acfc93..057f7eb 100644 --- a/app/api/products/__init__.py +++ b/app/api/products/__init__.py @@ -1 +1,2 @@ from .products import router +from .reset import router as reset_router diff --git a/app/api/products/products.py b/app/api/products/products.py index a0c677c..e44f9a4 100644 --- a/app/api/products/products.py +++ b/app/api/products/products.py @@ -1,5 +1,6 @@ from app import __version__ from fastapi import APIRouter +from fastapi import status import os, time import psycopg2 from dotenv import load_dotenv @@ -39,11 +40,10 @@ def root() -> dict: epoch = int(time.time() * 1000) meta = { + "severity": "success", "title": "Product List", - "description": "from the products Postgres table", "version": __version__, "base_url": base_url, "time": epoch, - "severity": "success", } return {"meta": meta, "data": products} diff --git a/app/api/products/reset.py b/app/api/products/reset.py new file mode 100644 index 0000000..b1bfa74 --- /dev/null +++ b/app/api/products/reset.py @@ -0,0 +1,103 @@ +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/root.py b/app/api/root.py index e00c4b3..2c89a22 100644 --- a/app/api/root.py +++ b/app/api/root.py @@ -13,13 +13,11 @@ def root() -> dict: base_url = os.getenv("BASE_URL", "http://localhost:8000") epoch = int(time.time() * 1000) meta = { + "severity": "success", "title": "NX-AI says hi", - "description": "This is the base_url", "version": __version__, "base_url": base_url, "time": epoch, - "severity": "success", - "message": "Welcome to NX AI!" } endpoints = [ {"name": "docs", "url": f"{base_url}/docs"}, diff --git a/app/api/routes.py b/app/api/routes.py index 359f853..d89d651 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -12,12 +12,10 @@ from app.api.root import router as root_router from app.api.health import router as health_router -from app.api.echo import router as echo_router -from app.api.import_csv import router as import_csv_router from app.api.products.products import router as products_router +from app.api.products.reset import router as reset_router router.include_router(root_router) router.include_router(health_router) -router.include_router(echo_router) -router.include_router(import_csv_router) router.include_router(products_router) +router.include_router(reset_router) diff --git a/app/main.py b/app/main.py index 4ade825..96bc379 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,5 @@ from app import __version__ -"""NX AI - FastAPI entry point.""" - +"""NX-AI Open Source, production ready Python FastAPI/Postgres app for NX""" from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -12,7 +11,7 @@ from app.api.routes import router app = FastAPI( - title="NX AI", + title="NX-AI", description="Production-ready Python FastAPI app for NX", version=__version__, ) diff --git a/tests/test_routes.py b/tests/test_routes.py index afc7aad..b3431c9 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -17,9 +17,7 @@ def test_root_returns_welcome_message() -> None: json_data = response.json() assert "meta" in json_data assert "data" in json_data - assert "message" in json_data["meta"] - assert "NX AI" in json_data["meta"]["message"] - + assert "title" in json_data["meta"] def test_health_returns_ok() -> None: """GET /health should return status ok."""