Add user management, API testing tools, and fix database connection issues
- Added user routes (CRUD operations) for managing users - Created Postman collection for API testing - Added HTML test page for interactive API testing - Fixed UUID handling for species_id and breed_id - Added user validation for listings and locations - Improved error handling and validation - Added static file serving for test page - Updated server configuration with all routes
This commit is contained in:
parent
9f6f7a0bab
commit
b3899dc14d
|
|
@ -0,0 +1,637 @@
|
|||
{
|
||||
"info": {
|
||||
"_postman_id": "buysellservice-api-collection",
|
||||
"name": "BuySellService API",
|
||||
"description": "Complete API collection for Livestock Marketplace - BuySellService",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Users",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create User",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"id\": \"aaf295cb-a19e-4179-a2df-31c0c64ea9f4\",\n \"name\": \"Test User\",\n \"phone_number\": \"+919876543210\",\n \"avatar_url\": null,\n \"language\": \"en\",\n \"timezone\": \"Asia/Kolkata\",\n \"country_code\": \"+91\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/users",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"users"
|
||||
]
|
||||
},
|
||||
"description": "Create a new user. You can provide a specific UUID or let the system generate one."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get All Users",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/users?limit=100&offset=0",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"users"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "100"
|
||||
},
|
||||
{
|
||||
"key": "offset",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get a list of all users"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get User by ID",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/users/:userId",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"users",
|
||||
":userId"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "userId",
|
||||
"value": "aaf295cb-a19e-4179-a2df-31c0c64ea9f4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get a single user by UUID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Update User",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"Updated User Name\",\n \"phone_number\": \"+919876543210\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/users/:userId",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"users",
|
||||
":userId"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "userId",
|
||||
"value": "aaf295cb-a19e-4179-a2df-31c0c64ea9f4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Update user information"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Listings",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get All Listings",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/listings?status=active&limit=20&offset=0",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"listings"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "status",
|
||||
"value": "active"
|
||||
},
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "20"
|
||||
},
|
||||
{
|
||||
"key": "offset",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get all active listings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get Listing by ID",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/listings/:listingId",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"listings",
|
||||
":listingId"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "listingId",
|
||||
"value": "your-listing-uuid-here"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get a single listing with full details"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Create Listing",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"seller_id\": \"aaf295cb-a19e-4179-a2df-31c0c64ea9f4\",\n \"title\": \"High-yield Gir cow for sale\",\n \"price\": 55000,\n \"currency\": \"INR\",\n \"is_negotiable\": true,\n \"listing_type\": \"sale\",\n \"animal\": {\n \"species_id\": \"your-species-uuid\",\n \"breed_id\": \"your-breed-uuid\",\n \"sex\": \"F\",\n \"age_months\": 36,\n \"weight_kg\": 450,\n \"color_markings\": \"Brown with white patches\",\n \"quantity\": 1,\n \"purpose\": \"dairy\",\n \"health_status\": \"healthy\",\n \"vaccinated\": true,\n \"dewormed\": true,\n \"pregnancy_status\": \"pregnant\",\n \"milk_yield_litre_per_day\": 15,\n \"ear_tag_no\": \"TAG-12345\",\n \"description\": \"Calm nature, easy to handle.\"\n },\n \"media\": [\n {\n \"media_url\": \"https://cdn.app.com/listings/abc1.jpg\",\n \"media_type\": \"image\",\n \"is_primary\": true,\n \"sort_order\": 1\n }\n ]\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/listings",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"listings"
|
||||
]
|
||||
},
|
||||
"description": "Create a new listing with animal details"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Update Listing",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"title\": \"Updated title\",\n \"price\": 52000,\n \"status\": \"active\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/listings/:listingId",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"listings",
|
||||
":listingId"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "listingId",
|
||||
"value": "your-listing-uuid-here"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Update listing information"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Search Listings",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/listings/search?q=cow&limit=20&offset=0",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"listings",
|
||||
"search"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "q",
|
||||
"value": "cow"
|
||||
},
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "20"
|
||||
},
|
||||
{
|
||||
"key": "offset",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Search listings by text query"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Near Me Search",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/listings/near-me?lat=18.5204&lng=73.8567&radius_meters=100000&limit=20",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"listings",
|
||||
"near-me"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "lat",
|
||||
"value": "18.5204"
|
||||
},
|
||||
{
|
||||
"key": "lng",
|
||||
"value": "73.8567"
|
||||
},
|
||||
{
|
||||
"key": "radius_meters",
|
||||
"value": "100000"
|
||||
},
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "20"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Find listings near a specific location"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get Species",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/listings/species",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"listings",
|
||||
"species"
|
||||
]
|
||||
},
|
||||
"description": "Get all available species"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get Breeds",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/listings/breeds?species_id=your-species-uuid",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"listings",
|
||||
"breeds"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "species_id",
|
||||
"value": "your-species-uuid",
|
||||
"description": "Optional: Filter breeds by species"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get all available breeds (optionally filtered by species)"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Locations",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create Location",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"user_id\": \"aaf295cb-a19e-4179-a2df-31c0c64ea9f4\",\n \"lat\": 18.15,\n \"lng\": 74.5833,\n \"country\": \"India\",\n \"state\": \"Maharashtra\",\n \"district\": \"Pune\",\n \"city_village\": \"Baramati\",\n \"pincode\": \"413102\",\n \"location_type\": \"farm\",\n \"is_saved_address\": true,\n \"source_type\": \"manual\",\n \"source_confidence\": \"high\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/locations",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"locations"
|
||||
]
|
||||
},
|
||||
"description": "Create a new location. user_id is optional for captured locations but required for saved addresses."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get User Locations",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/locations/user/:userId",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"locations",
|
||||
"user",
|
||||
":userId"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "userId",
|
||||
"value": "aaf295cb-a19e-4179-a2df-31c0c64ea9f4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get all locations for a specific user"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get Location by ID",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/locations/:locationId",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"locations",
|
||||
":locationId"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "locationId",
|
||||
"value": "your-location-uuid-here"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get a single location by ID"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Update Location",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"city_village\": \"Updated City Name\",\n \"pincode\": \"413103\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/locations/:locationId",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"locations",
|
||||
":locationId"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "locationId",
|
||||
"value": "your-location-uuid-here"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Update location information"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Chat",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create/Get Conversation",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"buyer_id\": \"buyer-uuid-here\",\n \"seller_id\": \"seller-uuid-here\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/chat/conversations",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"chat",
|
||||
"conversations"
|
||||
]
|
||||
},
|
||||
"description": "Create a new conversation or get existing one between buyer and seller"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get User Conversations",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/chat/conversations/user/:userId",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"chat",
|
||||
"conversations",
|
||||
"user",
|
||||
":userId"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "userId",
|
||||
"value": "aaf295cb-a19e-4179-a2df-31c0c64ea9f4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get all conversations for a specific user"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Get Messages",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/chat/conversations/:conversationId/messages?limit=50&offset=0",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"chat",
|
||||
"conversations",
|
||||
":conversationId",
|
||||
"messages"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "limit",
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"key": "offset",
|
||||
"value": "0"
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "conversationId",
|
||||
"value": "your-conversation-uuid-here"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Get messages for a conversation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Send Message",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"conversation_id\": \"conversation-uuid-here\",\n \"sender_id\": \"sender-uuid-here\",\n \"receiver_id\": \"receiver-uuid-here\",\n \"content\": \"Hello, I'm interested in your listing.\",\n \"message_type\": \"text\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:3200/chat/messages",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "3200",
|
||||
"path": [
|
||||
"chat",
|
||||
"messages"
|
||||
]
|
||||
},
|
||||
"description": "Send a message in a conversation"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "baseUrl",
|
||||
"value": "http://localhost:3200",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
# Postman Testing Guide for BuySellService API
|
||||
|
||||
## 📥 How to Import the Collection
|
||||
|
||||
1. **Open Postman**
|
||||
2. Click **Import** button (top left)
|
||||
3. Select **File** tab
|
||||
4. Choose `BuySellService_API.postman_collection.json`
|
||||
5. Click **Import**
|
||||
|
||||
The collection will appear in your Postman sidebar with all endpoints organized by category.
|
||||
|
||||
## 🚀 Quick Start Testing
|
||||
|
||||
### Step 1: Create a User (Required First Step)
|
||||
|
||||
Before creating listings or locations, you need to create a user:
|
||||
|
||||
1. Go to **Users** → **Create User**
|
||||
2. The request body is pre-filled with the UUID from your error: `aaf295cb-a19e-4179-a2df-31c0c64ea9f4`
|
||||
3. Click **Send**
|
||||
4. You should get a 201 response with the created user
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"id": "aaf295cb-a19e-4179-a2df-31c0c64ea9f4",
|
||||
"name": "Test User",
|
||||
"phone_number": "+919876543210",
|
||||
"avatar_url": null,
|
||||
"language": "en",
|
||||
"timezone": "Asia/Kolkata",
|
||||
"country_code": "+91"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Get Species and Breeds
|
||||
|
||||
Before creating a listing, you need to know the UUIDs of species and breeds:
|
||||
|
||||
1. Go to **Listings** → **Get Species**
|
||||
2. Click **Send** - This will show you all available species with their UUIDs
|
||||
3. Copy a species UUID
|
||||
4. Go to **Listings** → **Get Breeds**
|
||||
5. Add the species UUID as a query parameter: `?species_id=your-species-uuid`
|
||||
6. Click **Send** - This will show breeds for that species
|
||||
7. Copy a breed UUID
|
||||
|
||||
### Step 3: Create a Location (Optional)
|
||||
|
||||
1. Go to **Locations** → **Create Location**
|
||||
2. Update the `user_id` in the request body to your user UUID
|
||||
3. Modify other fields as needed
|
||||
4. Click **Send**
|
||||
|
||||
### Step 4: Create a Listing
|
||||
|
||||
1. Go to **Listings** → **Create Listing**
|
||||
2. Update the request body:
|
||||
- `seller_id`: Use your user UUID
|
||||
- `species_id`: Use the species UUID from Step 2
|
||||
- `breed_id`: Use the breed UUID from Step 2
|
||||
3. Click **Send**
|
||||
|
||||
## 📋 Endpoint Categories
|
||||
|
||||
### 👤 Users
|
||||
- **Create User** - Create a new user (with optional UUID)
|
||||
- **Get All Users** - List all users
|
||||
- **Get User by ID** - Get specific user details
|
||||
- **Update User** - Update user information
|
||||
|
||||
### 📋 Listings
|
||||
- **Get All Listings** - List all active listings
|
||||
- **Get Listing by ID** - Get specific listing with full details
|
||||
- **Create Listing** - Create a new listing with animal details
|
||||
- **Update Listing** - Update listing information
|
||||
- **Search Listings** - Search by text query
|
||||
- **Near Me Search** - Find listings near coordinates
|
||||
- **Get Species** - Get all available species
|
||||
- **Get Breeds** - Get all breeds (optionally filtered by species)
|
||||
|
||||
### 📍 Locations
|
||||
- **Create Location** - Create a location (user_id optional for captured locations)
|
||||
- **Get User Locations** - Get all locations for a user
|
||||
- **Get Location by ID** - Get specific location
|
||||
- **Update Location** - Update location information
|
||||
|
||||
### 💬 Chat
|
||||
- **Create/Get Conversation** - Create or get existing conversation
|
||||
- **Get User Conversations** - Get all conversations for a user
|
||||
- **Get Messages** - Get messages in a conversation
|
||||
- **Send Message** - Send a message
|
||||
|
||||
## 🔧 Important Notes
|
||||
|
||||
### UUIDs Required
|
||||
- All IDs in this API are **UUIDs**, not integers
|
||||
- Make sure to use valid UUID format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
|
||||
### User ID Requirements
|
||||
- **For Listings**: `seller_id` must exist in users table
|
||||
- **For Locations**:
|
||||
- `user_id` is **optional** if `is_saved_address = false` (captured location)
|
||||
- `user_id` is **required** if `is_saved_address = true` (saved address)
|
||||
|
||||
### Testing Order
|
||||
1. ✅ Create User first
|
||||
2. ✅ Get Species and Breeds
|
||||
3. ✅ Create Location (optional)
|
||||
4. ✅ Create Listing
|
||||
|
||||
## 🐛 Common Errors
|
||||
|
||||
### "User does not exist"
|
||||
- **Solution**: Create the user first using the **Create User** endpoint
|
||||
|
||||
### "Invalid input syntax for type uuid"
|
||||
- **Solution**: Make sure you're using UUIDs, not integers. Check species_id and breed_id are UUIDs.
|
||||
|
||||
### "Foreign key constraint violation"
|
||||
- **Solution**: Ensure all referenced IDs (user_id, species_id, breed_id) exist in their respective tables
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
1. **Use Environment Variables**: Create a Postman environment with:
|
||||
- `baseUrl`: `http://localhost:3200`
|
||||
- `userId`: Your user UUID
|
||||
- `speciesId`: A species UUID
|
||||
- `breedId`: A breed UUID
|
||||
|
||||
2. **Save Responses**: After creating resources, save the returned UUIDs for use in other requests
|
||||
|
||||
3. **Test in Order**: Follow the testing order above to avoid foreign key errors
|
||||
|
||||
4. **Check Server**: Make sure your server is running on port 3200 before testing
|
||||
|
||||
## 🔗 Base URL
|
||||
|
||||
All endpoints use: `http://localhost:3200`
|
||||
|
||||
If your server runs on a different port, update the base URL in the collection variables.
|
||||
|
||||
|
|
@ -5,7 +5,9 @@
|
|||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server.js",
|
||||
"dev": "node server.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -20,6 +20,20 @@ const createNewLocation = async (client, userId, locationData) => {
|
|||
pincode,
|
||||
} = locationData;
|
||||
|
||||
// Validate user exists if this is a saved address
|
||||
let finalUserId = userId;
|
||||
if (is_saved_address && userId) {
|
||||
const userCheck = await client.query("SELECT id FROM users WHERE id = $1 AND deleted = FALSE", [userId]);
|
||||
if (userCheck.rows.length === 0) {
|
||||
throw new Error(`User with id ${userId} does not exist. Cannot create saved address for non-existent user.`);
|
||||
}
|
||||
} else if (!is_saved_address) {
|
||||
// For captured locations (not saved addresses), user_id can be NULL
|
||||
finalUserId = null;
|
||||
} else if (is_saved_address && !userId) {
|
||||
throw new Error("user_id is required when is_saved_address is true");
|
||||
}
|
||||
|
||||
// 1. Insert into locations (Merged Table)
|
||||
const insertLocationQuery = `
|
||||
INSERT INTO locations (
|
||||
|
|
@ -31,7 +45,7 @@ const createNewLocation = async (client, userId, locationData) => {
|
|||
RETURNING id
|
||||
`;
|
||||
const locationValues = [
|
||||
userId,
|
||||
finalUserId,
|
||||
lat,
|
||||
lng,
|
||||
source_type || "manual",
|
||||
|
|
@ -266,6 +280,18 @@ router.post("/", async (req, res) => {
|
|||
media // Array of { media_url, media_type, is_primary, sort_order }
|
||||
} = req.body;
|
||||
|
||||
// Validate seller exists
|
||||
if (seller_id) {
|
||||
const sellerCheck = await client.query("SELECT id FROM users WHERE id = $1 AND deleted = FALSE", [seller_id]);
|
||||
if (sellerCheck.rows.length === 0) {
|
||||
await client.query("ROLLBACK");
|
||||
return res.status(400).json({ error: `Seller with id ${seller_id} does not exist. Please create the user first.` });
|
||||
}
|
||||
} else {
|
||||
await client.query("ROLLBACK");
|
||||
return res.status(400).json({ error: "seller_id is required" });
|
||||
}
|
||||
|
||||
let final_location_id = animal?.location_id;
|
||||
|
||||
// 1. Create Location (if needed)
|
||||
|
|
@ -531,11 +557,32 @@ router.delete("/:id", async (req, res) => {
|
|||
});
|
||||
|
||||
|
||||
// Get the list of species
|
||||
router.get("/species", async (req, res) => {
|
||||
try {
|
||||
const queryText = "SELECT * FROM species WHERE deleted = FALSE ORDER BY name";
|
||||
const result = await pool.query(queryText);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error("Error fetching species:", err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// Get the list of breeds
|
||||
router.get("/breeds", async (req, res) => {
|
||||
try {
|
||||
const queryText = "SELECT * FROM breeds";
|
||||
const result = await pool.query(queryText);
|
||||
const { species_id } = req.query;
|
||||
let queryText = "SELECT * FROM breeds WHERE deleted = FALSE";
|
||||
const queryParams = [];
|
||||
|
||||
if (species_id) {
|
||||
queryText += " AND species_id = $1";
|
||||
queryParams.push(species_id);
|
||||
}
|
||||
|
||||
queryText += " ORDER BY name";
|
||||
const result = await pool.query(queryText, queryParams);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error("Error fetching breeds:", err);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,26 @@ router.post("/", async (req, res) => {
|
|||
pincode,
|
||||
} = req.body;
|
||||
|
||||
// Validate user exists if this is a saved address
|
||||
let finalUserId = user_id;
|
||||
if (is_saved_address && user_id) {
|
||||
const userCheck = await client.query("SELECT id FROM users WHERE id = $1 AND deleted = FALSE", [user_id]);
|
||||
if (userCheck.rows.length === 0) {
|
||||
await client.query("ROLLBACK");
|
||||
return res.status(400).json({
|
||||
error: `User with id ${user_id} does not exist. Cannot create saved address for non-existent user.`
|
||||
});
|
||||
}
|
||||
} else if (!is_saved_address) {
|
||||
// For captured locations (not saved addresses), user_id can be NULL
|
||||
finalUserId = null;
|
||||
} else if (is_saved_address && !user_id) {
|
||||
await client.query("ROLLBACK");
|
||||
return res.status(400).json({
|
||||
error: "user_id is required when is_saved_address is true"
|
||||
});
|
||||
}
|
||||
|
||||
// 1. Insert into locations
|
||||
const insertLocationQuery = `
|
||||
INSERT INTO locations (
|
||||
|
|
@ -35,7 +55,7 @@ router.post("/", async (req, res) => {
|
|||
RETURNING *
|
||||
`;
|
||||
const locationValues = [
|
||||
user_id,
|
||||
finalUserId,
|
||||
lat,
|
||||
lng,
|
||||
source_type || "manual",
|
||||
|
|
@ -59,7 +79,15 @@ router.post("/", async (req, res) => {
|
|||
} catch (err) {
|
||||
await client.query("ROLLBACK");
|
||||
console.error("Error creating location:", err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
|
||||
// Provide more specific error messages
|
||||
if (err.code === '23503') { // Foreign key violation
|
||||
return res.status(400).json({
|
||||
error: `Foreign key constraint violation: ${err.detail || 'The provided user_id does not exist in the users table.'}`
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({ error: err.message || "Internal server error" });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
import express from "express";
|
||||
import pool from "../db/pool.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 1. CREATE User
|
||||
router.post("/", async (req, res) => {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
|
||||
const {
|
||||
id, // Optional: if provided, use this UUID; otherwise generate one
|
||||
name,
|
||||
phone_number,
|
||||
avatar_url,
|
||||
language,
|
||||
timezone,
|
||||
country_code = "+91",
|
||||
} = req.body;
|
||||
|
||||
// If id is provided, use it; otherwise let the database generate one
|
||||
const insertUserQuery = id
|
||||
? `
|
||||
INSERT INTO users (id, name, phone_number, avatar_url, language, timezone, country_code)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *
|
||||
`
|
||||
: `
|
||||
INSERT INTO users (name, phone_number, avatar_url, language, timezone, country_code)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const userValues = id
|
||||
? [id, name, phone_number, avatar_url, language, timezone, country_code]
|
||||
: [name, phone_number, avatar_url, language, timezone, country_code];
|
||||
|
||||
const userResult = await client.query(insertUserQuery, userValues);
|
||||
|
||||
await client.query("COMMIT");
|
||||
|
||||
res.status(201).json(userResult.rows[0]);
|
||||
} catch (err) {
|
||||
await client.query("ROLLBACK");
|
||||
console.error("Error creating user:", err);
|
||||
|
||||
if (err.code === "23505") {
|
||||
// Unique constraint violation
|
||||
return res.status(400).json({
|
||||
error: err.detail || "A user with this phone number or ID already exists",
|
||||
});
|
||||
}
|
||||
|
||||
if (err.code === "23503") {
|
||||
// Foreign key violation
|
||||
return res.status(400).json({
|
||||
error: err.detail || "Foreign key constraint violation",
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({ error: err.message || "Internal server error" });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
// 2. GET All Users
|
||||
router.get("/", async (req, res) => {
|
||||
try {
|
||||
const { limit = 100, offset = 0 } = req.query;
|
||||
const queryText = `
|
||||
SELECT id, name, phone_number, avatar_url, language, timezone, country_code,
|
||||
is_active, created_at, updated_at
|
||||
FROM users
|
||||
WHERE deleted = FALSE
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1 OFFSET $2
|
||||
`;
|
||||
const result = await pool.query(queryText, [limit, offset]);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error("Error fetching users:", err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// 3. GET Single User
|
||||
router.get("/:id", async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const queryText = `
|
||||
SELECT id, name, phone_number, avatar_url, language, timezone, country_code,
|
||||
is_active, created_at, updated_at
|
||||
FROM users
|
||||
WHERE id = $1 AND deleted = FALSE
|
||||
`;
|
||||
const result = await pool.query(queryText, [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error("Error fetching user:", err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// 4. UPDATE User
|
||||
router.put("/:id", async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, phone_number, avatar_url, language, timezone, country_code, is_active } = req.body;
|
||||
|
||||
const updateQuery = `
|
||||
UPDATE users
|
||||
SET name = COALESCE($1, name),
|
||||
phone_number = COALESCE($2, phone_number),
|
||||
avatar_url = COALESCE($3, avatar_url),
|
||||
language = COALESCE($4, language),
|
||||
timezone = COALESCE($5, timezone),
|
||||
country_code = COALESCE($6, country_code),
|
||||
is_active = COALESCE($7, is_active)
|
||||
WHERE id = $8 AND deleted = FALSE
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const values = [name, phone_number, avatar_url, language, timezone, country_code, is_active, id];
|
||||
|
||||
const result = await pool.query(updateQuery, values);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error("Error updating user:", err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// 5. DELETE User (Soft Delete)
|
||||
router.delete("/:id", async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const queryText = `
|
||||
UPDATE users
|
||||
SET deleted = TRUE
|
||||
WHERE id = $1
|
||||
RETURNING id
|
||||
`;
|
||||
const result = await pool.query(queryText, [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
res.json({ message: "User deleted successfully" });
|
||||
} catch (err) {
|
||||
console.error("Error deleting user:", err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
23
server.js
23
server.js
|
|
@ -1,24 +1,31 @@
|
|||
import express from "express";
|
||||
import cors from "cors";
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import http from "http";
|
||||
import listingRoutes from "./routes/listingRoutes.js";
|
||||
import locationRoutes from "./routes/locationRoutes.js";
|
||||
import chatRoutes from "./routes/chatRoutes.js";
|
||||
import userRoutes from "./routes/userRoutes.js";
|
||||
import { initSocket } from "./socket.js";
|
||||
import { startExpirationJob } from "./jobs/expirationJob.js";
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Serve static files from public directory
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
app.use(express.static(join(__dirname, 'public')));
|
||||
|
||||
const PORT = process.env.PORT || 3200;
|
||||
|
||||
// Add routes here
|
||||
import listingRoutes from "./routes/listingRoutes.js";
|
||||
import locationRoutes from "./routes/locationRoutes.js";
|
||||
import chatRoutes from "./routes/chatRoutes.js";
|
||||
|
||||
|
||||
app.use("/listings", listingRoutes);
|
||||
app.use("/locations", locationRoutes);
|
||||
app.use("/chat", chatRoutes);
|
||||
import http from "http";
|
||||
import { initSocket } from "./socket.js";
|
||||
import { startExpirationJob } from "./jobs/expirationJob.js";
|
||||
app.use("/users", userRoutes);
|
||||
|
||||
const server = http.createServer(app);
|
||||
initSocket(server);
|
||||
|
|
|
|||
Loading…
Reference in New Issue