api-v1/README.md

790 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Livestock Marketplace Listing Service Spec
This document covers:
1. All DB tables required for animal listings
2. API endpoints purpose, request, and response formats
---
## 1. Database Tables
> Convention: Every table has `created_at` and `updated_at` (`TIMESTAMP`).
### 1.1 `users` (reference)
Minimal definition (assuming you already have auth elsewhere).
| Column | Type | Constraints | Description |
| ---------- | --------- | ---------------- | ---------------------- |
| id | UUID | PK | User ID (seller/buyer) |
| name | VARCHAR | NOT NULL | Display name |
| phone | VARCHAR | UNIQUE, NOT NULL | Phone number |
| created_at | TIMESTAMP | NOT NULL | Row created time |
| updated_at | TIMESTAMP | NOT NULL | Row last updated time |
**Relationships**
- 1 `user` → N `listings`
- 1 `user` → N `locations` (saved addresses only)
---
### 1.2 `species`
| Column | Type | Constraints | Description |
| ---------- | --------- | ----------- | -------------------------- |
| id | INT | PK | Species ID |
| name | VARCHAR | UNIQUE | e.g. Cattle, Buffalo, Goat |
| created_at | TIMESTAMP | NOT NULL | |
| updated_at | TIMESTAMP | NOT NULL | |
**Relationships**
- 1 `species` → N `breeds`
- 1 `species` → N `animals`
---
### 1.3 `breeds`
| Column | Type | Constraints | Description |
| ----------- | --------- | --------------- | ---------------- |
| id | INT | PK | Breed ID |
| species_id | INT | FK → species.id | Parent species |
| name | VARCHAR | NOT NULL | e.g. Gir, Murrah |
| description | TEXT | NULL | Optional notes |
| created_at | TIMESTAMP | NOT NULL | |
| updated_at | TIMESTAMP | NOT NULL | |
**Relationships**
- 1 `species` → N `breeds`
- 1 `breed` → N `animals`
---
### 1.4 `locations`
Used both for:
- **Captured locations** (no user, not saved; `user_id = NULL`, `is_saved_address = false`)
- **Saved addresses** for a user (e.g. farm, home; `user_id` set, `is_saved_address = true`)
| Column | Type | Constraints | Description |
| ----------------- | --------- | --------------------------- | --------------------------------------------------------------------- |
| id | UUID | PK | Location ID |
| user_id | UUID | FK → users.id, NULLABLE | Owner if this is a saved address; NULL if just captured for a listing |
| is_saved_address | BOOLEAN | NOT NULL, default false | True if user chose to save it as an address |
| location_type | VARCHAR | NULL (enum suggestion) | e.g. `farm`, `home`, `office`, `other` |
| country | VARCHAR | NULL | Country |
| state | VARCHAR | NULL | State |
| district | VARCHAR | NULL | District |
| city_village | VARCHAR | NULL | City / village |
| pincode | VARCHAR | NULL | Postal code |
| lat | DECIMAL | NULL | Latitude |
| lng | DECIMAL | NULL | Longitude |
| source_type | VARCHAR | NOT NULL, default `unknown` | `device_gps`, `manual`, `unknown` |
| source_confidence | VARCHAR | NOT NULL, default `medium` | `high`, `medium`, `low` |
| created_at | TIMESTAMP | NOT NULL | |
| updated_at | TIMESTAMP | NOT NULL | |
**Interpretation**
- **Captured only**: `user_id = NULL`, `is_saved_address = false`
- **Captured + saved as users farm/home**: set `user_id`, `is_saved_address = true`, `location_type = 'farm'` (or similar).
**Relationships**
- 1 `user` → N `locations` (saved addresses)
- 1 `location` → N `animals` (many animals can share same farm address)
---
### 1.5 `animals`
One animal per listing (enforced via unique constraint at `listings.animal_id`).
| Column | Type | Constraints | Description |
| -------------------------- | --------- | ------------------------- | ----------------------------------------------- |
| id | UUID | PK | Animal ID |
| species_id | INT | FK → species.id, NOT NULL | Species |
| breed_id | INT | FK → breeds.id, NULL | Breed (optional) |
| sex | VARCHAR | NOT NULL | `M`, `F`, `Neutered` |
| age_months | INT | NULL | Age in months |
| weight_kg | DECIMAL | NULL | Approx weight |
| color_markings | VARCHAR | NULL | Color / markings |
| quantity | INT | NOT NULL, default 1 | Number of animals in this listing |
| purpose | VARCHAR | NOT NULL | `dairy`, `meat`, `breeding`, `pet`, `work`, etc |
| health_status | VARCHAR | NOT NULL | `healthy`, `minor_issues`, `serious_issues` |
| vaccinated | BOOLEAN | NOT NULL, default false | |
| dewormed | BOOLEAN | NOT NULL, default false | |
| previous_pregnancies_count | INT | NULL | For females, number of previous pregnancies |
| pregnancy_status | VARCHAR | NULL | `not_pregnant`, `pregnant`, `recently_calved` |
| milk_yield_litre_per_day | DECIMAL | NULL | Avg daily milk yield |
| ear_tag_no | VARCHAR | NULL | Tag / registration ID |
| description | TEXT | NULL | Detailed description |
| suggested_care | TEXT | NULL | Suggested food & accessories (free-text) |
| location_id | UUID | FK → locations.id, NULL | Location of animal (farm etc.). NULL if unknown |
| created_at | TIMESTAMP | NOT NULL | |
| updated_at | TIMESTAMP | NOT NULL | |
**Relationships**
- 1 `species` → N `animals`
- 1 `breed` → N `animals`
- 1 `location` → N `animals`
- 1 `animal` ↔ 1 `listing` (via `listings.animal_id` UNIQUE)
---
### 1.6 `listings`
One listing per animal (11).
| Column | Type | Constraints | Description |
| ------------------------ | --------- | --------------------------------- | ------------------------------------- |
| id | UUID | PK | Listing ID |
| seller_id | UUID | FK → users.id, NOT NULL | Seller |
| animal_id | UUID | FK → animals.id, UNIQUE, NOT NULL | The animal this listing is for |
| title | VARCHAR | NOT NULL | Listing title |
| price | DECIMAL | NOT NULL | Asking price |
| currency | VARCHAR | NOT NULL, e.g. `INR` | Currency code |
| is_negotiable | BOOLEAN | NOT NULL, default true | Price negotiable |
| listing_type | VARCHAR | NOT NULL | `sale`, `stud_service`, `adoption` |
| status | VARCHAR | NOT NULL, default `active` | `active`, `sold`, `expired`, `hidden` |
| views_count | INT | NOT NULL, default 0 | Total views |
| bookmarks_count | INT | NOT NULL, default 0 | Times bookmarked |
| enquiries_call_count | INT | NOT NULL, default 0 | Phone enquiries |
| enquiries_whatsapp_count | INT | NOT NULL, default 0 | WhatsApp enquiries |
| clicks_count | INT | NOT NULL, default 0 | Other CTA clicks (e.g. “View number”) |
| created_at | TIMESTAMP | NOT NULL | |
| updated_at | TIMESTAMP | NOT NULL | |
**Relationships**
- 1 `user` → N `listings`
- 1 `animal` ↔ 1 `listing`
- 1 `listing` → N `listing_media`
---
### 1.7 `listing_media` (images / videos)
| Column | Type | Constraints | Description |
| ---------- | --------- | ----------------------- | -------------------------------- |
| id | UUID | PK | Media ID |
| listing_id | UUID | FK → listings.id | Parent listing |
| media_url | VARCHAR | NOT NULL | URL to image/video |
| media_type | VARCHAR | NOT NULL | `image`, `video` |
| is_primary | BOOLEAN | NOT NULL, default false | True if main display image/video |
| sort_order | INT | NOT NULL, default 0 | For ordering media in gallery |
| created_at | TIMESTAMP | NOT NULL | |
| updated_at | TIMESTAMP | NOT NULL | |
**Relationships**
- 1 `listing` → N `listing_media`
---
### 1.8 Relationship Summary
- **1N**
- `users``listings`
- `users``locations` (saved addresses)
- `species``breeds`
- `species``animals`
- `breeds``animals`
- `locations``animals`
- `listings``listing_media`
- **11**
- `animals``listings` (enforced via `listings.animal_id` UNIQUE)
- **NM**
- None currently; all many-to-many are avoided in this MVP schema.
---
## 2. API Endpoints
### 2.1 Create Listing (with Animal + optional Location)
#### `POST /listings`
**Purpose**
Create a new listing and its animal. Optionally:
- Use an existing `location_id` or
- Create a new captured/saved location in the same call.
**Request (JSON)**
```json
{
"seller_id": "UUID-of-seller",
"title": "High-yield Gir cow for sale",
"price": 55000,
"currency": "INR",
"is_negotiable": true,
"listing_type": "sale",
"animal": {
"species_id": 1,
"breed_id": 10,
"sex": "F",
"age_months": 36,
"weight_kg": 450,
"color_markings": "Brown with white patches",
"quantity": 1,
"purpose": "dairy",
"health_status": "healthy",
"vaccinated": true,
"dewormed": true,
"previous_pregnancies_count": 1,
"pregnancy_status": "pregnant",
"milk_yield_litre_per_day": 15,
"ear_tag_no": "TAG-12345",
"description": "Calm nature, easy to handle.",
"suggested_care": "Green fodder, mineral mix, clean shed.",
"location_id": "existing-location-uuid",
"new_location": {
"country": "India",
"state": "Maharashtra",
"district": "Pune",
"city_village": "Baramati",
"pincode": "413102",
"lat": 18.15,
"lng": 74.5833,
"source_type": "device_gps",
"source_confidence": "high",
"save_as_address": true,
"location_type": "farm"
}
},
"media": [
{
"media_url": "https://cdn.app.com/listings/abc1.jpg",
"media_type": "image",
"is_primary": true,
"sort_order": 1
}
]
}
```
Notes:
- Client can either:
- Provide `location_id`, **or**
- Provide `new_location` object. If `save_as_address = true`, backend should create a `locations` row with `user_id = seller_id`, `is_saved_address = true`.
- Media is optional in this first call; can also be added later via media APIs.
**Response (201 Created)**
```json
{
"listing": {
"id": "listing-uuid",
"seller_id": "UUID-of-seller",
"animal_id": "animal-uuid",
"title": "High-yield Gir cow for sale",
"price": 55000,
"currency": "INR",
"is_negotiable": true,
"listing_type": "sale",
"status": "active",
"views_count": 0,
"bookmarks_count": 0,
"enquiries_call_count": 0,
"enquiries_whatsapp_count": 0,
"clicks_count": 0,
"created_at": "2025-11-22T10:00:00Z",
"updated_at": "2025-11-22T10:00:00Z",
"animal": {
"id": "animal-uuid",
"species_id": 1,
"breed_id": 10,
"sex": "F",
"age_months": 36,
"weight_kg": 450,
"color_markings": "Brown with white patches",
"quantity": 1,
"purpose": "dairy",
"health_status": "healthy",
"vaccinated": true,
"dewormed": true,
"previous_pregnancies_count": 1,
"pregnancy_status": "pregnant",
"milk_yield_litre_per_day": 15,
"ear_tag_no": "TAG-12345",
"description": "Calm nature, easy to handle.",
"suggested_care": "Green fodder, mineral mix, clean shed.",
"location": {
"id": "location-uuid",
"user_id": "UUID-of-seller",
"is_saved_address": true,
"location_type": "farm",
"country": "India",
"state": "Maharashtra",
"district": "Pune",
"city_village": "Baramati",
"pincode": "413102",
"lat": 18.15,
"lng": 74.5833,
"source_type": "device_gps",
"source_confidence": "high"
}
},
"media": [
{
"id": "media-uuid",
"media_url": "https://cdn.app.com/listings/abc1.jpg",
"media_type": "image",
"is_primary": true,
"sort_order": 1
}
]
}
}
```
---
### 2.2 List / Search Listings
#### `GET /listings`
**Purpose**
List active listings with optional filters (species, location, price, etc.).
**Query parameters (examples)**
- `species_id` (int, optional)
- `breed_id` (int, optional)
- `state` (string, optional)
- `district` (string, optional)
- `min_price`, `max_price` (optional)
- `listing_type` (string, optional)
- `page`, `page_size` (for pagination)
**Request**
```http
GET /listings?species_id=1&state=Maharashtra&page=1&page_size=20
```
**Response (200 OK)**
```json
{
"items": [
{
"id": "listing-uuid",
"title": "High-yield Gir cow for sale",
"price": 55000,
"currency": "INR",
"is_negotiable": true,
"listing_type": "sale",
"status": "active",
"species_id": 1,
"breed_id": 10,
"animal_id": "animal-uuid",
"thumbnail_url": "https://cdn.app.com/listings/abc1.jpg",
"location_summary": {
"state": "Maharashtra",
"district": "Pune",
"city_village": "Baramati"
},
"created_at": "2025-11-22T10:00:00Z"
}
],
"page": 1,
"page_size": 20,
"total": 1
}
```
---
### 2.3 Get Listing Detail
#### `GET /listings/{listing_id}`
**Purpose**
Get full details of a single listing (including animal, location, media).
**Response (200 OK)**
```json
{
"id": "listing-uuid",
"seller_id": "UUID-of-seller",
"animal_id": "animal-uuid",
"title": "High-yield Gir cow for sale",
"price": 55000,
"currency": "INR",
"is_negotiable": true,
"listing_type": "sale",
"status": "active",
"views_count": 120,
"bookmarks_count": 10,
"enquiries_call_count": 5,
"enquiries_whatsapp_count": 8,
"clicks_count": 14,
"created_at": "2025-11-22T10:00:00Z",
"updated_at": "2025-11-22T11:00:00Z",
"animal": {
"...": "full animal object as above"
},
"media": [
{
"id": "media-uuid",
"media_url": "https://cdn.app.com/listings/abc1.jpg",
"media_type": "image",
"is_primary": true,
"sort_order": 1
}
]
}
```
---
### 2.4 Update Listing (and Animal)
#### `PUT /listings/{listing_id}`
**Purpose**
Edit listing fields and animal details (e.g. price, status, description, suggested care).
**Request (JSON)**
Only fields to update need to be sent (PATCH style with PUT semantics).
```json
{
"title": "Gir cow price reduced",
"price": 52000,
"status": "active",
"animal": {
"description": "Price reduced, urgent sale.",
"suggested_care": "Green fodder, clean water, regular deworming."
}
}
```
**Response (200 OK)**
Returns updated listing object (same shape as `GET /listings/{id}`).
---
### 2.5 Create / Capture Location
#### `POST /locations`
**Purpose**
Create a new location. Used for:
- Saved address for a user (farm/home)
- Captured location for an animal/listing (not necessarily saved)
**Request (JSON)**
```json
{
"user_id": "UUID-of-user-or-null",
"is_saved_address": true,
"location_type": "farm",
"country": "India",
"state": "Maharashtra",
"district": "Pune",
"city_village": "Baramati",
"pincode": "413102",
"lat": 18.15,
"lng": 74.5833,
"source_type": "device_gps",
"source_confidence": "high"
}
```
- For **captured-only** (not saved): set `user_id = null`, `is_saved_address = false`.
**Response (201 Created)**
```json
{
"id": "location-uuid",
"user_id": "UUID-of-user-or-null",
"is_saved_address": true,
"location_type": "farm",
"country": "India",
"state": "Maharashtra",
"district": "Pune",
"city_village": "Baramati",
"pincode": "413102",
"lat": 18.15,
"lng": 74.5833,
"source_type": "device_gps",
"source_confidence": "high",
"created_at": "2025-11-22T10:05:00Z",
"updated_at": "2025-11-22T10:05:00Z"
}
```
---
### 2.6 Update Location (PUT for Locations)
#### `PUT /locations/{location_id}`
**Purpose**
Update location details OR convert a captured location into a saved address for a user (e.g. mark as farm).
**Request (JSON)**
```json
{
"user_id": "UUID-of-user",
"is_saved_address": true,
"location_type": "farm",
"city_village": "New Village Name",
"pincode": "413103"
}
```
**Response (200 OK)**
```json
{
"id": "location-uuid",
"user_id": "UUID-of-user",
"is_saved_address": true,
"location_type": "farm",
"country": "India",
"state": "Maharashtra",
"district": "Pune",
"city_village": "New Village Name",
"pincode": "413103",
"lat": 18.15,
"lng": 74.5833,
"source_type": "device_gps",
"source_confidence": "high",
"created_at": "2025-11-22T10:05:00Z",
"updated_at": "2025-11-22T11:00:00Z"
}
```
---
### 2.7 Get Saved Locations for a User
#### `GET /users/{user_id}/locations`
**Purpose**
Fetch all saved addresses for a given user (farm, home, etc.).
**Response (200 OK)**
```json
{
"items": [
{
"id": "location-uuid-1",
"user_id": "UUID-of-user",
"is_saved_address": true,
"location_type": "farm",
"country": "India",
"state": "Maharashtra",
"district": "Pune",
"city_village": "Baramati",
"pincode": "413102",
"lat": 18.15,
"lng": 74.5833,
"source_type": "device_gps",
"source_confidence": "high",
"created_at": "2025-11-22T10:00:00Z",
"updated_at": "2025-11-22T10:00:00Z"
}
]
}
```
---
### 2.8 Add Media to Listing
#### `POST /listings/{listing_id}/media`
**Purpose**
Attach new images/videos to a listing after creation.
**Request (JSON)**
```json
{
"items": [
{
"media_url": "https://cdn.app.com/listings/abc2.jpg",
"media_type": "image",
"is_primary": false,
"sort_order": 2
},
{
"media_url": "https://cdn.app.com/listings/abc3.mp4",
"media_type": "video",
"is_primary": false,
"sort_order": 3
}
]
}
```
**Response (201 Created)**
```json
{
"media": [
{
"id": "media-uuid-2",
"listing_id": "listing-uuid",
"media_url": "https://cdn.app.com/listings/abc2.jpg",
"media_type": "image",
"is_primary": false,
"sort_order": 2
},
{
"id": "media-uuid-3",
"listing_id": "listing-uuid",
"media_url": "https://cdn.app.com/listings/abc3.mp4",
"media_type": "video",
"is_primary": false,
"sort_order": 3
}
]
}
```
---
### 2.9 Update Media (PUT for Images/Media)
#### `PUT /listing-media/{media_id}`
**Purpose**
Update a single media item (e.g. mark as primary, change sort order, fix URL).
**Request (JSON)**
```json
{
"is_primary": true,
"sort_order": 1
}
```
**Response (200 OK)**
```json
{
"id": "media-uuid-2",
"listing_id": "listing-uuid",
"media_url": "https://cdn.app.com/listings/abc2.jpg",
"media_type": "image",
"is_primary": true,
"sort_order": 1,
"created_at": "2025-11-22T10:10:00Z",
"updated_at": "2025-11-22T10:20:00Z"
}
```
---
### 2.10 Create a Custom Requirement
#### `POST /requirements`
**Purpose**
Custom Requirements allow buyers to express needs such as
"Looking for a cow giving more than 10 litres of milk per day."
These relate to an animal_id and are visible to sellers while listing animals.
**Request Body**
```json
{
"buyer_id": 12,
"animal_id": 201,
"title": "Cow giving 10+ litres milk",
"description": "Healthy cow with high milk output",
"min_price": 30000,
"max_price": 60000,
"location": "Pune"
}
```
**Response**
```json
{
"requirement_id": 501,
"message": "Custom requirement created successfully"
}
```
---
### 2.11 Update an Existing Requirement
#### `PUT /requirements/{requirement_id}`
**Request Body**
```json
{
"title": "Cow giving 12+ litres",
"max_price": 65000
}
```
---
### 2.12 Delete a Requirement
#### `DELETE /requirements/{requirement_id}`
---
### 2.13 Get All Requirements for a Buyer
#### `GET /requirements/buyer/{buyer_id}`
**Purpose**
Retrieves all active and past requirements of a buyer.
---
### 2.14 Get Matching Requirements for an Animal (For Sellers)
#### `GET /requirements/matching?animal_id={animal_id}`
**Purpose**
Used when a seller lists an animal so the system can show matching requirements.