costco-grocery-list/docs/guides/api-documentation.md
2026-01-27 00:03:58 -08:00

13 KiB

Costco Grocery List API Documentation

Base URL: http://localhost:5000/api

Table of Contents


Authentication

All authenticated endpoints require a JWT token in the Authorization header:

Authorization: Bearer <token>

POST /auth/register

Register a new user account.

Access: Public

Request Body:

{
  "username": "string (lowercase)",
  "password": "string",
  "name": "string"
}

Response: 200 OK

{
  "message": "User registered",
  "user": {
    "id": 1,
    "username": "johndoe",
    "name": "John Doe",
    "role": "viewer"
  }
}

Error: 400 Bad Request

{
  "message": "Registration failed",
  "error": "Error details"
}

POST /auth/login

Authenticate and receive a JWT token.

Access: Public

Request Body:

{
  "username": "string (lowercase)",
  "password": "string"
}

Response: 200 OK

{
  "token": "jwt_token_string",
  "username": "johndoe",
  "role": "editor"
}

Errors:

  • 401 Unauthorized - User not found or invalid credentials
{
  "message": "User not found"
}

or

{
  "message": "Invalid credentials"
}

Grocery List Management

GET /list

Retrieve all unbought grocery items.

Access: Authenticated (All roles)

Query Parameters: None

Response: 200 OK

[
  {
    "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

{
  "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

[
  { "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

[
  {
    "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

{
  "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

{
  "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

{
  "message": "Image updated successfully"
}

Error: 400 Bad Request

{
  "message": "No image provided"
}

POST /list/mark-bought

Mark an item as purchased.

Access: Editor, Admin

Request Body:

{
  "id": 1
}

Response: 200 OK

{
  "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:

{
  "itemName": "string (optional)",
  "quantity": 2,
  "classification": {
    "item_type": "dairy",
    "item_group": "milk",
    "zone": "DAIRY"
  }
}

Response: 200 OK

{
  "message": "Item updated successfully"
}

Errors:

  • 400 Bad Request - Invalid classification values
{
  "message": "Invalid item_type"
}

or

{
  "message": "Invalid item_group for selected item_type"
}

or

{
  "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

{
  "exists": true
}

or

{
  "exists": false
}

GET /users/test

Health check endpoint for user routes.

Access: Public

Response: 200 OK

{
  "message": "User route is working"
}

Admin Operations

GET /admin/users

Retrieve all registered users.

Access: Admin only

Query Parameters: None

Response: 200 OK

[
  {
    "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:

{
  "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

{
  "message": "Role updated",
  "id": 1,
  "role": "admin"
}

Errors:

  • 400 Bad Request - Invalid role
{
  "error": "Invalid role"
}
  • 404 Not Found - User not found
{
  "error": "User not found"
}
  • 500 Internal Server Error
{
  "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

{
  "message": "User deleted",
  "id": 5
}

Errors:

  • 404 Not Found
{
  "error": "User not found"
}
  • 500 Internal Server Error
{
  "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

{
  "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:

{
  "message": "No token provided"
}

Invalid Token:

{
  "message": "Invalid or expired token"
}

Insufficient Permissions:

{
  "message": "Access denied"
}

Data Models

User

{
  id: number
  username: string (lowercase, unique)
  password: string (bcrypt hashed)
  name: string
  role: "viewer" | "editor" | "admin"
}

Grocery Item

{
  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

{
  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

{
  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

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

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

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)