update readme
This commit is contained in:
parent
7c58ab57f7
commit
b5c530cfad
771
API_DOCUMENTATION.md
Normal file
771
API_DOCUMENTATION.md
Normal file
@ -0,0 +1,771 @@
|
|||||||
|
# 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)
|
||||||
774
README.md
Normal file
774
README.md
Normal file
@ -0,0 +1,774 @@
|
|||||||
|
# Costco Grocery List
|
||||||
|
|
||||||
|
A full-stack web application for managing grocery shopping lists with role-based access control, image support, and intelligent item classification.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [System Architecture](#system-architecture)
|
||||||
|
- [Key Features](#key-features)
|
||||||
|
- [Technology Stack](#technology-stack)
|
||||||
|
- [Data Flow](#data-flow)
|
||||||
|
- [Role-Based Access Control](#role-based-access-control)
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [API Documentation](#api-documentation)
|
||||||
|
- [Project Structure](#project-structure)
|
||||||
|
- [Development Workflow](#development-workflow)
|
||||||
|
- [Deployment](#deployment)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Overview
|
||||||
|
|
||||||
|
The Costco Grocery List application provides a collaborative platform for managing household grocery shopping. Users can add items with photos, track quantities, mark items as purchased, and organize items by store zones. The system supports multiple users with different permission levels, making it ideal for families or shared households.
|
||||||
|
|
||||||
|
**Live Demo:** [https://costco.nicosaya.com](https://costco.nicosaya.com)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ System Architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph "Client Layer"
|
||||||
|
A[React 19 SPA]
|
||||||
|
B[Vite Dev Server]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Application Layer"
|
||||||
|
C[Express 5 API]
|
||||||
|
D[JWT Auth Middleware]
|
||||||
|
E[RBAC Middleware]
|
||||||
|
F[Image Processing]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Data Layer"
|
||||||
|
G[(PostgreSQL)]
|
||||||
|
H[grocery_list]
|
||||||
|
I[users]
|
||||||
|
J[item_classification]
|
||||||
|
K[grocery_history]
|
||||||
|
end
|
||||||
|
|
||||||
|
A -->|HTTP/REST| C
|
||||||
|
C --> D
|
||||||
|
D --> E
|
||||||
|
E --> F
|
||||||
|
C --> G
|
||||||
|
G --> H
|
||||||
|
G --> I
|
||||||
|
G --> J
|
||||||
|
G --> K
|
||||||
|
|
||||||
|
style A fill:#61dafb
|
||||||
|
style C fill:#259dff
|
||||||
|
style G fill:#336791
|
||||||
|
```
|
||||||
|
|
||||||
|
### Architecture Layers
|
||||||
|
|
||||||
|
1. **Presentation Layer (Frontend)**
|
||||||
|
- React 19 with modern hooks (useState, useEffect, useContext)
|
||||||
|
- Component-based architecture organized by feature
|
||||||
|
- CSS custom properties for theming
|
||||||
|
- Axios for HTTP requests with interceptors
|
||||||
|
|
||||||
|
2. **Business Logic Layer (Backend)**
|
||||||
|
- Express.js REST API
|
||||||
|
- JWT-based authentication
|
||||||
|
- Role-based access control (RBAC)
|
||||||
|
- Image optimization middleware (Sharp)
|
||||||
|
- Centralized error handling
|
||||||
|
|
||||||
|
3. **Data Persistence Layer**
|
||||||
|
- PostgreSQL relational database
|
||||||
|
- Normalized schema with foreign key constraints
|
||||||
|
- Junction table for item history tracking
|
||||||
|
- Binary storage for optimized images
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Key Features
|
||||||
|
|
||||||
|
### 🔐 Authentication & Authorization
|
||||||
|
- JWT token-based authentication (1 year expiration)
|
||||||
|
- Three-tier role system (Viewer, Editor, Admin)
|
||||||
|
- Secure password hashing with bcrypt
|
||||||
|
- Token-based session management
|
||||||
|
|
||||||
|
### 📝 Grocery List Management
|
||||||
|
- Add items with optional images
|
||||||
|
- Update item quantities
|
||||||
|
- Mark items as bought/unbought
|
||||||
|
- View recently bought items (24-hour window)
|
||||||
|
- Long-press to edit items (mobile-friendly)
|
||||||
|
|
||||||
|
### 🖼️ Image Support
|
||||||
|
- Upload product images
|
||||||
|
- Automatic image optimization (800x800px, JPEG 85%)
|
||||||
|
- Base64 encoding for efficient storage
|
||||||
|
- 5MB maximum file size
|
||||||
|
- Support for JPEG, PNG, GIF, WebP
|
||||||
|
|
||||||
|
### 🏪 Smart Organization
|
||||||
|
- Item classification system (type, group, zone)
|
||||||
|
- 13 predefined store zones
|
||||||
|
- Sort by zone, alphabetically, or quantity
|
||||||
|
- Visual grouping by store location
|
||||||
|
- Intelligent item suggestions
|
||||||
|
|
||||||
|
### 🔍 Search & Suggestions
|
||||||
|
- Real-time autocomplete suggestions
|
||||||
|
- Fuzzy string matching (80% similarity threshold)
|
||||||
|
- Substring detection for partial matches
|
||||||
|
- Case-insensitive search
|
||||||
|
|
||||||
|
### 👥 User Management (Admin)
|
||||||
|
- View all registered users
|
||||||
|
- Update user roles
|
||||||
|
- Delete user accounts
|
||||||
|
- User activity tracking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Technology Stack
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
| Technology | Version | Purpose |
|
||||||
|
|------------|---------|---------|
|
||||||
|
| React | 19.2.0 | UI framework |
|
||||||
|
| React Router | 7.9.6 | Client-side routing |
|
||||||
|
| Axios | 1.13.2 | HTTP client |
|
||||||
|
| Vite | 7.2.2 | Build tool & dev server |
|
||||||
|
| TypeScript | 5.9.3 | Type safety |
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
| Technology | Version | Purpose |
|
||||||
|
|------------|---------|---------|
|
||||||
|
| Node.js | 20.x | Runtime environment |
|
||||||
|
| Express | 5.1.0 | Web framework |
|
||||||
|
| PostgreSQL | 8.16.0 | Database |
|
||||||
|
| JWT | 9.0.2 | Authentication |
|
||||||
|
| Bcrypt | 3.0.3 | Password hashing |
|
||||||
|
| Sharp | 0.34.5 | Image processing |
|
||||||
|
| Multer | 2.0.2 | File upload handling |
|
||||||
|
|
||||||
|
### DevOps
|
||||||
|
| Technology | Purpose |
|
||||||
|
|------------|---------|
|
||||||
|
| Docker | Containerization |
|
||||||
|
| Docker Compose | Multi-container orchestration |
|
||||||
|
| Gitea Actions | CI/CD pipeline |
|
||||||
|
| Nginx | Production static file serving |
|
||||||
|
| Nodemon | Development hot-reload |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Data Flow
|
||||||
|
|
||||||
|
### Adding an Item
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User
|
||||||
|
participant Frontend
|
||||||
|
participant API
|
||||||
|
participant Auth
|
||||||
|
participant RBAC
|
||||||
|
participant Database
|
||||||
|
|
||||||
|
User->>Frontend: Enter item name & quantity
|
||||||
|
User->>Frontend: Upload image (optional)
|
||||||
|
Frontend->>API: POST /list/add (FormData)
|
||||||
|
API->>Auth: Verify JWT token
|
||||||
|
Auth->>RBAC: Check role (Editor/Admin)
|
||||||
|
RBAC->>API: Authorization granted
|
||||||
|
API->>API: Process & optimize image
|
||||||
|
API->>Database: Check if item exists
|
||||||
|
alt Item exists & unbought
|
||||||
|
Database-->>API: Return existing item
|
||||||
|
API->>Database: UPDATE quantity
|
||||||
|
else Item exists & bought
|
||||||
|
Database-->>API: Return existing item
|
||||||
|
API->>Database: SET bought=false, UPDATE quantity
|
||||||
|
else Item doesn't exist
|
||||||
|
API->>Database: INSERT new item
|
||||||
|
end
|
||||||
|
API->>Database: INSERT grocery_history record
|
||||||
|
Database-->>API: Success
|
||||||
|
API-->>Frontend: 200 OK {message, addedBy}
|
||||||
|
Frontend-->>User: Show success message
|
||||||
|
Frontend->>API: GET /list (refresh)
|
||||||
|
API->>Database: SELECT unbought items
|
||||||
|
Database-->>API: Return items
|
||||||
|
API-->>Frontend: Item list
|
||||||
|
Frontend-->>User: Update UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User
|
||||||
|
participant Frontend
|
||||||
|
participant API
|
||||||
|
participant Database
|
||||||
|
|
||||||
|
User->>Frontend: Enter credentials
|
||||||
|
Frontend->>API: POST /auth/login
|
||||||
|
API->>Database: SELECT user WHERE username
|
||||||
|
Database-->>API: User record
|
||||||
|
API->>API: Compare password hash
|
||||||
|
alt Valid credentials
|
||||||
|
API->>API: Generate JWT token
|
||||||
|
API-->>Frontend: {token, role, username}
|
||||||
|
Frontend->>Frontend: Store token in localStorage
|
||||||
|
Frontend->>Frontend: Store role in AuthContext
|
||||||
|
Frontend->>API: GET /list (with token)
|
||||||
|
API->>API: Verify token
|
||||||
|
API-->>Frontend: Protected data
|
||||||
|
else Invalid credentials
|
||||||
|
API-->>Frontend: 401 Unauthorized
|
||||||
|
Frontend-->>User: Show error message
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Item Classification Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A[Add/Edit Item] --> B{Classification Exists?}
|
||||||
|
B -->|Yes| C[Display Existing]
|
||||||
|
B -->|No| D[Show Empty Form]
|
||||||
|
C --> E[User Edits]
|
||||||
|
D --> E
|
||||||
|
E --> F{Validate Classification}
|
||||||
|
F -->|Valid| G[Upsert to DB]
|
||||||
|
F -->|Invalid| H[Show Error]
|
||||||
|
G --> I[confidence=1.0, source='user']
|
||||||
|
I --> J[Update Item List]
|
||||||
|
H --> E
|
||||||
|
|
||||||
|
style G fill:#90EE90
|
||||||
|
style H fill:#FFB6C1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Role-Based Access Control
|
||||||
|
|
||||||
|
### Role Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
Admin (Full Access)
|
||||||
|
├── User Management
|
||||||
|
├── Item Management
|
||||||
|
└── View Access
|
||||||
|
|
||||||
|
Editor (Modify Access)
|
||||||
|
├── Add Items
|
||||||
|
├── Edit Items
|
||||||
|
├── Mark as Bought
|
||||||
|
└── View Access
|
||||||
|
|
||||||
|
Viewer (Read-Only)
|
||||||
|
└── View Lists Only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Matrix
|
||||||
|
|
||||||
|
| Feature | Viewer | Editor | Admin |
|
||||||
|
|---------|--------|--------|-------|
|
||||||
|
| View grocery list | ✅ | ✅ | ✅ |
|
||||||
|
| View recently bought | ✅ | ✅ | ✅ |
|
||||||
|
| Get suggestions | ✅ | ✅ | ✅ |
|
||||||
|
| View classifications | ✅ | ✅ | ✅ |
|
||||||
|
| Add items | ❌ | ✅ | ✅ |
|
||||||
|
| Edit items | ❌ | ✅ | ✅ |
|
||||||
|
| Upload images | ❌ | ✅ | ✅ |
|
||||||
|
| Mark items bought | ❌ | ✅ | ✅ |
|
||||||
|
| Update classifications | ❌ | ✅ | ✅ |
|
||||||
|
| View all users | ❌ | ❌ | ✅ |
|
||||||
|
| Update user roles | ❌ | ❌ | ✅ |
|
||||||
|
| Delete users | ❌ | ❌ | ✅ |
|
||||||
|
|
||||||
|
### Middleware Chain
|
||||||
|
|
||||||
|
Protected routes use a middleware chain pattern:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
router.post("/add",
|
||||||
|
auth, // Verify JWT token
|
||||||
|
requireRole(ROLES.EDITOR, ROLES.ADMIN), // Check role
|
||||||
|
upload.single("image"), // Handle file upload
|
||||||
|
processImage, // Optimize image
|
||||||
|
controller.addItem // Execute business logic
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- **Node.js** 20.x or higher
|
||||||
|
- **PostgreSQL** 8.x or higher
|
||||||
|
- **Docker** (optional, recommended)
|
||||||
|
- **Git** for version control
|
||||||
|
|
||||||
|
### Local Development Setup
|
||||||
|
|
||||||
|
1. **Clone the repository**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-org/costco-grocery-list.git
|
||||||
|
cd costco-grocery-list
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure environment variables**
|
||||||
|
|
||||||
|
Create `backend/.env`:
|
||||||
|
```env
|
||||||
|
# Database Configuration
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=your_password
|
||||||
|
DB_DATABASE=grocery_list
|
||||||
|
DB_PORT=5432
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
JWT_SECRET=your_secret_key_here
|
||||||
|
|
||||||
|
# CORS Configuration
|
||||||
|
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||||
|
|
||||||
|
# Server
|
||||||
|
PORT=5000
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start with Docker (Recommended)**
|
||||||
|
```bash
|
||||||
|
# Development mode with hot-reload
|
||||||
|
docker-compose -f docker-compose.dev.yml up
|
||||||
|
|
||||||
|
# Access application:
|
||||||
|
# Frontend: http://localhost:3000
|
||||||
|
# Backend: http://localhost:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Manual Setup (Alternative)**
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Database Setup**
|
||||||
|
|
||||||
|
Create PostgreSQL database and tables:
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE grocery_list;
|
||||||
|
|
||||||
|
-- See backend/models/*.js for table schemas
|
||||||
|
```
|
||||||
|
|
||||||
|
### First User Registration
|
||||||
|
|
||||||
|
The first registered user should be manually promoted to admin:
|
||||||
|
```sql
|
||||||
|
UPDATE users SET role = 'admin' WHERE username = 'your_username';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 API Documentation
|
||||||
|
|
||||||
|
For detailed API documentation including all endpoints, request/response formats, and examples, see [API_DOCUMENTATION.md](./API_DOCUMENTATION.md).
|
||||||
|
|
||||||
|
### Quick Reference
|
||||||
|
|
||||||
|
**Base URL:** `http://localhost:5000/api`
|
||||||
|
|
||||||
|
**Common Endpoints:**
|
||||||
|
- `POST /auth/register` - Register new user
|
||||||
|
- `POST /auth/login` - Authenticate user
|
||||||
|
- `GET /list` - Get all unbought items
|
||||||
|
- `POST /list/add` - Add or update item
|
||||||
|
- `POST /list/mark-bought` - Mark item as bought
|
||||||
|
- `GET /list/recently-bought` - Get items bought in last 24h
|
||||||
|
- `GET /admin/users` - Get all users (Admin only)
|
||||||
|
|
||||||
|
**Authentication:**
|
||||||
|
All protected endpoints require a JWT token in the Authorization header:
|
||||||
|
```
|
||||||
|
Authorization: Bearer <your_jwt_token>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
costco-grocery-list/
|
||||||
|
├── .gitea/
|
||||||
|
│ └── workflows/
|
||||||
|
│ └── deploy.yml # CI/CD pipeline configuration
|
||||||
|
├── backend/
|
||||||
|
│ ├── constants/
|
||||||
|
│ │ └── classifications.js # Item type/zone definitions
|
||||||
|
│ ├── controllers/
|
||||||
|
│ │ ├── auth.controller.js # Authentication logic
|
||||||
|
│ │ ├── lists.controller.js # Grocery list logic
|
||||||
|
│ │ └── users.controller.js # User management logic
|
||||||
|
│ ├── db/
|
||||||
|
│ │ └── pool.js # PostgreSQL connection pool
|
||||||
|
│ ├── middleware/
|
||||||
|
│ │ ├── auth.js # JWT verification
|
||||||
|
│ │ ├── rbac.js # Role-based access control
|
||||||
|
│ │ └── image.js # Image upload & processing
|
||||||
|
│ ├── models/
|
||||||
|
│ │ ├── list.model.js # Grocery list database queries
|
||||||
|
│ │ └── user.model.js # User database queries
|
||||||
|
│ ├── routes/
|
||||||
|
│ │ ├── auth.routes.js # Authentication routes
|
||||||
|
│ │ ├── list.routes.js # Grocery list routes
|
||||||
|
│ │ ├── admin.routes.js # Admin routes
|
||||||
|
│ │ └── users.routes.js # User routes
|
||||||
|
│ ├── app.js # Express app configuration
|
||||||
|
│ ├── server.js # Server entry point
|
||||||
|
│ ├── Dockerfile # Backend container config
|
||||||
|
│ └── package.json
|
||||||
|
├── frontend/
|
||||||
|
│ ├── public/ # Static assets
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── api/
|
||||||
|
│ │ │ ├── axios.js # Axios instance with interceptors
|
||||||
|
│ │ │ ├── auth.js # Auth API calls
|
||||||
|
│ │ │ ├── list.js # List API calls
|
||||||
|
│ │ │ └── users.js # User API calls
|
||||||
|
│ │ ├── components/
|
||||||
|
│ │ │ ├── common/ # Reusable components
|
||||||
|
│ │ │ ├── forms/ # Form components
|
||||||
|
│ │ │ ├── items/ # Item-related components
|
||||||
|
│ │ │ ├── layout/ # Layout components
|
||||||
|
│ │ │ └── modals/ # Modal dialogs
|
||||||
|
│ │ ├── constants/
|
||||||
|
│ │ │ └── roles.js # Role constants
|
||||||
|
│ │ ├── context/
|
||||||
|
│ │ │ └── AuthContext.jsx # Authentication context
|
||||||
|
│ │ ├── pages/
|
||||||
|
│ │ │ ├── AdminPanel.jsx # Admin user management
|
||||||
|
│ │ │ ├── GroceryList.jsx # Main grocery list page
|
||||||
|
│ │ │ ├── Login.jsx # Login page
|
||||||
|
│ │ │ └── Register.jsx # Registration page
|
||||||
|
│ │ ├── styles/
|
||||||
|
│ │ │ ├── theme.css # CSS custom properties
|
||||||
|
│ │ │ ├── components/ # Component-specific styles
|
||||||
|
│ │ │ └── pages/ # Page-specific styles
|
||||||
|
│ │ ├── utils/
|
||||||
|
│ │ │ ├── PrivateRoute.jsx # Route protection
|
||||||
|
│ │ │ ├── RoleGuard.jsx # Role-based component guard
|
||||||
|
│ │ │ └── stringSimilarity.js # Fuzzy matching algorithm
|
||||||
|
│ │ ├── App.jsx # Root component with routing
|
||||||
|
│ │ └── main.tsx # Application entry point
|
||||||
|
│ ├── Dockerfile # Production build (nginx)
|
||||||
|
│ ├── Dockerfile.dev # Development build (Vite)
|
||||||
|
│ └── package.json
|
||||||
|
├── docker-compose.yml # Production compose file
|
||||||
|
├── docker-compose.dev.yml # Development compose file
|
||||||
|
├── docker-compose.prod.yml # Local production testing
|
||||||
|
├── API_DOCUMENTATION.md # Detailed API reference
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Directories
|
||||||
|
|
||||||
|
- **`backend/constants/`** - Classification definitions (item types, groups, zones)
|
||||||
|
- **`backend/middleware/`** - Authentication, authorization, and image processing
|
||||||
|
- **`frontend/src/components/`** - Organized into 5 categories (common, forms, items, layout, modals)
|
||||||
|
- **`frontend/src/styles/`** - Theme system with CSS custom properties
|
||||||
|
- **`.gitea/workflows/`** - CI/CD pipeline for automated deployment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔨 Development Workflow
|
||||||
|
|
||||||
|
### Component Organization
|
||||||
|
|
||||||
|
Frontend components are organized by feature:
|
||||||
|
|
||||||
|
```
|
||||||
|
components/
|
||||||
|
├── common/ # Reusable UI components
|
||||||
|
│ ├── ErrorMessage.jsx
|
||||||
|
│ ├── FloatingActionButton.jsx
|
||||||
|
│ ├── FormInput.jsx
|
||||||
|
│ ├── SortDropdown.jsx
|
||||||
|
│ └── UserRoleCard.jsx
|
||||||
|
├── forms/ # Form components
|
||||||
|
│ ├── AddItemForm.jsx
|
||||||
|
│ ├── ClassificationSection.jsx
|
||||||
|
│ └── ImageUploadSection.jsx
|
||||||
|
├── items/ # Item-related components
|
||||||
|
│ ├── GroceryItem.tsx
|
||||||
|
│ ├── GroceryListItem.jsx
|
||||||
|
│ └── SuggestionList.tsx
|
||||||
|
├── layout/ # Layout components
|
||||||
|
│ ├── AppLayout.jsx
|
||||||
|
│ └── Navbar.jsx
|
||||||
|
└── modals/ # Modal dialogs
|
||||||
|
├── AddImageModal.jsx
|
||||||
|
├── AddItemWithDetailsModal.jsx
|
||||||
|
├── ConfirmBuyModal.jsx
|
||||||
|
├── EditItemModal.jsx
|
||||||
|
├── ImageModal.jsx
|
||||||
|
├── ImageUploadModal.jsx
|
||||||
|
├── ItemClassificationModal.jsx
|
||||||
|
└── SimilarItemModal.jsx
|
||||||
|
```
|
||||||
|
|
||||||
|
Each subdirectory has an `index.js` barrel export for cleaner imports.
|
||||||
|
|
||||||
|
### Theme System
|
||||||
|
|
||||||
|
The application uses CSS custom properties for consistent theming:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Theme variables defined in frontend/src/styles/theme.css */
|
||||||
|
:root {
|
||||||
|
/* Colors */
|
||||||
|
--color-primary: #0066cc;
|
||||||
|
--color-secondary: #6c757d;
|
||||||
|
--color-success: #28a745;
|
||||||
|
--color-danger: #dc3545;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--spacing-xs: 0.25rem;
|
||||||
|
--spacing-sm: 0.5rem;
|
||||||
|
--spacing-md: 1rem;
|
||||||
|
--spacing-lg: 1.5rem;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', ...;
|
||||||
|
--font-size-base: 1rem;
|
||||||
|
|
||||||
|
/* And many more... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Standards
|
||||||
|
|
||||||
|
- **Backend**: CommonJS modules, async/await for asynchronous operations
|
||||||
|
- **Frontend**: ES6 modules, functional components with hooks
|
||||||
|
- **TypeScript**: Used for type-safe components (gradually migrating)
|
||||||
|
- **Naming**: camelCase for functions/variables, PascalCase for components
|
||||||
|
- **File Structure**: Feature-based organization over type-based
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Currently, the project uses manual testing. Automated testing infrastructure is planned for future development.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚢 Deployment
|
||||||
|
|
||||||
|
### CI/CD Pipeline
|
||||||
|
|
||||||
|
The application uses Gitea Actions for automated deployment:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .gitea/workflows/deploy.yml
|
||||||
|
|
||||||
|
Workflow:
|
||||||
|
1. Build Stage:
|
||||||
|
- Install dependencies
|
||||||
|
- Run tests (if present)
|
||||||
|
- Build Docker images
|
||||||
|
- Tag with :latest and :<commit-sha>
|
||||||
|
- Push to private registry
|
||||||
|
|
||||||
|
2. Deploy Stage:
|
||||||
|
- SSH to production server
|
||||||
|
- Upload docker-compose.yml
|
||||||
|
- Pull latest images
|
||||||
|
- Restart containers
|
||||||
|
- Prune old images
|
||||||
|
|
||||||
|
3. Notify Stage:
|
||||||
|
- Send deployment status via webhook
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Production Server
|
||||||
|
├── Nginx Reverse Proxy (Port 80/443)
|
||||||
|
│ ├── /api → Backend Container (Port 5000)
|
||||||
|
│ └── /* → Frontend Container (Port 3000)
|
||||||
|
├── Docker Compose
|
||||||
|
│ ├── backend:latest (from registry)
|
||||||
|
│ └── frontend:latest (from registry)
|
||||||
|
└── PostgreSQL (External, not containerized)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Configuration
|
||||||
|
|
||||||
|
**Production (`docker-compose.yml`):**
|
||||||
|
- Pulls pre-built images from registry
|
||||||
|
- Uses external PostgreSQL database
|
||||||
|
- Environment configured via `backend.env` and `frontend.env`
|
||||||
|
- Automatic restart on failure
|
||||||
|
|
||||||
|
**Development (`docker-compose.dev.yml`):**
|
||||||
|
- Builds images locally
|
||||||
|
- Volume mounts for hot-reload
|
||||||
|
- Uses local `.env` files
|
||||||
|
- Nodemon for backend, Vite HMR for frontend
|
||||||
|
|
||||||
|
### Deployment Process
|
||||||
|
|
||||||
|
1. **Commit and push to `main` branch**
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Your commit message"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **CI/CD automatically:**
|
||||||
|
- Runs tests
|
||||||
|
- Builds Docker images
|
||||||
|
- Pushes to registry
|
||||||
|
- Deploys to production
|
||||||
|
|
||||||
|
3. **Manual deployment (if needed):**
|
||||||
|
```bash
|
||||||
|
ssh user@production-server
|
||||||
|
cd /opt/costco-app
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d --remove-orphans
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗄️ Database Schema
|
||||||
|
|
||||||
|
### Entity Relationship Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
users ||--o{ grocery_list : creates
|
||||||
|
users ||--o{ grocery_history : adds
|
||||||
|
grocery_list ||--o{ grocery_history : tracks
|
||||||
|
grocery_list ||--o| item_classification : has
|
||||||
|
|
||||||
|
users {
|
||||||
|
int id PK
|
||||||
|
string username UK
|
||||||
|
string password
|
||||||
|
string name
|
||||||
|
string role
|
||||||
|
}
|
||||||
|
|
||||||
|
grocery_list {
|
||||||
|
int id PK
|
||||||
|
string item_name
|
||||||
|
int quantity
|
||||||
|
boolean bought
|
||||||
|
bytea item_image
|
||||||
|
string image_mime_type
|
||||||
|
int added_by FK
|
||||||
|
timestamp modified_on
|
||||||
|
}
|
||||||
|
|
||||||
|
item_classification {
|
||||||
|
int id PK,FK
|
||||||
|
string item_type
|
||||||
|
string item_group
|
||||||
|
string zone
|
||||||
|
float confidence
|
||||||
|
string source
|
||||||
|
}
|
||||||
|
|
||||||
|
grocery_history {
|
||||||
|
int id PK
|
||||||
|
int list_item_id FK
|
||||||
|
int quantity
|
||||||
|
int added_by FK
|
||||||
|
timestamp added_on
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Relationships
|
||||||
|
|
||||||
|
- **users → grocery_list**: One-to-many (creator relationship)
|
||||||
|
- **users → grocery_history**: One-to-many (contributor relationship)
|
||||||
|
- **grocery_list → grocery_history**: One-to-many (tracks all additions/modifications)
|
||||||
|
- **grocery_list → item_classification**: One-to-one (optional classification data)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please follow these guidelines:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||||
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
5. Open a Pull Request
|
||||||
|
|
||||||
|
### Development Guidelines
|
||||||
|
|
||||||
|
- Follow existing code style and structure
|
||||||
|
- Add comments for complex logic
|
||||||
|
- Update documentation for new features
|
||||||
|
- Test thoroughly before submitting PR
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👤 Author
|
||||||
|
|
||||||
|
**Nico Saya**
|
||||||
|
- Repository: [git.nicosaya.com/nalalangan/costco-grocery-list](https://git.nicosaya.com/nalalangan/costco-grocery-list)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🙏 Acknowledgments
|
||||||
|
|
||||||
|
- React team for the excellent framework
|
||||||
|
- Express.js community for robust server framework
|
||||||
|
- PostgreSQL for reliable data persistence
|
||||||
|
- Sharp for efficient image processing
|
||||||
|
- All contributors and users of this application
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
For issues, questions, or feature requests, please:
|
||||||
|
1. Check existing issues in the repository
|
||||||
|
2. Create a new issue with detailed description
|
||||||
|
3. Include steps to reproduce (for bugs)
|
||||||
|
4. Tag appropriately (bug, enhancement, question, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** January 4, 2026
|
||||||
@ -1,34 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Move to common/
|
|
||||||
mv components/FloatingActionButton.jsx components/common/
|
|
||||||
mv components/SortDropdown.jsx components/common/
|
|
||||||
mv components/ErrorMessage.jsx components/common/
|
|
||||||
mv components/FormInput.jsx components/common/
|
|
||||||
mv components/UserRoleCard.jsx components/common/
|
|
||||||
|
|
||||||
# Move to modals/
|
|
||||||
mv components/AddItemWithDetailsModal.jsx components/modals/
|
|
||||||
mv components/EditItemModal.jsx components/modals/
|
|
||||||
mv components/SimilarItemModal.jsx components/modals/
|
|
||||||
mv components/ConfirmBuyModal.jsx components/modals/
|
|
||||||
mv components/ImageModal.jsx components/modals/
|
|
||||||
mv components/AddImageModal.jsx components/modals/
|
|
||||||
mv components/ImageUploadModal.jsx components/modals/
|
|
||||||
mv components/ItemClassificationModal.jsx components/modals/
|
|
||||||
|
|
||||||
# Move to forms/
|
|
||||||
mv components/AddItemForm.jsx components/forms/
|
|
||||||
mv components/ImageUploadSection.jsx components/forms/
|
|
||||||
mv components/ClassificationSection.jsx components/forms/
|
|
||||||
|
|
||||||
# Move to items/
|
|
||||||
mv components/GroceryListItem.jsx components/items/
|
|
||||||
mv components/GroceryItem.tsx components/items/
|
|
||||||
mv components/SuggestionList.tsx components/items/
|
|
||||||
|
|
||||||
# Move to layout/
|
|
||||||
mv components/AppLayout.jsx components/layout/
|
|
||||||
mv components/Navbar.jsx components/layout/
|
|
||||||
|
|
||||||
echo "Components moved successfully!"
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Move page styles
|
|
||||||
mv styles/GroceryList.css styles/pages/
|
|
||||||
mv styles/Login.css styles/pages/
|
|
||||||
mv styles/Register.css styles/pages/
|
|
||||||
|
|
||||||
# Move component styles
|
|
||||||
mv styles/Navbar.css styles/components/
|
|
||||||
mv styles/AddItemWithDetailsModal.css styles/components/
|
|
||||||
mv styles/EditItemModal.css styles/components/
|
|
||||||
mv styles/ImageUploadSection.css styles/components/
|
|
||||||
mv styles/ClassificationSection.css styles/components/
|
|
||||||
|
|
||||||
echo "Styles moved successfully!"
|
|
||||||
Loading…
Reference in New Issue
Block a user