# 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 <token>
```

### 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)
