liteapi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ module Analytics
5
+ # Retrieve weekly analytics
6
+ #
7
+ # Fetches weekly analytics data for a date range.
8
+ #
9
+ # @param from [String] Start date (YYYY-MM-DD)
10
+ # @param to [String] End date (YYYY-MM-DD)
11
+ # @return [Hash] Weekly analytics data
12
+ def weekly_analytics(from:, to:)
13
+ dashboard_post('analytics/weekly', { from: from, to: to })
14
+ end
15
+
16
+ # Retrieve detailed analytics report
17
+ #
18
+ # Fetches a detailed analytics report including revenue and sales data.
19
+ #
20
+ # @param from [String] Start date (YYYY-MM-DD)
21
+ # @param to [String] End date (YYYY-MM-DD)
22
+ # @return [Hash] Detailed analytics report
23
+ def analytics_report(from:, to:)
24
+ dashboard_post('analytics/report', { from: from, to: to })
25
+ end
26
+
27
+ # Retrieve market analytics
28
+ #
29
+ # Fetches market-level analytics data.
30
+ #
31
+ # @param from [String] Start date (YYYY-MM-DD)
32
+ # @param to [String] End date (YYYY-MM-DD)
33
+ # @return [Hash] Market analytics data
34
+ def market_analytics(from:, to:)
35
+ dashboard_post('analytics/market', { from: from, to: to })
36
+ end
37
+
38
+ # Retrieve most booked hotels
39
+ #
40
+ # Fetches data on most booked hotels during a date range.
41
+ #
42
+ # @param from [String] Start date (YYYY-MM-DD)
43
+ # @param to [String] End date (YYYY-MM-DD)
44
+ # @return [Array] List of most booked hotels
45
+ def most_booked_hotels(from:, to:)
46
+ dashboard_post('analytics/top-hotels', { from: from, to: to })
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ module Booking
5
+ # Pre-book a rate to confirm availability and pricing
6
+ #
7
+ # Confirms if the room and rates are still available. Returns a prebook_id
8
+ # needed for the final booking.
9
+ #
10
+ # @param offer_id [String] The offer/rate ID from full_rates
11
+ # @return [Hash] Prebook confirmation with prebook_id
12
+ def prebook(offer_id:)
13
+ book_post('rates/prebook', { offerId: offer_id })
14
+ end
15
+
16
+ # Complete a booking
17
+ #
18
+ # Confirms the booking with guest and payment information.
19
+ #
20
+ # @param prebook_id [String] The prebook ID from prebook response
21
+ # @param guest [Hash] Guest information (first_name, last_name, email)
22
+ # @param payment [Hash] Payment information (card holder name, number, expiry, cvc)
23
+ # @param client_reference [String, nil] Your reference for this booking
24
+ # @return [Hash] Booking confirmation with booking_id
25
+ def book(prebook_id:, guest:, payment:, client_reference: nil)
26
+ body = {
27
+ prebookId: prebook_id,
28
+ guestInfo: {
29
+ guestFirstName: guest[:first_name],
30
+ guestLastName: guest[:last_name],
31
+ guestEmail: guest[:email]
32
+ },
33
+ paymentMethod: {
34
+ holderName: payment[:holder_name],
35
+ paymentMethodId: payment[:payment_method_id] || 'creditCard',
36
+ cardNumber: payment[:card_number],
37
+ expireDate: payment[:expire_date],
38
+ cvc: payment[:cvc]
39
+ }
40
+ }
41
+ body[:clientReference] = client_reference if client_reference
42
+
43
+ book_post('rates/book', body)
44
+ end
45
+
46
+ # List bookings by client reference
47
+ #
48
+ # @param client_reference [String] Your reference to search for
49
+ # @return [Array] List of bookings
50
+ def bookings(client_reference:)
51
+ book_get('bookings', clientReference: client_reference)
52
+ end
53
+
54
+ # Retrieve a specific booking
55
+ #
56
+ # @param booking_id [String] The booking ID
57
+ # @return [Hash] Booking details
58
+ def booking(booking_id)
59
+ book_get("bookings/#{booking_id}")
60
+ end
61
+
62
+ # Cancel a booking
63
+ #
64
+ # Cancellation success depends on the cancellation policy.
65
+ # Non-refundable bookings or those past the cancellation date cannot be cancelled.
66
+ #
67
+ # @param booking_id [String] The booking ID to cancel
68
+ # @return [Hash] Cancellation confirmation
69
+ def cancel_booking(booking_id)
70
+ book_put("bookings/#{booking_id}")
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ class Client
5
+ include StaticData
6
+ include Rates
7
+ include Booking
8
+ include Guests
9
+ include Vouchers
10
+ include Analytics
11
+
12
+ attr_reader :api_key, :base_url, :book_base_url, :dashboard_base_url,
13
+ :timeout, :open_timeout, :max_retries
14
+
15
+ def initialize(api_key: nil, base_url: nil, book_base_url: nil, dashboard_base_url: nil,
16
+ timeout: nil, open_timeout: nil, max_retries: nil)
17
+ config = Liteapi.configuration
18
+
19
+ @api_key = api_key || config.api_key
20
+ @base_url = base_url || config.base_url
21
+ @book_base_url = book_base_url || config.book_base_url
22
+ @dashboard_base_url = dashboard_base_url || config.dashboard_base_url
23
+ @timeout = timeout || config.timeout
24
+ @open_timeout = open_timeout || config.open_timeout
25
+ @max_retries = max_retries || config.max_retries
26
+
27
+ validate_api_key!
28
+ end
29
+
30
+ private
31
+
32
+ def validate_api_key!
33
+ return if @api_key && !@api_key.empty?
34
+
35
+ raise Liteapi::Error, 'API key is required. Set via Liteapi.configure or pass api_key: to Client.new'
36
+ end
37
+
38
+ def connection
39
+ @connection ||= Connection.new(
40
+ api_key: @api_key,
41
+ base_url: @base_url,
42
+ timeout: @timeout,
43
+ open_timeout: @open_timeout,
44
+ max_retries: @max_retries
45
+ )
46
+ end
47
+
48
+ def book_connection
49
+ @book_connection ||= Connection.new(
50
+ api_key: @api_key,
51
+ base_url: @book_base_url,
52
+ timeout: @timeout,
53
+ open_timeout: @open_timeout,
54
+ max_retries: @max_retries
55
+ )
56
+ end
57
+
58
+ def dashboard_connection
59
+ @dashboard_connection ||= Connection.new(
60
+ api_key: @api_key,
61
+ base_url: @dashboard_base_url,
62
+ timeout: @timeout,
63
+ open_timeout: @open_timeout,
64
+ max_retries: @max_retries
65
+ )
66
+ end
67
+
68
+ def get(path, params = {})
69
+ connection.get(path, params)
70
+ end
71
+
72
+ def post(path, body = {})
73
+ connection.post(path, body)
74
+ end
75
+
76
+ def put(path, body = {})
77
+ connection.put(path, body)
78
+ end
79
+
80
+ def book_get(path, params = {})
81
+ book_connection.get(path, params)
82
+ end
83
+
84
+ def book_post(path, body = {})
85
+ book_connection.post(path, body)
86
+ end
87
+
88
+ def book_put(path, body = {})
89
+ book_connection.put(path, body)
90
+ end
91
+
92
+ def dashboard_get(path, params = {})
93
+ dashboard_connection.get(path, params)
94
+ end
95
+
96
+ def dashboard_post(path, body = {})
97
+ dashboard_connection.post(path, body)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ class Configuration
5
+ attr_accessor :api_key, :base_url, :book_base_url, :dashboard_base_url,
6
+ :timeout, :open_timeout, :max_retries
7
+
8
+ DEFAULT_BASE_URL = 'https://api.liteapi.travel/v3.0'
9
+ DEFAULT_BOOK_BASE_URL = 'https://book.liteapi.travel/v3.0'
10
+ DEFAULT_DASHBOARD_BASE_URL = 'https://da.liteapi.travel'
11
+ DEFAULT_TIMEOUT = 30
12
+ DEFAULT_OPEN_TIMEOUT = 10
13
+ DEFAULT_MAX_RETRIES = 3
14
+
15
+ def initialize
16
+ @api_key = ENV.fetch('LITEAPI_API_KEY', nil)
17
+ @base_url = DEFAULT_BASE_URL
18
+ @book_base_url = DEFAULT_BOOK_BASE_URL
19
+ @dashboard_base_url = DEFAULT_DASHBOARD_BASE_URL
20
+ @timeout = DEFAULT_TIMEOUT
21
+ @open_timeout = DEFAULT_OPEN_TIMEOUT
22
+ @max_retries = DEFAULT_MAX_RETRIES
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday/retry'
5
+
6
+ module Liteapi
7
+ class Connection
8
+ RETRY_STATUSES = [429, 500, 502, 503, 504].freeze
9
+
10
+ def initialize(api_key:, base_url:, timeout:, open_timeout:, max_retries:)
11
+ @api_key = api_key
12
+ @base_url = base_url
13
+ @timeout = timeout
14
+ @open_timeout = open_timeout
15
+ @max_retries = max_retries
16
+ end
17
+
18
+ def get(path, params = {})
19
+ request(:get, path, params)
20
+ end
21
+
22
+ def post(path, body = {})
23
+ request(:post, path, body)
24
+ end
25
+
26
+ def put(path, body = {})
27
+ request(:put, path, body)
28
+ end
29
+
30
+ private
31
+
32
+ def request(method, path, payload)
33
+ response = connection.public_send(method, path) do |req|
34
+ case method
35
+ when :get
36
+ req.params = payload if payload.any?
37
+ when :post, :put
38
+ req.body = payload
39
+ end
40
+ end
41
+
42
+ handle_response(response)
43
+ rescue Faraday::TimeoutError
44
+ raise Liteapi::Error, 'Request timed out'
45
+ rescue Faraday::ConnectionFailed
46
+ raise Liteapi::Error, 'Connection failed'
47
+ end
48
+
49
+ def handle_response(response)
50
+ body = response.body || {}
51
+
52
+ case response.status
53
+ when 200..299
54
+ body['data']
55
+ when 401
56
+ raise AuthenticationError.new(
57
+ error_message(body),
58
+ status: response.status,
59
+ code: body.dig('error', 'code'),
60
+ response: body
61
+ )
62
+ when 404
63
+ raise NotFoundError.new(
64
+ error_message(body),
65
+ status: response.status,
66
+ code: body.dig('error', 'code'),
67
+ response: body
68
+ )
69
+ when 422
70
+ raise ValidationError.new(
71
+ error_message(body),
72
+ status: response.status,
73
+ code: body.dig('error', 'code'),
74
+ response: body
75
+ )
76
+ when 429
77
+ raise RateLimitError.new(
78
+ error_message(body),
79
+ status: response.status,
80
+ code: body.dig('error', 'code'),
81
+ response: body
82
+ )
83
+ when 400..499
84
+ raise ClientError.new(
85
+ error_message(body),
86
+ status: response.status,
87
+ code: body.dig('error', 'code'),
88
+ response: body
89
+ )
90
+ when 500..599
91
+ raise ServerError.new(
92
+ error_message(body),
93
+ status: response.status,
94
+ code: body.dig('error', 'code'),
95
+ response: body
96
+ )
97
+ else
98
+ raise APIError.new(
99
+ "Unexpected response status: #{response.status}",
100
+ status: response.status,
101
+ response: body
102
+ )
103
+ end
104
+ end
105
+
106
+ def error_message(body)
107
+ body.dig('error', 'message') || body['message'] || 'Unknown error'
108
+ end
109
+
110
+ def connection
111
+ @connection ||= Faraday.new(url: @base_url) do |f|
112
+ f.request :json
113
+ f.response :json
114
+ f.request :retry, retry_options
115
+ f.options.timeout = @timeout
116
+ f.options.open_timeout = @open_timeout
117
+ f.headers['X-API-Key'] = @api_key
118
+ f.headers['Accept'] = 'application/json'
119
+ f.adapter Faraday.default_adapter
120
+ end
121
+ end
122
+
123
+ def retry_options
124
+ {
125
+ max: @max_retries,
126
+ interval: 0.5,
127
+ interval_randomness: 0.5,
128
+ backoff_factor: 2,
129
+ retry_statuses: RETRY_STATUSES,
130
+ retry_block: ->(_env, _options, retries, exception) {
131
+ # Could add logging here if needed
132
+ }
133
+ }
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ class Error < StandardError; end
5
+
6
+ class APIError < Error
7
+ attr_reader :status, :code, :response
8
+
9
+ def initialize(message = nil, status: nil, code: nil, response: nil)
10
+ @status = status
11
+ @code = code
12
+ @response = response
13
+ super(message)
14
+ end
15
+ end
16
+
17
+ class ClientError < APIError; end
18
+ class ServerError < APIError; end
19
+
20
+ class AuthenticationError < ClientError; end
21
+ class NotFoundError < ClientError; end
22
+ class RateLimitError < ClientError; end
23
+ class ValidationError < ClientError; end
24
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ module Guests
5
+ # Get loyalty program status
6
+ #
7
+ # Returns current loyalty program settings including status and cashback rates.
8
+ #
9
+ # @return [Hash] Loyalty program details
10
+ def loyalty
11
+ get('guests/loyalty')
12
+ end
13
+
14
+ # Enable loyalty program
15
+ #
16
+ # Creates a new loyalty program with specified settings.
17
+ #
18
+ # @param status [String] Program status ('enabled' or 'disabled')
19
+ # @param cashback_rate [Float] Cashback rate (e.g., 0.03 = 3%)
20
+ # @return [Hash] Loyalty program details
21
+ def enable_loyalty(status:, cashback_rate:)
22
+ post('guests/loyalty', {
23
+ status: status,
24
+ cashbackRate: cashback_rate
25
+ })
26
+ end
27
+
28
+ # Update loyalty program
29
+ #
30
+ # Updates existing loyalty program settings.
31
+ #
32
+ # @param status [String] Program status ('enabled' or 'disabled')
33
+ # @param cashback_rate [Float] Cashback rate (e.g., 0.03 = 3%)
34
+ # @return [Hash] Updated loyalty program details
35
+ def update_loyalty(status:, cashback_rate:)
36
+ put('guests/loyalty', {
37
+ status: status,
38
+ cashbackRate: cashback_rate
39
+ })
40
+ end
41
+
42
+ # Get guest details
43
+ #
44
+ # Retrieves detailed information about a guest including personal data,
45
+ # loyalty points, and booking history.
46
+ #
47
+ # @param guest_id [String, Integer] Guest identifier
48
+ # @return [Hash] Guest details
49
+ def guest(guest_id)
50
+ get("guests/#{guest_id}")
51
+ end
52
+
53
+ # Get guest bookings
54
+ #
55
+ # Retrieves all bookings for a specific guest with loyalty points
56
+ # and cashback information.
57
+ #
58
+ # @param guest_id [String, Integer] Guest identifier
59
+ # @return [Array] List of guest bookings
60
+ def guest_bookings(guest_id)
61
+ get("guests/#{guest_id}/bookings")
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ module Rates
5
+ # Search for full rates and availability for hotels
6
+ #
7
+ # Returns all available rooms with rates, cancellation policies for a list of hotel IDs.
8
+ # Includes loyalty rewards if guest_id is provided.
9
+ #
10
+ # @param hotel_ids [Array<String>] List of hotel IDs to search
11
+ # @param checkin [String] Check-in date (YYYY-MM-DD)
12
+ # @param checkout [String] Check-out date (YYYY-MM-DD)
13
+ # @param occupancies [Array<Hash>] Room occupancies, each with :rooms, :adults, :children (ages array)
14
+ # @param guest_nationality [String] Guest nationality (ISO-2)
15
+ # @param currency [String] Currency code
16
+ # @param guest_id [String, nil] Guest ID for loyalty pricing
17
+ # @return [Hash] Rates and availability data
18
+ #
19
+ # @example
20
+ # client.full_rates(
21
+ # hotel_ids: ['lp1234', 'lp5678'],
22
+ # checkin: '2025-03-01',
23
+ # checkout: '2025-03-03',
24
+ # occupancies: [{ rooms: 1, adults: 2, children: [5, 10] }],
25
+ # guest_nationality: 'US',
26
+ # currency: 'USD'
27
+ # )
28
+ def full_rates(hotel_ids:, checkin:, checkout:, occupancies:, guest_nationality:, currency:,
29
+ guest_id: nil)
30
+ body = {
31
+ hotelIds: hotel_ids,
32
+ checkin: checkin,
33
+ checkout: checkout,
34
+ occupancies: occupancies,
35
+ guestNationality: guest_nationality,
36
+ currency: currency
37
+ }
38
+ body[:guestId] = guest_id if guest_id
39
+
40
+ post('hotels/rates', body)
41
+ end
42
+
43
+ # Search for minimum rates for hotels
44
+ #
45
+ # Returns only the minimum rate per hotel for quick comparisons.
46
+ #
47
+ # @param hotel_ids [Array<String>] List of hotel IDs to search
48
+ # @param checkin [String] Check-in date (YYYY-MM-DD)
49
+ # @param checkout [String] Check-out date (YYYY-MM-DD)
50
+ # @param occupancies [Array<Hash>] Room occupancies, each with :rooms, :adults, :children (ages array)
51
+ # @param guest_nationality [String] Guest nationality (ISO-2)
52
+ # @param currency [String] Currency code
53
+ # @return [Hash] Minimum rates data
54
+ #
55
+ # @example
56
+ # client.min_rates(
57
+ # hotel_ids: ['lp1234'],
58
+ # checkin: '2025-03-01',
59
+ # checkout: '2025-03-03',
60
+ # occupancies: [{ rooms: 1, adults: 2 }],
61
+ # guest_nationality: 'US',
62
+ # currency: 'USD'
63
+ # )
64
+ def min_rates(hotel_ids:, checkin:, checkout:, occupancies:, guest_nationality:, currency:)
65
+ body = {
66
+ hotelIds: hotel_ids,
67
+ checkin: checkin,
68
+ checkout: checkout,
69
+ occupancies: occupancies,
70
+ guestNationality: guest_nationality,
71
+ currency: currency
72
+ }
73
+
74
+ post('hotels/min-rates', body)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ module StaticData
5
+ # Returns list of all countries with ISO-2 codes
6
+ def countries
7
+ get('data/countries')
8
+ end
9
+
10
+ # Returns list of cities for a country
11
+ #
12
+ # @param country_code [String] ISO-2 country code (e.g., 'SG', 'US')
13
+ def cities(country_code:)
14
+ get('data/cities', countryCode: country_code)
15
+ end
16
+
17
+ # Search for places and areas
18
+ #
19
+ # @param query [String] Search text (e.g., 'Rome', 'Manhattan')
20
+ # @param type [String, nil] Filter by place type (e.g., 'hotel')
21
+ # @param language [String] Language for results (default: 'en')
22
+ def places(query:, type: nil, language: 'en')
23
+ params = { textQuery: query, language: language }
24
+ params[:type] = type if type
25
+ get('data/places', params)
26
+ end
27
+
28
+ # Returns all available currency codes
29
+ def currencies
30
+ get('data/currencies')
31
+ end
32
+
33
+ # Returns IATA codes for all airports
34
+ def iata_codes
35
+ get('data/iataCodes')
36
+ end
37
+
38
+ # Returns list of hotel facility types
39
+ def hotel_facilities
40
+ get('data/facilities')
41
+ end
42
+
43
+ # Returns list of hotel types (Hotel, Hostel, Resort, etc.)
44
+ def hotel_types
45
+ get('data/hotelTypes')
46
+ end
47
+
48
+ # Returns list of hotel chains
49
+ def hotel_chains
50
+ get('data/chains')
51
+ end
52
+
53
+ # Search for hotels
54
+ #
55
+ # @param country_code [String] ISO-2 country code (required)
56
+ # @param city_name [String, nil] Filter by city name
57
+ # @param latitude [Float, nil] Latitude for geo search
58
+ # @param longitude [Float, nil] Longitude for geo search
59
+ # @param radius [Integer, nil] Search radius in km (for geo search)
60
+ # @param language [String] Language for results (default: 'en')
61
+ def hotels(country_code:, city_name: nil, latitude: nil, longitude: nil, radius: nil, language: 'en')
62
+ params = { countryCode: country_code, language: language }
63
+ params[:cityName] = city_name if city_name
64
+ params[:latitude] = latitude if latitude
65
+ params[:longitude] = longitude if longitude
66
+ params[:radius] = radius if radius
67
+ get('data/hotels', params)
68
+ end
69
+
70
+ # Get detailed hotel information
71
+ #
72
+ # @param hotel_id [String] Hotel identifier
73
+ # @param language [String, nil] Language for results
74
+ def hotel(hotel_id, language: nil)
75
+ params = { hotelId: hotel_id }
76
+ params[:language] = language if language
77
+ get('data/hotel', params)
78
+ end
79
+
80
+ # Get hotel reviews with optional sentiment analysis
81
+ #
82
+ # @param hotel_id [String] Hotel identifier
83
+ # @param limit [Integer, nil] Maximum reviews to return (max 1000)
84
+ # @param sentiment [Boolean] Include sentiment analysis
85
+ def hotel_reviews(hotel_id:, limit: nil, sentiment: false)
86
+ params = { hotelId: hotel_id }
87
+ params[:limit] = limit if limit
88
+ params[:getSentiment] = sentiment if sentiment
89
+ get('data/reviews', params)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liteapi
4
+ VERSION = '0.1.0'
5
+ end