# Costco Grocery List API Documentation Base URL: `http://localhost:5000/api` ## Table of Contents - [Authentication](#authentication) - [Grocery List Management](#grocery-list-management) - [User Management](#user-management) - [Admin Operations](#admin-operations) - [Role-Based Access Control](#role-based-access-control) - [Error Responses](#error-responses) --- ## Authentication All authenticated endpoints require a JWT token in the `Authorization` header: ``` Authorization: Bearer ``` ### POST /auth/register Register a new user account. **Access:** Public **Request Body:** ```json { "username": "string (lowercase)", "password": "string", "name": "string" } ``` **Response:** `200 OK` ```json { "message": "User registered", "user": { "id": 1, "username": "johndoe", "name": "John Doe", "role": "viewer" } } ``` **Error:** `400 Bad Request` ```json { "message": "Registration failed", "error": "Error details" } ``` --- ### POST /auth/login Authenticate and receive a JWT token. **Access:** Public **Request Body:** ```json { "username": "string (lowercase)", "password": "string" } ``` **Response:** `200 OK` ```json { "token": "jwt_token_string", "username": "johndoe", "role": "editor" } ``` **Errors:** - `401 Unauthorized` - User not found or invalid credentials ```json { "message": "User not found" } ``` or ```json { "message": "Invalid credentials" } ``` --- ## Grocery List Management ### GET /list Retrieve all unbought grocery items. **Access:** Authenticated (All roles) **Query Parameters:** None **Response:** `200 OK` ```json [ { "id": 1, "item_name": "milk", "quantity": 2, "bought": false, "item_image": "base64_encoded_string", "image_mime_type": "image/jpeg", "added_by_users": ["John Doe", "Jane Smith"], "modified_on": "2026-01-03T12:00:00Z", "item_type": "dairy", "item_group": "milk", "zone": "DAIRY" } ] ``` **Fields:** - `id` - Unique item identifier - `item_name` - Name of grocery item (lowercase) - `quantity` - Number of items needed - `bought` - Purchase status (always false for this endpoint) - `item_image` - Base64 encoded image (nullable) - `image_mime_type` - MIME type of image (nullable) - `added_by_users` - Array of user names who added/modified this item - `modified_on` - Last modification timestamp - `item_type` - Classification type (nullable) - `item_group` - Classification group (nullable) - `zone` - Store zone location (nullable) --- ### GET /list/item-by-name Retrieve a specific item by name (case-insensitive). **Access:** Authenticated (All roles) **Query Parameters:** - `itemName` (required) - Name of the item to search **Example:** `GET /list/item-by-name?itemName=milk` **Response:** `200 OK` ```json { "id": 1, "item_name": "milk", "quantity": 2, "bought": false, "item_image": "base64_encoded_string", "image_mime_type": "image/jpeg" } ``` Returns `null` if item not found. --- ### GET /list/suggest Get item name suggestions based on partial input. **Access:** Authenticated (All roles) **Query Parameters:** - `query` (required) - Partial item name to search **Example:** `GET /list/suggest?query=mil` **Response:** `200 OK` ```json [ { "item_name": "milk" }, { "item_name": "almond milk" }, { "item_name": "oat milk" } ] ``` **Behavior:** - Case-insensitive partial matching (ILIKE) - Searches all items in `grocery_list` (bought and unbought) - Returns up to 10 suggestions - Ordered by database query result --- ### GET /list/recently-bought Retrieve items bought within the last 24 hours. **Access:** Authenticated (All roles) **Query Parameters:** None **Response:** `200 OK` ```json [ { "id": 5, "item_name": "bread", "quantity": 1, "bought": true, "item_image": "base64_encoded_string", "image_mime_type": "image/jpeg", "added_by_users": ["John Doe"], "modified_on": "2026-01-03T10:30:00Z", "item_type": "bakery", "item_group": "bread", "zone": "BAKERY" } ] ``` **Behavior:** - Returns items with `bought = true` - Filters by `modified_on` within last 24 hours - Includes classification data if available --- ### GET /list/item/:id/classification Get classification data for a specific item. **Access:** Authenticated (All roles) **Path Parameters:** - `id` (required) - Item ID **Example:** `GET /list/item/1/classification` **Response:** `200 OK` ```json { "item_type": "dairy", "item_group": "milk", "zone": "DAIRY", "confidence": 1.0, "source": "user" } ``` Returns empty object `{}` if no classification exists. **Fields:** - `item_type` - Classification type (e.g., "dairy", "produce", "meat") - `item_group` - Sub-group within type (e.g., "milk", "cheese") - `zone` - Store zone (e.g., "DAIRY", "PRODUCE", "MEAT") - `confidence` - Classification confidence (0.0 to 1.0) - `source` - Origin of classification ("user" or "auto") --- ### POST /list/add Add a new item or update quantity of existing item. **Access:** Editor, Admin **Content-Type:** `multipart/form-data` **Request Body:** ``` itemName: string (required) quantity: number (required) image: file (optional) - Image file ``` **Response:** `200 OK` ```json { "message": "Item added/updated", "addedBy": 1 } ``` **Behavior:** - If item exists and is unbought: updates quantity - If item exists and is bought: marks as unbought with new quantity - If item doesn't exist: creates new item - Adds record to `grocery_history` table - Processes and optimizes uploaded image (800x800px, JPEG 85% quality) **Image Processing:** - Accepted formats: JPEG, PNG, GIF, WebP - Max file size: 5MB - Output: 800x800px JPEG at 85% quality - Stored as binary in database --- ### POST /list/update-image Update or add image to an existing item. **Access:** Editor, Admin **Content-Type:** `multipart/form-data` **Request Body:** ``` id: number (required) itemName: string (required) quantity: number (required) image: file (required) - Image file ``` **Response:** `200 OK` ```json { "message": "Image updated successfully" } ``` **Error:** `400 Bad Request` ```json { "message": "No image provided" } ``` --- ### POST /list/mark-bought Mark an item as purchased. **Access:** Editor, Admin **Request Body:** ```json { "id": 1 } ``` **Response:** `200 OK` ```json { "message": "Item marked bought" } ``` **Behavior:** - Sets `bought = true` - Updates `modified_on` timestamp - Item moves to "Recently Bought" list --- ### PUT /list/item/:id Update item details and/or classification. **Access:** Editor, Admin **Path Parameters:** - `id` (required) - Item ID **Request Body:** ```json { "itemName": "string (optional)", "quantity": 2, "classification": { "item_type": "dairy", "item_group": "milk", "zone": "DAIRY" } } ``` **Response:** `200 OK` ```json { "message": "Item updated successfully" } ``` **Errors:** - `400 Bad Request` - Invalid classification values ```json { "message": "Invalid item_type" } ``` or ```json { "message": "Invalid item_group for selected item_type" } ``` or ```json { "message": "Invalid zone" } ``` **Classification Validation:** - `item_type`: Must be one of the valid types defined in `backend/constants/classifications.js` - `item_group`: Must be valid for the selected `item_type` - `zone`: Must be one of the predefined store zones **Valid Zones:** - ENTRANCE, PRODUCE, MEAT, DELI, BAKERY, DAIRY, FROZEN, DRY_GOODS, BEVERAGES, SNACKS, HOUSEHOLD, HEALTH, CHECKOUT --- ## User Management ### GET /users/exists Check if a username already exists. **Access:** Public **Query Parameters:** - `username` (required) - Username to check **Example:** `GET /users/exists?username=johndoe` **Response:** `200 OK` ```json { "exists": true } ``` or ```json { "exists": false } ``` --- ### GET /users/test Health check endpoint for user routes. **Access:** Public **Response:** `200 OK` ```json { "message": "User route is working" } ``` --- ## Admin Operations ### GET /admin/users Retrieve all registered users. **Access:** Admin only **Query Parameters:** None **Response:** `200 OK` ```json [ { "id": 1, "username": "johndoe", "name": "John Doe", "role": "editor" }, { "id": 2, "username": "janesmith", "name": "Jane Smith", "role": "viewer" } ] ``` --- ### PUT /admin/users Update a user's role. **Access:** Admin only **Request Body:** ```json { "id": 1, "role": "admin" } ``` **Valid Roles:** - `viewer` - Read-only access - `editor` - Can add/modify/buy items - `admin` - Full access including user management **Response:** `200 OK` ```json { "message": "Role updated", "id": 1, "role": "admin" } ``` **Errors:** - `400 Bad Request` - Invalid role ```json { "error": "Invalid role" } ``` - `404 Not Found` - User not found ```json { "error": "User not found" } ``` - `500 Internal Server Error` ```json { "error": "Failed to update role" } ``` --- ### DELETE /admin/users Delete a user account. **Access:** Admin only **Path Parameters:** - `id` (required) - User ID **Example:** `DELETE /admin/users?id=5` **Response:** `200 OK` ```json { "message": "User deleted", "id": 5 } ``` **Errors:** - `404 Not Found` ```json { "error": "User not found" } ``` - `500 Internal Server Error` ```json { "error": "Failed to delete user" } ``` --- ## Role-Based Access Control ### Roles and Permissions | Role | View Lists | Add/Edit Items | Buy Items | User Management | |------|-----------|----------------|-----------|-----------------| | **viewer** | ✅ | ❌ | ❌ | ❌ | | **editor** | ✅ | ✅ | ✅ | ❌ | | **admin** | ✅ | ✅ | ✅ | ✅ | ### Protected Endpoints by Role **All Authenticated Users (viewer, editor, admin):** - GET /list - GET /list/item-by-name - GET /list/suggest - GET /list/recently-bought - GET /list/item/:id/classification **Editor and Admin Only:** - POST /list/add - POST /list/update-image - POST /list/mark-bought - PUT /list/item/:id **Admin Only:** - GET /admin/users - PUT /admin/users - DELETE /admin/users --- ## Error Responses ### Standard Error Format ```json { "message": "Error description", "error": "Detailed error information (optional)" } ``` ### Common HTTP Status Codes | Code | Meaning | Common Causes | |------|---------|---------------| | 200 | OK | Successful request | | 400 | Bad Request | Invalid input data, missing required fields | | 401 | Unauthorized | Missing/invalid token, invalid credentials | | 403 | Forbidden | Insufficient permissions for operation | | 404 | Not Found | Resource doesn't exist | | 500 | Internal Server Error | Server-side error | ### Authentication Errors **Missing Token:** ```json { "message": "No token provided" } ``` **Invalid Token:** ```json { "message": "Invalid or expired token" } ``` **Insufficient Permissions:** ```json { "message": "Access denied" } ``` --- ## Data Models ### User ```typescript { id: number username: string (lowercase, unique) password: string (bcrypt hashed) name: string role: "viewer" | "editor" | "admin" } ``` ### Grocery Item ```typescript { id: number item_name: string (lowercase) quantity: number bought: boolean item_image: Buffer | null (binary image data) image_mime_type: string | null added_by: number (user id) modified_on: timestamp } ``` ### Item Classification ```typescript { id: number (references grocery_list.id) item_type: string | null item_group: string | null zone: string | null confidence: number (0.0 - 1.0) source: "user" | "auto" } ``` ### Grocery History ```typescript { id: number list_item_id: number (references grocery_list.id) quantity: number added_by: number (user id) added_on: timestamp } ``` --- ## Notes ### Case Sensitivity - Usernames are stored and compared in **lowercase** - Item names are stored in **lowercase** - All searches are **case-insensitive** (ILIKE in PostgreSQL) ### Token Expiration - JWT tokens expire after **1 year** - Include token expiration handling in client applications ### Image Optimization - Images are automatically processed: - Resized to 800x800px (maintains aspect ratio) - Converted to JPEG format - Compressed to 85% quality - Max upload size: 5MB ### Database Transaction Handling - Item additions/updates include history tracking - Classification updates are atomic operations ### CORS Configuration - Allowed origins configured via `ALLOWED_ORIGINS` environment variable - Supports both static origins and IP range patterns (e.g., `192.168.*.*`) --- ## Frontend Integration Examples ### Login Flow ```javascript const response = await axios.post('/api/auth/login', { username: 'johndoe', password: 'password123' }); const { token, role, username } = response.data; localStorage.setItem('token', token); localStorage.setItem('role', role); localStorage.setItem('username', username); ``` ### Authenticated Request ```javascript import axios from 'axios'; const api = axios.create({ baseURL: 'http://localhost:5000/api', headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); const items = await api.get('/list'); ``` ### Adding Item with Image ```javascript const formData = new FormData(); formData.append('itemName', 'milk'); formData.append('quantity', 2); formData.append('image', imageFile); // File object from input await api.post('/list/add', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); ``` --- ## Version Information - API Version: 1.0 - Last Updated: January 3, 2026 - Backend Framework: Express 5.1.0 - Database: PostgreSQL 8.16.0 - Authentication: JWT (jsonwebtoken 9.0.2)