inital commit

This commit is contained in:
2026-04-10 20:51:43 -05:00
parent cd1f2eae29
commit 562a8525d0
85 changed files with 4820 additions and 2 deletions

451
api.md Normal file
View File

@@ -0,0 +1,451 @@
# WaterTrek API (Frontend Handoff)
Base URL: `/api/v1/`
Auth: JWT Bearer token in `Authorization: Bearer <access_token>`
## Auth + Accounts
### Register Vendor
- `POST /api/v1/accounts/register/vendor/`
- Public endpoint
Request:
```json
{
"email": "vendor@example.com",
"password": "StrongPassword123",
"first_name": "Nora",
"last_name": "Smith",
"phone_number": "+1-555-1234",
"business_name": "Nora Rentals",
"description": "Jet ski and boat rentals",
"contact_phone": "+1-555-1234",
"contact_email": "hello@norarentals.com",
"address_line1": "1 Harbor Way",
"city": "Miami",
"state": "FL",
"postal_code": "33101",
"country": "USA"
}
```
Response `201`:
```json
{
"id": 12,
"email": "vendor@example.com",
"first_name": "Nora",
"last_name": "Smith",
"phone_number": "+1-555-1234",
"is_vendor": true,
"is_customer": false,
"vendor_profile": {
"business_name": "Nora Rentals",
"slug": "nora-rentals",
"description": "Jet ski and boat rentals",
"contact_phone": "+1-555-1234",
"contact_email": "hello@norarentals.com",
"address_line1": "1 Harbor Way",
"address_line2": "",
"city": "Miami",
"state": "FL",
"postal_code": "33101",
"country": "USA",
"created_at": "2026-04-08T17:00:00Z",
"updated_at": "2026-04-08T17:00:00Z"
}
}
```
### Login (JWT)
- `POST /api/v1/accounts/token/`
Request:
```json
{
"email": "vendor@example.com",
"password": "StrongPassword123"
}
```
Response `200`:
```json
{
"refresh": "<refresh_token>",
"access": "<access_token>"
}
```
### Refresh JWT
- `POST /api/v1/accounts/token/refresh/`
Request:
```json
{
"refresh": "<refresh_token>"
}
```
### Current User
- `GET /api/v1/accounts/me/`
- Requires JWT
### Vendor Profile Setup / Update
- `GET /api/v1/accounts/vendor-profile/me/`
- `PATCH /api/v1/accounts/vendor-profile/me/`
- Requires vendor JWT
Patch example:
```json
{
"business_name": "Nora Rentals LLC",
"description": "Premium water equipment rentals",
"city": "Fort Lauderdale"
}
```
## Equipment APIs
### Public Equipment List
- `GET /api/v1/equipment/items/`
- Public endpoint
- Query params:
- `category` (category id or slug)
- `location` (icontains match)
- `vendor_slug`
- `min_price`
- `max_price`
- `available_from` (ISO datetime)
- `available_to` (ISO datetime)
Example:
`GET /api/v1/equipment/items/?location=miami&min_price=100&max_price=300`
### Public Equipment Detail
- `GET /api/v1/equipment/items/{public_id}/`
- Public endpoint
- **Marketing / click-through:** Each successful detail response appends:
- `marketing_click_id` (integer): pass this on `POST /api/v1/booking/bookings/request/` as `marketing_click_id` so bookings can be attributed to the visit (organic or campaign).
- `click_traffic_type`: `"organic"` or `"marketing"` (derived from query params below).
- Optional **query params** (standard UTM + common ad IDs) are read and stored on the click row for vendor reporting:
- `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`
- `gclid`, `fbclid`
- If **any** of those params is non-empty, `click_traffic_type` is `marketing`; otherwise `organic`.
Example:
`GET /api/v1/equipment/items/jetski-001/?utm_source=google&utm_medium=cpc&utm_campaign=spring_sale`
### Vendor Inventory CRUD
- `GET /api/v1/equipment/vendor/items/`
- `POST /api/v1/equipment/vendor/items/`
- `GET /api/v1/equipment/vendor/items/{id}/`
- `PUT /api/v1/equipment/vendor/items/{id}/`
- `PATCH /api/v1/equipment/vendor/items/{id}/`
- `DELETE /api/v1/equipment/vendor/items/{id}/`
- Requires vendor JWT
Create payload:
```json
{
"public_id": "jetski-001",
"title": "Yamaha Jet Ski",
"description": "Fast and stable for 2 riders",
"details": {
"engine_cc": 998,
"seats": 2
},
"location": "Miami Beach",
"price_per_day": "180.00",
"is_active": true,
"category_id": 1
}
```
### Vendor Storefront by Slug
- `GET /api/v1/equipment/storefront/{slug}/`
- Public endpoint
- Returns vendor profile + active inventory list
## Adventrues APIs
### Public Adventure List
- `GET /api/v1/adventrues/offerings/`
- Public endpoint
- Query params:
- `category` (category id or slug)
- `location` (matches `meeting_point`)
- `vendor_slug`
- `min_price`
- `max_price`
- `available_from` (ISO datetime)
- `available_to` (ISO datetime)
### Public Adventure Detail
- `GET /api/v1/adventrues/offerings/{public_id}/`
- Public endpoint
- Same **marketing / click-through** behavior as equipment detail: response includes `marketing_click_id` and `click_traffic_type`; UTM / `gclid` / `fbclid` query params are captured when present.
### Vendor Adventure CRUD
- `GET /api/v1/adventrues/vendor/offerings/`
- `POST /api/v1/adventrues/vendor/offerings/`
- `GET /api/v1/adventrues/vendor/offerings/{id}/`
- `PUT /api/v1/adventrues/vendor/offerings/{id}/`
- `PATCH /api/v1/adventrues/vendor/offerings/{id}/`
- `DELETE /api/v1/adventrues/vendor/offerings/{id}/`
- Requires vendor JWT
Create payload:
```json
{
"public_id": "sunset-kayak-001",
"title": "Sunset Kayak Tour",
"description": "Guided paddle with sunset views",
"meeting_point": "Biscayne Bay Marina",
"duration_minutes": 120,
"capacity": 10,
"price_per_person": "75.00",
"is_active": true,
"category_id": 1
}
```
### Vendor Adventure Storefront by Slug
- `GET /api/v1/adventrues/storefront/{slug}/`
- Public endpoint
- Returns vendor profile + active adventure offerings
## Booking APIs
### Availability Check
- `GET /api/v1/booking/availability/`
- Public endpoint
- Required query params:
- exactly one of `equipment_item_id` or `adventure_offering_id`
- `starts_at` (ISO datetime)
- `ends_at` (ISO datetime)
- Rule notes:
- If availability slots exist for the target, booking requires at least one `is_available=true` slot fully covering the requested range.
- Any overlapping `is_available=false` slot blocks the request.
- If no slots exist yet, target is treated as bookable by default (subject to overlap checks).
Example:
`GET /api/v1/booking/availability/?equipment_item_id=5&starts_at=2026-04-12T10:00:00Z&ends_at=2026-04-13T10:00:00Z`
Response:
```json
{
"equipment_item_id": 5,
"adventure_offering_id": null,
"starts_at": "2026-04-12T10:00:00Z",
"ends_at": "2026-04-13T10:00:00Z",
"is_available": true,
"conflicts": 0
}
```
### Create Booking Request (Customer)
- `POST /api/v1/booking/bookings/request/`
- Requires customer JWT
Request:
```json
{
"equipment_item_id": 5,
"starts_at": "2026-04-12T10:00:00Z",
"ends_at": "2026-04-14T10:00:00Z",
"customer_notes": "Need two life jackets",
"marketing_click_id": 9042
}
```
- Optional `marketing_click_id`: integer from the latest `GET` detail response (`marketing_click_id`) for **the same** equipment item or adventure offering. Must be within **90 days** of the click. Omit for unattributed bookings (still allowed).
Response includes booking with status `requested` and `listing_click` (nullable FK id) when attribution was applied.
Adventure booking request example:
```json
{
"adventure_offering_id": 9,
"starts_at": "2026-05-10T15:00:00Z",
"ends_at": "2026-05-10T17:00:00Z",
"participants_count": 3,
"customer_notes": "First-time kayakers"
}
```
### List My Bookings
- `GET /api/v1/booking/bookings/`
- Requires JWT
- If caller is vendor: returns bookings for their vendor profile
- Else: returns bookings where caller is customer
### Booking Detail
- `GET /api/v1/booking/bookings/{id}/`
- Requires JWT with ownership (customer or vendor on that booking)
### Vendor Approve Booking
- `POST /api/v1/booking/bookings/{id}/approve/`
- Requires vendor JWT (own booking only)
Payload (optional):
```json
{
"vendor_notes": "Approved, pickup after 9AM",
"note": "Ready for pickup window"
}
```
### Vendor Decline Booking
- `POST /api/v1/booking/bookings/{id}/decline/`
- Requires vendor JWT (own booking only)
### Customer Cancel Booking
- `POST /api/v1/booking/bookings/{id}/cancel/`
- Requires customer JWT (own booking only)
Payload (optional):
```json
{
"note": "Trip postponed to next month"
}
```
## Payment APIs (Mock Stripe)
### Create Payment Intent (Mock)
- `POST /api/v1/payment/intents/`
- Requires customer JWT (must own booking)
- Booking must be in `approved` state
Request:
```json
{
"booking_id": 42,
"currency": "usd"
}
```
Response `201`:
```json
{
"payment": {
"id": 10,
"booking_id": 42,
"stripe_payment_intent_id": "pi_mock_123abc...",
"stripe_charge_id": "",
"amount": "360.00",
"currency": "usd",
"status": "requires_payment",
"created_at": "2026-04-08T18:00:00Z",
"updated_at": "2026-04-08T18:00:00Z"
},
"client_secret": "pi_secret_mock_123abc...",
"mocked": true
}
```
### Payment Status
- `GET /api/v1/payment/{payment_id}/status/`
- Requires JWT (booking customer or booking vendor owner)
### Stripe Webhook (Mocked)
- `POST /api/v1/payment/webhooks/stripe/`
- Public endpoint for local/mock integration
- Idempotent by `stripe_event_id`
Request:
```json
{
"stripe_event_id": "evt_mock_001",
"event_type": "payment_intent.succeeded",
"stripe_payment_intent_id": "pi_mock_123abc...",
"payload": {
"source": "frontend-test"
}
}
```
Supported `event_type` values:
- `payment_intent.processing`
- `payment_intent.succeeded`
- `payment_intent.payment_failed`
- `charge.refunded`
Behavior:
- Updates `PaymentRecord.status`
- On `payment_intent.succeeded`, booking auto-transitions `approved -> confirmed`
- Stores webhook in `WebhookEvent` and enforces idempotency
## Marketing APIs (UTM, click-through, vendor analytics)
Base path: `/api/v1/marketing/`
### Track listing click (explicit)
- `POST /api/v1/marketing/track/click/`
- Public (optional JWT if the user is logged in; stored on the click when present)
- Use when the SPA opens a listing **without** calling the public detail endpoint (e.g. client-side route from cache). Otherwise prefer relying on `GET` equipment/adventure detail, which records the click automatically.
Request:
```json
{
"listing_type": "equipment",
"public_id": "jetski-001",
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "april_promo",
"utm_term": "",
"utm_content": "hero",
"gclid": "",
"fbclid": "",
"referrer": "https://mail.example.com/"
}
```
- `listing_type`: `"equipment"` or `"adventure"` (must match `public_id`).
Response `201`:
```json
{
"marketing_click_id": 9042,
"traffic_type": "marketing"
}
```
### Vendor: list listing clicks
- `GET /api/v1/marketing/vendor/clicks/`
- Requires vendor JWT
- **Required query params:** `from`, `to` — ISO-8601 datetimes (UTC), half-open range `[from, to)`.
- Optional filters: `traffic_type` (`organic` | `marketing`), `listing_type` (`equipment` | `adventure`), `utm_campaign` (exact match).
### Vendor: marketing summary (conversions)
- `GET /api/v1/marketing/vendor/summary/`
- Requires vendor JWT
- **Required query params:** `from`, `to` — same as above.
Returns (shape):
- `clicks.organic` / `clicks.marketing` / `clicks.total` — listing detail or track-click events in range.
- `bookings_attributed.organic` — bookings in range whose `listing_click.traffic_type` is `organic`.
- `bookings_attributed.marketing` — bookings in range whose `listing_click.traffic_type` is `marketing`.
- `bookings_attributed.unattributed` — bookings in range with no `listing_click`.
- `conversion_rate_click_to_booking.organic``organic` bookings ÷ `organic` clicks (omitted / `null` if clicks = 0).
- `conversion_rate_click_to_booking.marketing` — same for marketing.
- `campaigns` — up to 50 rows with non-empty `utm_campaign` on clicks: `utm_source`, `utm_medium`, `utm_campaign`, `clicks`, `bookings` (bookings that referenced a click with that triple), `conversion_rate`.
**Interpreting organic vs marketing:** Traffic is classified **per click** from UTM / `gclid` / `fbclid`. To compare conversion rates fairly, the client should send `marketing_click_id` on checkout for both organic and paid visits when possible (so bookings are not left in `unattributed`).
## Notes for React Integration
- All datetimes are ISO-8601 and UTC.
- Booking overlap protection is implemented for `requested`, `approved`, and `confirmed` windows.
- `requested` works as a soft hold and blocks overlapping requests until resolved.
- `total_price` is computed server-side:
- equipment: rounded-up day count x `price_per_day`
- adventure: `participants_count` x `price_per_person`
- Payment APIs are currently mocked (Stripe-style IDs and webhook event flow) to unblock frontend integration before live Stripe keys are wired.
- Use `public_id` for equipment-facing detail pages and numeric `id` for vendor CRUD.
- Booking domain services are implemented server-side for rules and transitions:
- `is_bookable(...)`
- `quote_booking(...)`
- `create_booking_request(...)`
- `transition_booking_status(...)`
- Persist `marketing_click_id` from listing detail (or `POST .../marketing/track/click/`) and send it with `POST .../booking/bookings/request/` so vendors can tie bookings to campaigns and compare organic vs marketing conversion in `GET .../marketing/vendor/summary/`.