UNPKG

tipi-cloudbeds

Version:

Node.js library to connect to cloudbeds REST API

576 lines (543 loc) 19 kB
const { Oops } = require('@gokiteam/oops') const Joi = require('joi') const _ = require('lodash') const request = require('request-promise-native') const QueryParamsMaker = require('./helpers/QueryParamsMaker') const GrantType = require('./enums/GrantType') const Schemas = require('./Schemas') const ErrorReason = require('./enums/ErrorReason') class Cloudbeds { get defaults () { return Object.freeze({ base: 'https://hotels.cloudbeds.com/api/v1.1', resourceName: 'tipi-cloudbeds' }) } handleError (error) { if (error.isOops) throw error let oopsError = Oops.internal('something went wrong').reason(ErrorReason.unknown) if (error.statusCode) { const { statusCode, error: { error: errorType, error_description: errorDescription } = {} } = error if (statusCode === 401 && errorType === 'access_denied') { oopsError = Oops.unauthorized('The access token is invalid or expired.') .reason(ErrorReason.invalidAccessToken) } else if (statusCode === 401 && ['invalid_request', 'invalid_client'].includes(errorType)) { oopsError = Oops.unauthorized('The refresh token is invalid or expired.') .reason(ErrorReason.invalidRefreshToken) } else if (errorDescription === 'The authorization code is invalid or has expired.') { oopsError = Oops.unauthorized('The authorization code is invalid or expired') .reason(ErrorReason.invalidAuthorizationCode) } else if (errorDescription === 'The client secret supplied for a confidential client is invalid.') { oopsError = Oops.unauthorized('The client secret supplied for a confidential client is invalid.') .reason(ErrorReason.invalidClientSecret) } else if (errorType === 'server_error' && !errorDescription) { oopsError = Oops.unauthorized('The client id is invalid.') .reason(ErrorReason.invalidClientId) } } throw oopsError.resource(this.defaults.resourceName).meta(error.toString()) } constructor ({ clientId, clientSecret, mockBase = undefined } = {}) { if (!clientId || !clientSecret) throw Oops.failedPrecondition('client id and client secret are required') this.clientId = clientId this.clientSecret = clientSecret if (mockBase) { this.mockBase = mockBase } } get baseUrl () { if (this.mockBase) { return this.mockBase } return this.defaults.base } validate (data, schema) { const validationResult = Joi.validate(data, schema) if (validationResult.error) throw Oops.invalidArgument(validationResult.error.toString()) return true } async generateToken (data) { this.validate(data, Schemas.generateToken) const { grantType: grant_type, redirectUri: redirect_uri, code, refreshToken: refresh_token } = data const requestData = { method: 'POST', uri: `${this.baseUrl}/access_token`, json: true, formData: { client_secret: this.clientSecret, client_id: this.clientId, grant_type } } if (code) requestData.formData.code = code if (redirect_uri) requestData.formData.redirect_uri = redirect_uri if (refresh_token) requestData.formData.refresh_token = refresh_token try { const { access_token: accessToken, token_type: tokenType, expires_in: expiresIn, refresh_token: refreshToken } = await request(requestData) return { accessToken, tokenType, expiresIn, refreshToken } } catch (error) { this.handleError(error) } } async generateTokenByCode (data) { this.validate(data, Schemas.generateTokenByCode) data.grantType = GrantType.authorizationCode return this.generateToken(data) } async generateTokenByRefreshToken (data) { this.validate(data, Schemas.generateTokenByRefreshToken) data.grantType = GrantType.refreshToken return this.generateToken(data) } async checkAccessToken (tokens) { this.validate(tokens, Schemas.checkAccessToken) const { accessToken, tokenType } = tokens const requestData = { method: 'POST', uri: `${this.baseUrl}/access_token_check`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) return response.success } catch (error) { return false } } async refreshAccessToken (tokens) { this.validate(tokens, Schemas.refreshAccessToken) const { accessToken, refreshToken, tokenType } = tokens const isValid = await this.checkAccessToken({ accessToken, tokenType }) if (isValid) return tokens const newTokens = await this.generateTokenByRefreshToken({ refreshToken }) return newTokens } async subscribeToEvent (tokens, data) { this.validate({ tokens, data }, Schemas.subscribeToEvent) const { tokenType, accessToken } = tokens const requestData = { method: 'POST', uri: `${this.baseUrl}/postWebhook`, headers: { Authorization: `${tokenType} ${accessToken}` }, formData: data, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.alreadyExists('Subscription already exists') return response.data.subscriptionID } catch (error) { this.handleError(error) } } async unSubscribeFromEvent (tokens, data) { this.validate({ tokens, data }, Schemas.unSubscribeFromEvent) const { tokenType, accessToken } = tokens const { subscriptionId } = data const requestData = { method: 'DELETE', uri: `${this.baseUrl}/deleteWebhook?subscriptionID=${subscriptionId}`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.notFound('Subscription not found') return true } catch (error) { this.handleError(error) } } async getReservationById (tokens, data) { this.validate({ tokens, data }, Schemas.getReservationById) const { tokenType, accessToken } = tokens const { reservationId } = data const requestData = { method: 'GET', uri: `${this.baseUrl}/getReservation?reservationID=${reservationId}`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.notFound('Reservation not found') return response.data } catch (error) { this.handleError(error) } } async getReservations (tokens, data) { this.validate({ tokens, data }, Schemas.getReservations) const { tokenType, accessToken } = tokens const { propertyId: propertyID, ...rest } = data let uri = `${this.baseUrl}/getReservations` if (!_.isEmpty(data)) { const queryParams = QueryParamsMaker({ propertyID, ...rest }) uri = `${uri}?${queryParams}` } const requestData = { method: 'GET', uri, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.notFound('Something went wrong on getting reservations from cloudbeds!') return response } catch (error) { this.handleError(error) } } async postAppState (tokens, data) { this.validate({ tokens, data }, Schemas.postAppState) const { tokenType, accessToken } = tokens const { propertyId: propertyID, appState: app_state } = data const requestData = { method: 'POST', uri: `${this.baseUrl}/postAppState`, headers: { Authorization: `${tokenType} ${accessToken}` }, formData: { propertyID, app_state }, json: true } try { const response = await request(requestData) return response } catch (error) { this.handleError(error) } } async postRoomCheckIn (tokens, data) { this.validate({ tokens, data }, Schemas.postRoomCheckIn) const { tokenType, accessToken } = tokens const { reservationId: reservationID, subReservationId: subReservationID, roomId: roomID } = data const payload = { reservationID } if (subReservationID) payload.subReservationID = subReservationID if (roomID) payload.roomID = roomID const requestData = { method: 'POST', uri: `${this.baseUrl}/postRoomCheckIn`, headers: { Authorization: `${tokenType} ${accessToken}` }, formData: payload, json: true } try { const response = await request(requestData) return response } catch (error) { this.handleError(error) } } async getHotels (tokens) { this.validate(tokens, Schemas.getHotels) const { tokenType, accessToken } = tokens const requestData = { method: 'GET', uri: `${this.baseUrl}/getHotels`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.notFound('Hostels not found') return response.data } catch (error) { this.handleError(error) } } async getRooms (tokens, data) { this.validate({ tokens, data }, Schemas.getRooms) const { tokenType, accessToken } = tokens const { page, size } = data const requestData = { method: 'GET', uri: `${this.baseUrl}/getRooms?pageNumber=${page}&pageSize=${size}`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.notFound('Rooms not found') return response.data } catch (error) { this.handleError(error) } } async getReservationsWithRateDetails (tokens, data) { this.validate({ tokens, data }, Schemas.getReservationsWithRateDetails) const { tokenType, accessToken } = tokens const { reservationId } = data const requestData = { method: 'GET', uri: `${this.baseUrl}/getReservationsWithRateDetails?reservationID=${reservationId}`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.notFound('Reservation not found') const reservation = response.data.find(item => item.reservationID === reservationId) if (!reservation) throw Oops.notFound('Reservation not found') return reservation } catch (error) { this.handleError(error) } } // We need this function because /getReservationsWithRateDetails endpoint implemented in another // function (getReservationsWithRateDetails) and used in services to get one item async batchGetReservationsWithRateDetails (tokens, data) { this.validate({ tokens, data }, Schemas.getReservationsWithRateDetails) const { tokenType, accessToken } = tokens const { reservationId } = data const requestData = { method: 'GET', uri: `${this.baseUrl}/getReservationsWithRateDetails?reservationID=${reservationId}`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.notFound('Reservation not found') return response } catch (error) { this.handleError(error) } } async getHousekeepingStatus (tokens, data) { this.validate({ tokens, data }, Schemas.getHousekeepingStatus) const { tokenType, accessToken } = tokens const { roomTypeIds: roomTypeIDs, housekeeperIds: housekeeperIDs, ...rest } = data let uri = `${this.baseUrl}/getHousekeepingStatus` if (!_.isEmpty(data)) { const queryParams = QueryParamsMaker({ roomTypeIDs, housekeeperIDs, ...rest }) uri = `${uri}?${queryParams}` } const requestData = { method: 'GET', uri, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) { throw Oops.notFound('House keeping status not found').reason(ErrorReason.houseKeepingStatusNotFound) } return response } catch (error) { this.handleError(error) } } async postReservationNote (tokens, data) { this.validate({ tokens, data }, Schemas.postReservationNote) const { tokenType, accessToken } = tokens const { reservationId: reservationID, reservationNote } = data const payload = { reservationID, reservationNote } const requestData = { method: 'POST', uri: `${this.baseUrl}/postReservationNote`, headers: { Authorization: `${tokenType} ${accessToken}` }, formData: payload, json: true } try { const response = await request(requestData) return response } catch (error) { this.handleError(error) } } async getWebhooks (tokens) { this.validate({ tokens }, Schemas.getWebhooks) const { tokenType, accessToken } = tokens const requestData = { method: 'GET', uri: `${this.baseUrl}/getWebhooks`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) throw Oops.notFound('Webhooks not found') return response.data } catch (error) { this.handleError(error) } } async getGuestsByFilter (tokens, data) { this.validate({ tokens, data }, Schemas.getGuestsByFilter) const { tokenType, accessToken } = tokens const { reservationId: reservationID, roomId: roomID, propertyIds: propertyIDs } = data const queryParams = QueryParamsMaker({ reservationID, roomID, propertyIDs }) const requestData = { method: 'GET', uri: `${this.baseUrl}/getGuestsByFilter?${queryParams}`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) { throw Oops.unknown(response.message || 'unknown error').reason(ErrorReason.unsuccessfulRequest) } return response.data } catch (error) { this.handleError(error) } } async getGuest (tokens, data) { this.validate({ tokens, data }, Schemas.getGuest) const { tokenType, accessToken } = tokens const { reservationId: reservationID, guestId: guestID, propertyId: propertyID } = data const queryParams = QueryParamsMaker({ reservationID, guestID, propertyID }) const requestData = { method: 'GET', uri: `${this.baseUrl}/getGuest?${queryParams}`, headers: { Authorization: `${tokenType} ${accessToken}` }, json: true } try { const response = await request(requestData) if (response.success === false) { throw Oops.unknown(response.message || 'unknown error').reason(ErrorReason.unsuccessfulRequest) } return response.data } catch (error) { this.handleError(error) } } async putGuest (tokens, data) { this.validate({ tokens, data }, Schemas.putGuest) const { tokenType, accessToken } = tokens const { guestId: guestID, propertyId: propertyID, ...rest } = data const payload = { guestID, ...propertyID && { propertyID }, ...rest } const requestData = { method: 'PUT', uri: `${this.baseUrl}/putGuest`, headers: { Authorization: `${tokenType} ${accessToken}` }, body: payload, json: true } try { const response = await request(requestData) if (response.success === false) { throw Oops.unknown(response.message || 'unknown error').reason(ErrorReason.unsuccessfulRequest) } return response.data } catch (error) { this.handleError(error) } } async postGuest (tokens, data) { this.validate({ tokens, data }, Schemas.postGuest) const { tokenType, accessToken } = tokens const { reservationId: reservationID, propertyId: propertyID, ...rest } = data const payload = { reservationID, ...propertyID && { propertyID }, ...rest } const requestData = { method: 'POST', uri: `${this.baseUrl}/postGuest`, headers: { Authorization: `${tokenType} ${accessToken}` }, body: payload, json: true } try { const response = await request(requestData) if (response.success === false) { if (response.message === 'Max number of guests exceeded') { throw Oops.notAcceptable(response.message).reason(ErrorReason.reservationCapacityIsFull) } throw Oops.unknown(response.message || 'unknown error').reason(ErrorReason.unsuccessfulRequest) } return { guestId: response.guestID } } catch (error) { this.handleError(error) } } async postGuestsToRoom (tokens, data) { this.validate({ tokens, data }, Schemas.postGuestsToRoom) const { tokenType, accessToken } = tokens const { reservationId: reservationID, propertyId: propertyID, roomId: roomID, guestIds: guestIDs, removeGuestIds: removeGuestIDs, ...rest } = data const payload = { reservationID, roomID, guestIDs, ...removeGuestIDs && { removeGuestIDs }, ...propertyID && { propertyID }, ...rest } const requestData = { method: 'POST', uri: `${this.baseUrl}/postGuestsToRoom`, headers: { Authorization: `${tokenType} ${accessToken}` }, body: payload, json: true } try { const response = await request(requestData) if (response.success === false) { throw Oops.unknown(response.message || 'unknown error').reason(ErrorReason.unsuccessfulRequest) } return response.data } catch (error) { this.handleError(error) } } } module.exports = Cloudbeds