- Created main application entry point in `main.go`. - Added configuration management in `config/config.go` and tests in `config/config_test.go`. - Implemented handlers for book and stationery uploads in `handlers/book.go` and `handlers/stationery.go`, including validation logic. - Established database connection and services in `services/database.go` and `services/book_service.go`. - Defined models for books and stationery in `models/book.go` and `models/stationery.go`. - Set up Firebase integration for image uploads in `services/firebase.go`. - Created migration scripts for database schema in `migrations/001_create_tables.sql` and subsequent updates. - Added CORS and error handling middleware. - Included comprehensive tests for handlers, services, and utilities. - Documented API endpoints and usage in `README.md` and migration instructions in `migrations/README.md`. - Introduced `.gitignore` to exclude unnecessary files and directories. - Added Go module support with `go.mod` and `go.sum` files. - Implemented utility functions for slug generation and validation in `utils/slug.go` and `utils/validation.go`.
491 lines
13 KiB
Markdown
491 lines
13 KiB
Markdown
# 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`).
|