Skip to content

dmbr0/ex_hospitable

Repository files navigation

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:

def deps do
  [
    {:ex_hospitable, "~> 0.1.0"}
  ]
end

Configuration

Create a .env file in your project root with your Hospitable API credentials:

# 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

# Set authentication token programmatically
HospitableClient.set_token("your_access_token")

# Check if authenticated
HospitableClient.authenticated?()
# => true

Making API Requests

# 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:

# 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:

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
# 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

mix test

Code Quality

# Run Credo for code analysis
mix credo

# Run Dialyzer for type checking
mix dialyzer

Documentation

# 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.

About

A Hospitable API client written in Elixir.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages