# HospitableClient
Elixir client library for Hospitable Public API v2.
## Features
- Personal Access Token (PAT) authentication
- Centralized authentication state management with GenServer
- RESTful API support (GET, POST, PUT, PATCH, DELETE)
- Automatic JSON encoding/decoding
- Comprehensive error handling
- Environment-based configuration
- Periodic token validation
## Installation
Add `ex_hospitable` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ex_hospitable, "~> 0.1.0"}
]
end
```
## Configuration
Create a `.env` file in your project root with your Hospitable API credentials:
```bash
# Hospitable API Configuration
HOSPITABLE_ACCESS_TOKEN=your_personal_access_token_here
HOSPITABLE_BASE_URL=https://public.api.hospitable.com/v2
# Optional: Timeout settings (in milliseconds)
HOSPITABLE_TIMEOUT=30000
HOSPITABLE_RECV_TIMEOUT=30000
```
## Usage
### Setting up Authentication
```elixir
# Set authentication token programmatically
HospitableClient.set_token("your_access_token")
# Check if authenticated
HospitableClient.authenticated?()
# => true
```
### Making API Requests
```elixir
# Get all properties (first page, 10 per page)
{:ok, properties} = HospitableClient.get_properties()
# Get properties with pagination
{:ok, properties} = HospitableClient.get_properties(%{
page: 2,
per_page: 25
})
# Get properties with included resources (API specification compliant)
{:ok, properties} = HospitableClient.get_properties(%{
include: "user,listings,details,bookings"
})
# Get single property by UUID
{:ok, property} = HospitableClient.get_property("550e8400-e29b-41d4-a716-446655440000")
# Get single property with all includes
{:ok, property} = HospitableClient.get_property(
"550e8400-e29b-41d4-a716-446655440000",
%{include: "user,listings,details,bookings"}
)
# Create a new property
{:ok, property} = HospitableClient.post("/properties", %{
"name" => "My Vacation Rental",
"address" => "123 Beach Street"
})
# Update a property
{:ok, property} = HospitableClient.put("/properties/123", %{
"name" => "Updated Property Name"
})
# Partially update a property
{:ok, property} = HospitableClient.patch("/properties/123", %{
"name" => "Partially Updated"
})
# Delete a property
{:ok, _} = HospitableClient.delete("/properties/123")
```
### Properties Module - Advanced Features
The `HospitableClient.Properties` module provides specialized functions for property management:
```elixir
# Get all properties across all pages (handles pagination automatically)
{:ok, all_properties} = HospitableClient.Properties.get_all_properties()
# Get properties with custom pagination settings
{:ok, properties} = HospitableClient.Properties.get_all_properties(%{
per_page: 100, # Max page size for faster fetching
max_pages: 10, # Safety limit
include: "listings" # Include related resources
})
# Extract all unique amenities from properties
{:ok, response} = HospitableClient.get_properties()
amenities = HospitableClient.Properties.list_amenities(response)
# => ["wifi", "kitchen", "parking", "pool", ...]
# Extract property types and currencies
property_types = HospitableClient.Properties.list_property_types(response)
currencies = HospitableClient.Properties.list_currencies(response)
# Calculate distance between properties (using coordinates)
prop1 = response["data"] |> List.first()
prop2 = response["data"] |> List.last()
{:ok, distance_km} = HospitableClient.Properties.distance_between(prop1, prop2, :km)
{:ok, distance_miles} = HospitableClient.Properties.distance_between(prop1, prop2, :miles)
# Find properties near specific coordinates (10km radius around Berlin)
nearby_berlin = HospitableClient.Properties.find_nearby(response, 52.5200, 13.4050, 10, :km)
# Filter properties (client-side)
berlin_properties = HospitableClient.Properties.filter_properties(response, %{
city: "Berlin"
})
listed_with_kitchen = HospitableClient.Properties.filter_properties(response, %{
listed: true,
has_amenities: ["kitchen"]
})
large_properties = HospitableClient.Properties.filter_properties(response, %{
min_capacity: 4
})
# Advanced filtering with new options
pet_friendly_villas = HospitableClient.Properties.filter_properties(response, %{
property_type: "villa",
pets_allowed: true,
min_bedrooms: 3
})
# Location-based filtering with coordinates
nearby_properties = HospitableClient.Properties.filter_properties(response, %{
within_radius: %{lat: 52.5200, lon: 13.4050, radius: 50, unit: :km}
})
# Ultra-luxury property search
luxury_properties = HospitableClient.Properties.filter_properties(response, %{
currency: "USD",
has_amenities: ["pool", "gym", "concierge"],
events_allowed: true,
min_capacity: 8,
within_radius: %{lat: 40.7589, lon: -73.9851, radius: 25, unit: :miles}
})
```
#### Available Filter Options
**Basic Filters:**
- `:listed` - Filter by listed status (true/false)
- `:property_type` - Filter by property type (villa, apartment, penthouse, etc.)
- `:room_type` - Filter by room type (entire_place, private_room, etc.)
- `:currency` - Filter by currency code (EUR, USD, GBP, etc.)
- `:calendar_restricted` - Filter by calendar restriction status
**Location Filters:**
- `:city` - Filter by city name (case insensitive)
- `:state` - Filter by state/region name (case insensitive)
- `:country` - Filter by country code (case insensitive)
- `:within_radius` - Filter by distance from coordinates `%{lat: float, lon: float, radius: float, unit: :km/:miles}`
**Capacity Filters:**
- `:min_capacity` - Filter by minimum guest capacity
- `:max_capacity` - Filter by maximum guest capacity
- `:min_bedrooms` - Filter by minimum number of bedrooms
- `:min_bathrooms` - Filter by minimum number of bathrooms
**Feature Filters:**
- `:has_amenities` - Filter properties that have ALL specified amenities
**House Rules Filters:**
- `:pets_allowed` - Filter by pet policy (true/false)
- `:smoking_allowed` - Filter by smoking policy (true/false)
- `:events_allowed` - Filter by events policy (true/false)
### Error Handling
The client returns structured error tuples for different types of failures:
```elixir
case HospitableClient.get("/properties") do
{:ok, data} ->
# Success
process_properties(data)
{:error, {:unauthorized, error_data}} ->
# Authentication failed
handle_auth_error(error_data)
{:error, {:not_found, error_data}} ->
# Resource not found
handle_not_found(error_data)
{:error, {:client_error, status, error_data}} ->
# 4xx client error
handle_client_error(status, error_data)
{:error, {:server_error, status, error_data}} ->
# 5xx server error
handle_server_error(status, error_data)
{:error, reason} ->
# Other errors (network, JSON parsing, etc.)
handle_error(reason)
end
```
### Authentication Management
The authentication state is managed by a GenServer that provides:
- Centralized token storage
- Periodic token validation
- Automatic authentication status tracking
- Token lifecycle management
```elixir
# Get current token
{:ok, token} = HospitableClient.get_token()
# Validate token manually
:ok = HospitableClient.Auth.Manager.validate_token()
# Clear authentication
:ok = HospitableClient.Auth.Manager.clear_auth()
```
## API Reference
### Main Module: `HospitableClient`
- `set_token/1` - Set authentication token
- `get_token/0` - Get current token
- `authenticated?/0` - Check authentication status
- `get/2` - Make GET request
- `post/2` - Make POST request
- `put/2` - Make PUT request
- `patch/2` - Make PATCH request
- `delete/1` - Make DELETE request
- `get_properties/1` - Get paginated list of properties
- `get_property/2` - Get single property by ID
### Properties Module: `HospitableClient.Properties`
- `get_properties/1` - Get paginated list of properties with full options
- `get_property/2` - Get single property by UUID with options
- `get_all_properties/1` - Get all properties across all pages
- `list_amenities/1` - Extract unique amenities from properties
- `list_property_types/1` - Extract unique property types from properties
- `list_currencies/1` - Extract unique currencies from properties
- `filter_properties/2` - Filter properties by various criteria
- `group_properties/2` - Group properties by a specific field
- `distance_between/3` - Calculate distance between two properties
- `find_nearby/5` - Find properties within radius of coordinates
- `valid_uuid?/1` - Validate UUID format
### Authentication: `HospitableClient.Auth.Manager`
- `set_token/1` - Set authentication token
- `get_token/0` - Get current token
- `get_credentials/0` - Get current credentials
- `authenticated?/0` - Check authentication status
- `validate_token/0` - Validate token with API
- `clear_auth/0` - Clear authentication state
## Development
### Running Tests
```bash
mix test
```
### Code Quality
```bash
# Run Credo for code analysis
mix credo
# Run Dialyzer for type checking
mix dialyzer
```
### Documentation
```bash
# Generate documentation
mix docs
```
## Architecture
The library is built with the following architectural principles:
1. **Centralized Authentication**: A GenServer manages all authentication state
2. **Separation of Concerns**: HTTP client and authentication are separate modules
3. **Fault Tolerance**: Supervisor tree ensures processes restart on failure
4. **Configuration Flexibility**: Environment-based configuration with sensible defaults
5. **Error Transparency**: Structured error returns for different failure modes
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Run the test suite and code quality checks
6. Submit a pull request
## License
MIT License - see LICENSE file for details.