- Cleaned up the .gitea/workflows/test.yml file by removing an extra blank line for better readability.
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_URLenvironment 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):
{
"status": "ok",
"database": "connected",
"firebase": "connected"
}
Service Unavailable (503):
{
"status": "degraded",
"database": "disconnected",
"firebase": "connected"
}
Response Fields:
status(string):"ok"if all services connected,"degraded"if any service unavailabledatabase(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):
{
"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): Alwaystrueon successdata(object): Contains the created book databook_code(string): UUID generated for the bookbook_name(string): Book name as providedimage_url(string): Public URL of uploaded image in Firebase Storageslug(string): URL-friendly slug generated from book namecreated_at(string): ISO 8601 timestamp of creation
Error Responses:
400 Bad Request - Validation error:
{
"success": false,
"error": "book_name is required"
}
413 Payload Too Large - File size exceeds limit:
{
"success": false,
"error": "File size exceeds 10MB limit"
}
500 Internal Server Error - Server error:
{
"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):
{
"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): Alwaystrueon successdata(object): Contains the created stationery datastationery_code(string): UUID generated for the stationery itemstationery_name(string): Stationery name as providedimage_url(string): Public URL of uploaded image in Firebase Storageslug(string): URL-friendly slug generated from stationery namecreated_at(string): ISO 8601 timestamp of creation
Error Responses:
400 Bad Request - Validation error:
{
"success": false,
"error": "stationery_name is required"
}
413 Payload Too Large - File size exceeds limit:
{
"success": false,
"error": "File size exceeds 10MB limit"
}
500 Internal Server Error - Server error:
{
"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:
{
"success": true,
"data": { ... }
}
Error Response:
{
"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
-
Content-Type Header: For file uploads, use
multipart/form-data. Do NOT manually set theContent-Typeheader - let the browser/client set it automatically with the proper boundary. -
Image Processing: The backend expects images to be processed on the frontend (resized to 1000x1000, background removed) before upload. Upload the processed PNG image.
-
Auto-Generated Fields: The following fields are automatically generated by the backend:
book_code/stationery_code: UUID v4slug: Generated from product name (lowercase, spaces to hyphens, special characters removed)created_at: Current timestampupdated_at: Current timestamp
-
File Upload: Only one image file per request. The file field name must be
image. -
Error Messages: Always check the
errorfield in error responses for user-friendly error messages. -
CORS: The backend is configured to accept requests from the frontend URL specified in
FRONTEND_URLenvironment variable (default:http://localhost:5173).