# JD Book Uploader - Backend API Documentation ## Overview Backend API for uploading book and stationery products with images to Firebase Storage and storing metadata in PostgreSQL. ## Base URL - **Development**: `http://localhost:8080` - **Production**: Configure via `FRONTEND_URL` environment variable --- ## API Endpoints ### 1. Health Check **Endpoint:** `GET /api/health` **Description:** Check the health status of the API and its dependencies (database and Firebase). **Request:** - **Method:** `GET` - **Path:** `/api/health` - **Headers:** None required - **Body:** None **Response:** **Success (200 OK):** ```json { "status": "ok", "database": "connected", "firebase": "connected" } ``` **Service Unavailable (503):** ```json { "status": "degraded", "database": "disconnected", "firebase": "connected" } ``` **Response Fields:** - `status` (string): `"ok"` if all services connected, `"degraded"` if any service unavailable - `database` (string): `"connected"`, `"disconnected"`, or `"not_initialized"` - `firebase` (string): `"connected"`, `"disconnected"`, or `"not_initialized"` --- ### 2. Upload Book **Endpoint:** `POST /api/books` **Description:** Upload a book product with cover image. Image is uploaded to Firebase Storage and book metadata is stored in PostgreSQL. **Request:** - **Method:** `POST` - **Path:** `/api/books` - **Content-Type:** `multipart/form-data` - **Body:** FormData with the following fields: **Required Parameters:** - `image` (File) - Book cover image file - **Type:** File (multipart/form-data) - **Allowed types:** `image/png`, `image/jpeg`, `image/jpg`, `image/webp` - **Max size:** 10MB - **Required:** Yes - `book_name` (string) - Book name - **Type:** String - **Max length:** 200 characters - **Required:** Yes - **Validation:** Not empty - `cost` (number) - Cost price - **Type:** Number (decimal) - **Required:** Yes - **Validation:** Must be a positive number - `price` (number) - Selling price - **Type:** Number (decimal) - **Required:** Yes - **Validation:** Must be a positive number - `quantity` (integer) - Stock quantity - **Type:** Integer - **Required:** Yes - **Validation:** Must be a positive integer - `publisher_author` (string) - Publisher or author name - **Type:** String - **Max length:** 200 characters - **Required:** Yes - **Validation:** Not empty - `category` (string) - Book category - **Type:** String - **Max length:** 100 characters - **Required:** Yes - **Validation:** Not empty **Optional Parameters:** - `discount` (number) - Discount amount - **Type:** Number (decimal) - **Default:** 0 - **Required:** No - **Validation:** Must be non-negative (>= 0) - `description` (string) - Book description - **Type:** String - **Max length:** 1000 characters - **Required:** No **Request Body Format:** ``` Content-Type: multipart/form-data; boundary=----WebKitFormBoundary... ------WebKitFormBoundary... Content-Disposition: form-data; name="image"; filename="book-cover.png" Content-Type: image/png [binary image data] ------WebKitFormBoundary... Content-Disposition: form-data; name="book_name" The Great Gatsby ------WebKitFormBoundary... Content-Disposition: form-data; name="cost" 15.50 ------WebKitFormBoundary... Content-Disposition: form-data; name="price" 24.99 ------WebKitFormBoundary... Content-Disposition: form-data; name="discount" 0 ------WebKitFormBoundary... Content-Disposition: form-data; name="quantity" 100 ------WebKitFormBoundary... Content-Disposition: form-data; name="publisher_author" F. Scott Fitzgerald ------WebKitFormBoundary... Content-Disposition: form-data; name="category" Fiction ------WebKitFormBoundary... Content-Disposition: form-data; name="description" A classic American novel ------WebKitFormBoundary...-- ``` **Success Response (201 Created):** ```json { "success": true, "data": { "book_code": "550e8400-e29b-41d4-a716-446655440000", "book_name": "The Great Gatsby", "image_url": "https://storage.googleapis.com/download/storage/v1/b/kaisa-341a6.appspot.com/o/images%2F2025%2F11%2F1732123456_abc123.png?generation=1732123456789&alt=media", "slug": "the-great-gatsby", "created_at": "2025-11-20T17:30:00Z" } } ``` **Response Fields:** - `success` (boolean): Always `true` on success - `data` (object): Contains the created book data - `book_code` (string): UUID generated for the book - `book_name` (string): Book name as provided - `image_url` (string): Public URL of uploaded image in Firebase Storage - `slug` (string): URL-friendly slug generated from book name - `created_at` (string): ISO 8601 timestamp of creation **Error Responses:** **400 Bad Request** - Validation error: ```json { "success": false, "error": "book_name is required" } ``` **413 Payload Too Large** - File size exceeds limit: ```json { "success": false, "error": "File size exceeds 10MB limit" } ``` **500 Internal Server Error** - Server error: ```json { "success": false, "error": "Failed to upload image" } ``` **Common Error Messages:** - `"Image file is required"` - No image file provided - `"Invalid file type. Only PNG, JPEG, and WEBP are allowed"` - Unsupported file type - `"File size exceeds 10MB limit"` - File too large - `"book_name is required"` - Missing required field - `"Invalid cost value"` - Invalid number format or negative value - `"Failed to parse form data"` - Malformed request - `"Failed to upload image"` - Firebase upload error - `"Failed to create book"` - Database insert error --- ### 3. Upload Stationery **Endpoint:** `POST /api/stationery` **Description:** Upload a stationery product with image. Image is uploaded to Firebase Storage and stationery metadata is stored in PostgreSQL. **Request:** - **Method:** `POST` - **Path:** `/api/stationery` - **Content-Type:** `multipart/form-data` - **Body:** FormData with the following fields: **Required Parameters:** - `image` (File) - Stationery image file - **Type:** File (multipart/form-data) - **Allowed types:** `image/png`, `image/jpeg`, `image/jpg`, `image/webp` - **Max size:** 10MB - **Required:** Yes - `stationery_name` (string) - Stationery name - **Type:** String - **Max length:** 200 characters - **Required:** Yes - **Validation:** Not empty - `cost` (number) - Cost price - **Type:** Number (decimal) - **Required:** Yes - **Validation:** Must be a positive number - `price` (number) - Selling price - **Type:** Number (decimal) - **Required:** Yes - **Validation:** Must be a positive number - `quantity` (integer) - Stock quantity - **Type:** Integer - **Required:** Yes - **Validation:** Must be a positive integer - `category` (string) - Stationery category - **Type:** String - **Max length:** 100 characters - **Required:** Yes - **Validation:** Not empty **Optional Parameters:** - `discount` (number) - Discount amount - **Type:** Number (decimal) - **Default:** 0 - **Required:** No - **Validation:** Must be non-negative (>= 0) - `color` (string) - Product color - **Type:** String - **Required:** No - `material` (string) - Product material - **Type:** String - **Required:** No - `dimensions` (string) - Product dimensions - **Type:** String - **Required:** No - `description` (string) - Product description - **Type:** String - **Max length:** 1000 characters - **Required:** No **Request Body Format:** ``` Content-Type: multipart/form-data; boundary=----WebKitFormBoundary... ------WebKitFormBoundary... Content-Disposition: form-data; name="image"; filename="pen.png" Content-Type: image/png [binary image data] ------WebKitFormBoundary... Content-Disposition: form-data; name="stationery_name" Blue Ballpoint Pen ------WebKitFormBoundary... Content-Disposition: form-data; name="cost" 2.50 ------WebKitFormBoundary... Content-Disposition: form-data; name="price" 5.99 ------WebKitFormBoundary... Content-Disposition: form-data; name="discount" 0 ------WebKitFormBoundary... Content-Disposition: form-data; name="quantity" 200 ------WebKitFormBoundary... Content-Disposition: form-data; name="category" Writing ------WebKitFormBoundary... Content-Disposition: form-data; name="color" Blue ------WebKitFormBoundary... Content-Disposition: form-data; name="material" Plastic ------WebKitFormBoundary... Content-Disposition: form-data; name="dimensions" 15cm ------WebKitFormBoundary... Content-Disposition: form-data; name="description" Smooth writing ballpoint pen ------WebKitFormBoundary...-- ``` **Success Response (201 Created):** ```json { "success": true, "data": { "stationery_code": "550e8400-e29b-41d4-a716-446655440001", "stationery_name": "Blue Ballpoint Pen", "image_url": "https://storage.googleapis.com/download/storage/v1/b/kaisa-341a6.appspot.com/o/images%2F2025%2F11%2F1732123456_def456.png?generation=1732123456789&alt=media", "slug": "blue-ballpoint-pen", "created_at": "2025-11-20T17:30:00Z" } } ``` **Response Fields:** - `success` (boolean): Always `true` on success - `data` (object): Contains the created stationery data - `stationery_code` (string): UUID generated for the stationery item - `stationery_name` (string): Stationery name as provided - `image_url` (string): Public URL of uploaded image in Firebase Storage - `slug` (string): URL-friendly slug generated from stationery name - `created_at` (string): ISO 8601 timestamp of creation **Error Responses:** **400 Bad Request** - Validation error: ```json { "success": false, "error": "stationery_name is required" } ``` **413 Payload Too Large** - File size exceeds limit: ```json { "success": false, "error": "File size exceeds 10MB limit" } ``` **500 Internal Server Error** - Server error: ```json { "success": false, "error": "Failed to upload image" } ``` **Common Error Messages:** - `"Image file is required"` - No image file provided - `"Invalid file type. Only PNG, JPEG, and WEBP are allowed"` - Unsupported file type - `"File size exceeds 10MB limit"` - File too large - `"stationery_name is required"` - Missing required field - `"Invalid cost value"` - Invalid number format or negative value - `"Failed to parse form data"` - Malformed request - `"Failed to upload image"` - Firebase upload error - `"Failed to create stationery"` - Database insert error --- ## Response Format All API responses follow a consistent structure: **Success Response:** ```json { "success": true, "data": { ... } } ``` **Error Response:** ```json { "success": false, "error": "Error message" } ``` --- ## HTTP Status Codes - **200 OK** - Health check successful - **201 Created** - Resource created successfully (book/stationery uploaded) - **400 Bad Request** - Validation error or invalid input - **413 Payload Too Large** - File size exceeds 10MB limit - **500 Internal Server Error** - Server error (database, Firebase, or internal error) - **503 Service Unavailable** - Service dependencies (database or Firebase) unavailable --- ## Field Validation Summary ### Book Upload Fields | Field | Type | Required | Max Length | Validation Rules | |-------|------|----------|------------|------------------| | `image` | File | Yes | 10MB | PNG, JPEG, or WEBP only | | `book_name` | string | Yes | 200 | Not empty | | `cost` | number | Yes | - | Positive number (> 0) | | `price` | number | Yes | - | Positive number (> 0) | | `discount` | number | No | - | Non-negative (>= 0), default: 0 | | `quantity` | integer | Yes | - | Positive integer (> 0) | | `publisher_author` | string | Yes | 200 | Not empty | | `category` | string | Yes | 100 | Not empty | | `description` | string | No | 1000 | - | ### Stationery Upload Fields | Field | Type | Required | Max Length | Validation Rules | |-------|------|----------|------------|------------------| | `image` | File | Yes | 10MB | PNG, JPEG, or WEBP only | | `stationery_name` | string | Yes | 200 | Not empty | | `cost` | number | Yes | - | Positive number (> 0) | | `price` | number | Yes | - | Positive number (> 0) | | `discount` | number | No | - | Non-negative (>= 0), default: 0 | | `quantity` | integer | Yes | - | Positive integer (> 0) | | `category` | string | Yes | 100 | Not empty | | `color` | string | No | - | - | | `material` | string | No | - | - | | `dimensions` | string | No | - | - | | `description` | string | No | 1000 | - | --- ## Notes 1. **Content-Type Header**: For file uploads, use `multipart/form-data`. Do NOT manually set the `Content-Type` header - let the browser/client set it automatically with the proper boundary. 2. **Image Processing**: The backend expects images to be processed on the frontend (resized to 1000x1000, background removed) before upload. Upload the processed PNG image. 3. **Auto-Generated Fields**: The following fields are automatically generated by the backend: - `book_code` / `stationery_code`: UUID v4 - `slug`: Generated from product name (lowercase, spaces to hyphens, special characters removed) - `created_at`: Current timestamp - `updated_at`: Current timestamp 4. **File Upload**: Only one image file per request. The file field name must be `image`. 5. **Error Messages**: Always check the `error` field in error responses for user-friendly error messages. 6. **CORS**: The backend is configured to accept requests from the frontend URL specified in `FRONTEND_URL` environment variable (default: `http://localhost:5173`).