From 1dbab98e888f1866b6b3381d3aed15547ae09e77 Mon Sep 17 00:00:00 2001 From: Goldlabel Apps Ltd Date: Mon, 23 Mar 2026 11:54:38 +0000 Subject: [PATCH 1/2] Remove description and message from API meta Remove the 'description' and 'message' fields from the meta objects in the root and products endpoints and move the 'severity' key for consistent ordering. This standardizes the response meta payloads and removes redundant/static text (e.g., 'Welcome to NX AI!'); no other behavior changes were made. --- app/api/products/products.py | 3 +-- app/api/root.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/api/products/products.py b/app/api/products/products.py index a0c677c..89da37f 100644 --- a/app/api/products/products.py +++ b/app/api/products/products.py @@ -39,11 +39,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/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"}, From 85569ff8eefc065c9228395dfe1e6f9658c112c5 Mon Sep 17 00:00:00 2001 From: Goldlabel Apps Ltd Date: Mon, 23 Mar 2026 12:29:20 +0000 Subject: [PATCH 2/2] Add products reset endpoint and update routes Bump version to 1.0.9, remove legacy echo and CSV import endpoints, and add a new /products/reset endpoint that recreates and seeds the product table. Exports reset router from app.api.products and includes it in the central routes; also keep the products listing route that returns product data with enriched meta (version, base_url, timestamp). Update app metadata/title in main.py and adjust tests to validate the new meta structure. --- app/__init__.py | 2 +- app/api/echo.py | 8 --- app/api/import_csv.py | 56 ------------------- app/api/products/__init__.py | 1 + app/api/products/products.py | 1 + app/api/products/reset.py | 103 +++++++++++++++++++++++++++++++++++ app/api/routes.py | 6 +- app/main.py | 5 +- tests/test_routes.py | 4 +- 9 files changed, 111 insertions(+), 75 deletions(-) delete mode 100644 app/api/echo.py delete mode 100644 app/api/import_csv.py create mode 100644 app/api/products/reset.py 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 89da37f..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 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/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."""