1 line
281 KiB
JSON
1 line
281 KiB
JSON
{"repo": "charlespjr/hotel3", "files": {"server/server.js": {"35551d303aa1b7f97d4557e66dbc3763fb888157": {"t": 1785746684.3178895, "n": 27649, "h": "805ebc805d8a295d", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nconst fs = require(\"fs\");\n// Load environment variables from .env file\n// Ensure you have a .env file in the root directory with:\n// PROD_API_KEY=your_lite_api_key_here\n// DEEPSEEK_API_KEY=your_deepseek_api_key_here\n// FRONTEND_URL=http://localhost:your_frontend_port (if applicable for CORS in dev)\nrequire(\"dotenv\").config();\nconsole.log(\"Environment variables loaded:\", {\n DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY ? \"Present\" : \"Missing\",\n PROD_API_KEY: process.env.PROD_API_KEY ? \"Present\" : \"Missing\",\n FRONTEND_URL: process.env.FRONTEND_URL || \"Not set\",\n BASE_URL: process.env.BASE_URL || \"Not set\"\n});\nconst { sendBookingConfirmation } = require('./email');\nconst { generateAllArticles, generateArticle, blogTopics, generateHTML } = require('./blog-generator');\nconst OpenAI = require('openai');\n\n// Initialize DeepSeek OpenAI client\nconst deepSeekApiKey = process.env.DEEPSEEK_API_KEY;\nlet openaiClient;\nif (deepSeekApiKey) {\n openaiClient = new OpenAI({\n apiKey: deepSeekApiKey,\n baseURL: 'https://api.deepseek.com/v1', // Or 'https://api.deepseek.com'\n });\n console.log(\"DeepSeek OpenAI client initialized.\");\n} else {\n console.warn(\"DEEPSEEK_API_KEY not found in .env. DeepSeek client not initialized.\");\n}\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\n// Add blog posts endpoint\napp.get('/api/blog-posts', (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const blogPosts = files.filter(file => file.endsWith('.html'));\n res.json(blogPosts);\n } catch (error) {\n console.error('Error reading blog posts:', error);\n res.status(500).json({ error: 'Failed to load blog posts' });\n }\n});\n\n// Serve individual blog posts\napp.get('/blog/:post', (req, res) => {\n const postFile = req.params.post;\n const blogDir = path.join(__dirname, '../client/blog');\n const postPath = path.join(blogDir, postFile);\n \n if (fs.existsSync(postPath)) {\n res.sendFile(postPath);\n } else {\n res.status(404).send('Blog post not found');\n }\n});\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\n\n/**\n * Fetches hotel list from LITE API based on city/country,\n * then gets full rates for these hotels based on checkin, checkout, and adults.\n * @param {object} searchParams - Contains city, countryCode, checkin, checkout, adults.\n * @returns {Promise<object>} Object containing rates array or empty if not found/error.\n */\nasync function fetchHotelsAndRates(searchParams) {\n const { city, countryCode, checkin, checkout, adults } = searchParams;\n console.log(\"Refactored search: Searching hotels with params:\", { countryCode, city, checkin, checkout, adults });\n const apiKey = process.env.PROD_API_KEY; // Ensure this is accessible\n if (!apiKey) {\n throw new Error(\"LITE_API_KEY (PROD_API_KEY) missing\");\n }\n const sdk = liteApi(apiKey);\n\n try {\n const hotelListResponse = await sdk.getHotels(countryCode, city, 0, 10); // Limit to 10 for now\n if (!hotelListResponse || !hotelListResponse.data || !Array.isArray(hotelListResponse.data)) {\n console.error(\"Invalid hotel list response from LITE API:\", hotelListResponse);\n return { rates: [] }; // Or throw specific error\n }\n const hotelsData = hotelListResponse.data;\n if (hotelsData.length === 0) {\n console.log(\"No hotels found by LITE API for the given criteria.\");\n return { rates: [] };\n }\n const hotelIds = hotelsData.map((hotel) => hotel.id);\n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\", // Or make configurable\n guestNationality: \"US\", // Or make configurable\n checkin: checkin,\n checkout: checkout,\n });\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response from LITE API:\", ratesResponse);\n return { rates: [] }; // Or throw specific error\n }\n const ratesData = ratesResponse.data;\n ratesData.forEach((rate) => {\n rate.hotel = hotelsData.find((hotel) => hotel.id === rate.hotelId);\n });\n return { rates: ratesData };\n } catch (error) {\n console.error(\"Error in fetchHotelsAndRates:\", error);\n throw error; // Re-throw to be caught by caller\n }\n}\n\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit via GET\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n try {\n const result = await fetchHotelsAndRates({ city, countryCode, checkin, checkout, adults });\n res.json(result);\n } catch (error) {\n console.error(\"Error in /api/search-hotels GET endpoint:\", error);\n res.status(500).json({ error: \"Internal server error\", details: error.message });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve sitemap.xml with correct content type\napp.get('/sitemap.xml', (req, res) => {\n res.type('application/xml');\n res.sendFile(path.join(__dirname, '../client/sitemap.xml'));\n});\n\n// New Chatbot API endpoint\napp.post(\"/api/chatbot/converse\", async (req, res) => {\n const { message, history = [] } = req.body;\n\n if (!openaiClient) {\n return res.status(500).json({ reply: \"Chatbot is not configured (missing API key).\" });\n }\n\n // System prompt to guide the DeepSeek model\n // It instructs the model on its role, required parameters for hotel search,\n // and how/when to use the 'get_hotel_availability' function.\n const messages = [\n {\n role: \"system\",\n content: \"You are a helpful assistant for booking hotels. Your goal is to gather necessary information (city, countryCode, check-in date, check-out date, and number of adults) from the user to search for hotels. If any of these details are missing, ask the user for them. Once you have all details, use the 'get_hotel_availability' function. Do not make up information if the user asks for something you don't know; instead, say you don't know or ask them to clarify. Dates should be in YYYY-MM-DD format.\"\n },\n ...history, // Spread previous messages\n { role: \"user\", content: message }\n ];\n\n // Define the tool(s) that the DeepSeek model can request to call.\n // 'get_hotel_availability' allows the model to ask our server to fetch hotel data.\n const tools = [\n {\n type: \"function\",\n function: {\n name: \"get_hotel_availability\",\n description: \"Get hotel availability and rates based on user criteria.\",\n parameters: {\n type: \"object\",\n properties: {\n city: { type: \"string\", description: \"The city for the hotel search, e.g., London\" },\n countryCode: { type: \"string\", description: \"The ISO-2 country code for the city, e.g., GB for United Kingdom. Infer this if possible from the city or ask the user.\" },\n checkin: { type: \"string\", description: \"Check-in date in YYYY-MM-DD format.\" },\n checkout: { type: \"string\", description: \"Check-out date in YYYY-MM-DD format.\" },\n adults: { type: \"integer\", description: \"Number of adults.\" }\n },\n required: [\"city\", \"countryCode\", \"checkin\", \"checkout\", \"adults\"]\n }\n }\n }\n ];\n\n try {\n console.log(\"Sending to DeepSeek:\", JSON.stringify(messages, null, 2));\n // First call to DeepSeek: send conversation history and user message,\n // allowing the model to either respond directly or request a tool call.\n const deepSeekResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\", // Or \"deepseek-reasoner\"\n messages: messages,\n tools: tools,\n tool_choice: \"auto\",\n });\n\n const responseMessage = deepSeekResponse.choices[0].message;\n console.log(\"Received from DeepSeek:\", JSON.stringify(responseMessage, null, 2));\n\n if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {\n const toolCall = responseMessage.tool_calls[0];\n if (toolCall.function.name === \"get_hotel_availability\") {\n // DeepSeek requested to call our 'get_hotel_availability' function.\n // Extract arguments provided by DeepSeek.\n const args = JSON.parse(toolCall.function.arguments);\n console.log(\"DeepSeek requested tool call: get_hotel_availability with args:\", args);\n\n // Call our internal LITE API hotel search function.\n const hotelResults = await fetchHotelsAndRates(args);\n\n // Add DeepSeek's message (requesting tool call) to history\n messages.push(responseMessage);\n\n if (hotelResults.rates && hotelResults.rates.length > 0) {\n let hotelSummaryText = \"Okay, I found some hotels for you:\\n\";\n hotelResults.rates.slice(0, 3).forEach(rate => {\n hotelSummaryText += `- ${rate.hotel.name} (ID: ${rate.hotel.id})\\n`;\n });\n if (hotelResults.rates.length > 3) {\n hotelSummaryText += \"And a few more. Ask for details on any hotel by its ID or name if you're interested!\\n\";\n }\n\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n // Send summary for DeepSeek to use in its response, not the full hotel data\n content: JSON.stringify(hotelResults.rates.slice(0,3).map(r => ({id: r.hotel.id, name: r.hotel.name, city: r.hotel.city, country: r.hotel.countryCode })))\n });\n\n // Send the tool's execution results back to DeepSeek.\n // This allows DeepSeek to formulate a natural language response based on the hotel data found (or not found).\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup response:\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n // The final reply to the client comes from this follow-up response.\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, hotel_data: hotelResults.rates, is_hotel_list: true, history: messages });\n\n } else {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n content: JSON.stringify({error: \"No hotels found for the given criteria. You could try different dates or a different city.\"})\n });\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup (no hotels):\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, is_hotel_list: false, history: messages });\n }\n } else {\n // Unexpected tool called\n console.warn(\"DeepSeek called an unexpected tool:\", toolCall.function.name);\n messages.push(responseMessage); // Add DeepSeek's message\n // Fallback to a simple response without tool processing\n res.json({ reply: \"An unexpected tool was called by the assistant.\", is_hotel_list: false, history: messages });\n }\n } else {\n // No tool call, just a regular message from DeepSeek\n messages.push(responseMessage);\n res.json({ reply: responseMessage.content, is_hotel_list: false, history: messages });\n }\n } catch (error) {\n console.error(\"Error in /api/chatbot/converse:\", error);\n if (error.response) {\n console.error(\"DeepSeek API Error Response Status:\", error.status);\n console.error(\"DeepSeek API Error Response Headers:\", error.headers);\n console.error(\"DeepSeek API Error Response Data:\", error.error); // OpenAI Node SDK nests error details here\n res.status(error.status || 500).json({ error: \"Error calling DeepSeek API\", details: error.error });\n } else {\n res.status(500).json({ error: \"Internal server error while processing chat\", details: error.message });\n }\n }\n});\n\n// Blog generation API endpoint\napp.post(\"/api/generate-blog\", async (req, res) => {\n try {\n await generateAllArticles();\n res.json({ success: true, message: \"All blog articles and sitemap generated successfully\" });\n } catch (error) {\n console.error(\"Error generating blog articles:\", error);\n res.status(500).json({ \n error: \"Failed to generate blog articles\", \n details: error.message \n });\n }\n});\n\n/**\n * API endpoint to fetch hotel reviews from LITE API.\n * Expects a 'hotelId' query parameter.\n */\napp.get(\"/api/hotel-reviews\", async (req, res) => {\n const { hotelId } = req.query;\n console.log(`Fetching reviews for hotelId: ${hotelId}`);\n\n if (!hotelId) {\n return res.status(400).json({ error: \"hotelId is required\" });\n }\n\n const currentApiKey = process.env.PROD_API_KEY; // Using currentApiKey to avoid conflict with global apiKey\n if (!currentApiKey) {\n console.error(\"LITE_API_KEY (PROD_API_KEY) missing for reviews endpoint\");\n return res.status(500).json({ error: \"API key not configured on server.\" });\n }\n const sdk = liteApi(currentApiKey);\n\n try {\n // Assuming the sdk.getHotelReviews(hotelId, limit) maps to GET /data/reviews\n // Fetch up to 20 reviews.\n const reviewsResponse = await sdk.getHotelReviews(hotelId, 20);\n\n if (!reviewsResponse || !reviewsResponse.data) {\n console.warn(`No reviews found or invalid response for hotelId: ${hotelId}`, reviewsResponse);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.json({ data: reviewsResponse.data });\n } catch (error) {\n console.error(`Error fetching reviews for hotelId ${hotelId}:`, error);\n // Check if the error is from the LiteAPI SDK due to \"No Content\" or similar\n if (error.response && error.response.status === 204) {\n console.warn(`No reviews found (204 No Content) for hotelId: ${hotelId}`);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.status(500).json({ error: \"Failed to fetch hotel reviews\", details: error.message });\n }\n});\n\n// Blog posts API endpoint\napp.get(\"/api/blog-posts\", (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const htmlFiles = files.filter(file => file.endsWith('.html'));\n res.json(htmlFiles);\n } catch (error) {\n console.error(\"Error reading blog posts:\", error);\n res.status(500).json({ \n error: \"Failed to read blog posts\", \n details: error.message \n });\n }\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Catch-all route for SPA - THIS MUST BE LAST for client-side routing to work\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n\n// Generate blog posts for hotel locations\napp.post('/api/generate-hotel-locations', async (req, res) => {\n console.log('Starting hotel location blog generation...');\n\n const locations = [\n 'New York, NY', 'Los Angeles, CA', 'Chicago, IL', 'Houston, TX', 'Phoenix, AZ',\n // ... existing locations ...\n ];\n\n try {\n // Process cities in chunks of 10 for parallel processing\n const chunkSize = 10;\n for (let i = 0; i < locations.length; i += chunkSize) {\n const chunk = locations.slice(i, i + chunkSize);\n console.log(`Processing chunk ${i/chunkSize + 1} of ${Math.ceil(locations.length/chunkSize)}`);\n \n await Promise.all(chunk.map(async (location) => {\n console.log(`Generating article for: Hotels near ${location}`);\n \n const prompt = `Write a concise blog post about hotels in ${location}. Include:\n1. Popular hotel districts\n2. Average prices\n3. Types of hotels\n4. Nearby attractions\n5. Transportation options\n6. Best times to visit\n7. Tips for finding deals\n\nFormat in HTML with headings and lists.`;\n\n const response = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: prompt }],\n temperature: 0.5,\n max_tokens: 1000\n });\n\n const content = response.choices[0].message.content;\n const fileName = `hotels-near-${location.toLowerCase().replace(/,?\\s+/g, '-')}.html`;\n \n const htmlContent = generateHTML({\n title: `Hotels Near ${location}`,\n slug: `hotels-near-${location.toLowerCase().replace(/,?\\s+/g, '-')}`,\n keywords: ['hotels', location, 'travel']\n }, content);\n\n fs.writeFileSync(path.join(__dirname, '../client/blog', fileName), htmlContent);\n console.log(`Saved article: ${fileName}`);\n }));\n }\n\n res.json({ message: 'Hotel location blog posts generated successfully' });\n } catch (error) {\n console.error('Error generating hotel location blog posts:', error);\n res.status(500).json({ error: 'Failed to generate hotel location blog posts' });\n }\n});\n"}, "59b282682b3ba8cbc2f6ef8206be3c7f79089566": {"t": 1785766809.9333723, "n": 27649, "h": "805ebc805d8a295d", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nconst fs = require(\"fs\");\n// Load environment variables from .env file\n// Ensure you have a .env file in the root directory with:\n// PROD_API_KEY=your_lite_api_key_here\n// DEEPSEEK_API_KEY=your_deepseek_api_key_here\n// FRONTEND_URL=http://localhost:your_frontend_port (if applicable for CORS in dev)\nrequire(\"dotenv\").config();\nconsole.log(\"Environment variables loaded:\", {\n DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY ? \"Present\" : \"Missing\",\n PROD_API_KEY: process.env.PROD_API_KEY ? \"Present\" : \"Missing\",\n FRONTEND_URL: process.env.FRONTEND_URL || \"Not set\",\n BASE_URL: process.env.BASE_URL || \"Not set\"\n});\nconst { sendBookingConfirmation } = require('./email');\nconst { generateAllArticles, generateArticle, blogTopics, generateHTML } = require('./blog-generator');\nconst OpenAI = require('openai');\n\n// Initialize DeepSeek OpenAI client\nconst deepSeekApiKey = process.env.DEEPSEEK_API_KEY;\nlet openaiClient;\nif (deepSeekApiKey) {\n openaiClient = new OpenAI({\n apiKey: deepSeekApiKey,\n baseURL: 'https://api.deepseek.com/v1', // Or 'https://api.deepseek.com'\n });\n console.log(\"DeepSeek OpenAI client initialized.\");\n} else {\n console.warn(\"DEEPSEEK_API_KEY not found in .env. DeepSeek client not initialized.\");\n}\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\n// Add blog posts endpoint\napp.get('/api/blog-posts', (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const blogPosts = files.filter(file => file.endsWith('.html'));\n res.json(blogPosts);\n } catch (error) {\n console.error('Error reading blog posts:', error);\n res.status(500).json({ error: 'Failed to load blog posts' });\n }\n});\n\n// Serve individual blog posts\napp.get('/blog/:post', (req, res) => {\n const postFile = req.params.post;\n const blogDir = path.join(__dirname, '../client/blog');\n const postPath = path.join(blogDir, postFile);\n \n if (fs.existsSync(postPath)) {\n res.sendFile(postPath);\n } else {\n res.status(404).send('Blog post not found');\n }\n});\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\n\n/**\n * Fetches hotel list from LITE API based on city/country,\n * then gets full rates for these hotels based on checkin, checkout, and adults.\n * @param {object} searchParams - Contains city, countryCode, checkin, checkout, adults.\n * @returns {Promise<object>} Object containing rates array or empty if not found/error.\n */\nasync function fetchHotelsAndRates(searchParams) {\n const { city, countryCode, checkin, checkout, adults } = searchParams;\n console.log(\"Refactored search: Searching hotels with params:\", { countryCode, city, checkin, checkout, adults });\n const apiKey = process.env.PROD_API_KEY; // Ensure this is accessible\n if (!apiKey) {\n throw new Error(\"LITE_API_KEY (PROD_API_KEY) missing\");\n }\n const sdk = liteApi(apiKey);\n\n try {\n const hotelListResponse = await sdk.getHotels(countryCode, city, 0, 10); // Limit to 10 for now\n if (!hotelListResponse || !hotelListResponse.data || !Array.isArray(hotelListResponse.data)) {\n console.error(\"Invalid hotel list response from LITE API:\", hotelListResponse);\n return { rates: [] }; // Or throw specific error\n }\n const hotelsData = hotelListResponse.data;\n if (hotelsData.length === 0) {\n console.log(\"No hotels found by LITE API for the given criteria.\");\n return { rates: [] };\n }\n const hotelIds = hotelsData.map((hotel) => hotel.id);\n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\", // Or make configurable\n guestNationality: \"US\", // Or make configurable\n checkin: checkin,\n checkout: checkout,\n });\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response from LITE API:\", ratesResponse);\n return { rates: [] }; // Or throw specific error\n }\n const ratesData = ratesResponse.data;\n ratesData.forEach((rate) => {\n rate.hotel = hotelsData.find((hotel) => hotel.id === rate.hotelId);\n });\n return { rates: ratesData };\n } catch (error) {\n console.error(\"Error in fetchHotelsAndRates:\", error);\n throw error; // Re-throw to be caught by caller\n }\n}\n\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit via GET\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n try {\n const result = await fetchHotelsAndRates({ city, countryCode, checkin, checkout, adults });\n res.json(result);\n } catch (error) {\n console.error(\"Error in /api/search-hotels GET endpoint:\", error);\n res.status(500).json({ error: \"Internal server error\", details: error.message });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve sitemap.xml with correct content type\napp.get('/sitemap.xml', (req, res) => {\n res.type('application/xml');\n res.sendFile(path.join(__dirname, '../client/sitemap.xml'));\n});\n\n// New Chatbot API endpoint\napp.post(\"/api/chatbot/converse\", async (req, res) => {\n const { message, history = [] } = req.body;\n\n if (!openaiClient) {\n return res.status(500).json({ reply: \"Chatbot is not configured (missing API key).\" });\n }\n\n // System prompt to guide the DeepSeek model\n // It instructs the model on its role, required parameters for hotel search,\n // and how/when to use the 'get_hotel_availability' function.\n const messages = [\n {\n role: \"system\",\n content: \"You are a helpful assistant for booking hotels. Your goal is to gather necessary information (city, countryCode, check-in date, check-out date, and number of adults) from the user to search for hotels. If any of these details are missing, ask the user for them. Once you have all details, use the 'get_hotel_availability' function. Do not make up information if the user asks for something you don't know; instead, say you don't know or ask them to clarify. Dates should be in YYYY-MM-DD format.\"\n },\n ...history, // Spread previous messages\n { role: \"user\", content: message }\n ];\n\n // Define the tool(s) that the DeepSeek model can request to call.\n // 'get_hotel_availability' allows the model to ask our server to fetch hotel data.\n const tools = [\n {\n type: \"function\",\n function: {\n name: \"get_hotel_availability\",\n description: \"Get hotel availability and rates based on user criteria.\",\n parameters: {\n type: \"object\",\n properties: {\n city: { type: \"string\", description: \"The city for the hotel search, e.g., London\" },\n countryCode: { type: \"string\", description: \"The ISO-2 country code for the city, e.g., GB for United Kingdom. Infer this if possible from the city or ask the user.\" },\n checkin: { type: \"string\", description: \"Check-in date in YYYY-MM-DD format.\" },\n checkout: { type: \"string\", description: \"Check-out date in YYYY-MM-DD format.\" },\n adults: { type: \"integer\", description: \"Number of adults.\" }\n },\n required: [\"city\", \"countryCode\", \"checkin\", \"checkout\", \"adults\"]\n }\n }\n }\n ];\n\n try {\n console.log(\"Sending to DeepSeek:\", JSON.stringify(messages, null, 2));\n // First call to DeepSeek: send conversation history and user message,\n // allowing the model to either respond directly or request a tool call.\n const deepSeekResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\", // Or \"deepseek-reasoner\"\n messages: messages,\n tools: tools,\n tool_choice: \"auto\",\n });\n\n const responseMessage = deepSeekResponse.choices[0].message;\n console.log(\"Received from DeepSeek:\", JSON.stringify(responseMessage, null, 2));\n\n if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {\n const toolCall = responseMessage.tool_calls[0];\n if (toolCall.function.name === \"get_hotel_availability\") {\n // DeepSeek requested to call our 'get_hotel_availability' function.\n // Extract arguments provided by DeepSeek.\n const args = JSON.parse(toolCall.function.arguments);\n console.log(\"DeepSeek requested tool call: get_hotel_availability with args:\", args);\n\n // Call our internal LITE API hotel search function.\n const hotelResults = await fetchHotelsAndRates(args);\n\n // Add DeepSeek's message (requesting tool call) to history\n messages.push(responseMessage);\n\n if (hotelResults.rates && hotelResults.rates.length > 0) {\n let hotelSummaryText = \"Okay, I found some hotels for you:\\n\";\n hotelResults.rates.slice(0, 3).forEach(rate => {\n hotelSummaryText += `- ${rate.hotel.name} (ID: ${rate.hotel.id})\\n`;\n });\n if (hotelResults.rates.length > 3) {\n hotelSummaryText += \"And a few more. Ask for details on any hotel by its ID or name if you're interested!\\n\";\n }\n\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n // Send summary for DeepSeek to use in its response, not the full hotel data\n content: JSON.stringify(hotelResults.rates.slice(0,3).map(r => ({id: r.hotel.id, name: r.hotel.name, city: r.hotel.city, country: r.hotel.countryCode })))\n });\n\n // Send the tool's execution results back to DeepSeek.\n // This allows DeepSeek to formulate a natural language response based on the hotel data found (or not found).\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup response:\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n // The final reply to the client comes from this follow-up response.\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, hotel_data: hotelResults.rates, is_hotel_list: true, history: messages });\n\n } else {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n content: JSON.stringify({error: \"No hotels found for the given criteria. You could try different dates or a different city.\"})\n });\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup (no hotels):\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, is_hotel_list: false, history: messages });\n }\n } else {\n // Unexpected tool called\n console.warn(\"DeepSeek called an unexpected tool:\", toolCall.function.name);\n messages.push(responseMessage); // Add DeepSeek's message\n // Fallback to a simple response without tool processing\n res.json({ reply: \"An unexpected tool was called by the assistant.\", is_hotel_list: false, history: messages });\n }\n } else {\n // No tool call, just a regular message from DeepSeek\n messages.push(responseMessage);\n res.json({ reply: responseMessage.content, is_hotel_list: false, history: messages });\n }\n } catch (error) {\n console.error(\"Error in /api/chatbot/converse:\", error);\n if (error.response) {\n console.error(\"DeepSeek API Error Response Status:\", error.status);\n console.error(\"DeepSeek API Error Response Headers:\", error.headers);\n console.error(\"DeepSeek API Error Response Data:\", error.error); // OpenAI Node SDK nests error details here\n res.status(error.status || 500).json({ error: \"Error calling DeepSeek API\", details: error.error });\n } else {\n res.status(500).json({ error: \"Internal server error while processing chat\", details: error.message });\n }\n }\n});\n\n// Blog generation API endpoint\napp.post(\"/api/generate-blog\", async (req, res) => {\n try {\n await generateAllArticles();\n res.json({ success: true, message: \"All blog articles and sitemap generated successfully\" });\n } catch (error) {\n console.error(\"Error generating blog articles:\", error);\n res.status(500).json({ \n error: \"Failed to generate blog articles\", \n details: error.message \n });\n }\n});\n\n/**\n * API endpoint to fetch hotel reviews from LITE API.\n * Expects a 'hotelId' query parameter.\n */\napp.get(\"/api/hotel-reviews\", async (req, res) => {\n const { hotelId } = req.query;\n console.log(`Fetching reviews for hotelId: ${hotelId}`);\n\n if (!hotelId) {\n return res.status(400).json({ error: \"hotelId is required\" });\n }\n\n const currentApiKey = process.env.PROD_API_KEY; // Using currentApiKey to avoid conflict with global apiKey\n if (!currentApiKey) {\n console.error(\"LITE_API_KEY (PROD_API_KEY) missing for reviews endpoint\");\n return res.status(500).json({ error: \"API key not configured on server.\" });\n }\n const sdk = liteApi(currentApiKey);\n\n try {\n // Assuming the sdk.getHotelReviews(hotelId, limit) maps to GET /data/reviews\n // Fetch up to 20 reviews.\n const reviewsResponse = await sdk.getHotelReviews(hotelId, 20);\n\n if (!reviewsResponse || !reviewsResponse.data) {\n console.warn(`No reviews found or invalid response for hotelId: ${hotelId}`, reviewsResponse);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.json({ data: reviewsResponse.data });\n } catch (error) {\n console.error(`Error fetching reviews for hotelId ${hotelId}:`, error);\n // Check if the error is from the LiteAPI SDK due to \"No Content\" or similar\n if (error.response && error.response.status === 204) {\n console.warn(`No reviews found (204 No Content) for hotelId: ${hotelId}`);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.status(500).json({ error: \"Failed to fetch hotel reviews\", details: error.message });\n }\n});\n\n// Blog posts API endpoint\napp.get(\"/api/blog-posts\", (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const htmlFiles = files.filter(file => file.endsWith('.html'));\n res.json(htmlFiles);\n } catch (error) {\n console.error(\"Error reading blog posts:\", error);\n res.status(500).json({ \n error: \"Failed to read blog posts\", \n details: error.message \n });\n }\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Catch-all route for SPA - THIS MUST BE LAST for client-side routing to work\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n\n// Generate blog posts for hotel locations\napp.post('/api/generate-hotel-locations', async (req, res) => {\n console.log('Starting hotel location blog generation...');\n\n const locations = [\n 'New York, NY', 'Los Angeles, CA', 'Chicago, IL', 'Houston, TX', 'Phoenix, AZ',\n // ... existing locations ...\n ];\n\n try {\n // Process cities in chunks of 10 for parallel processing\n const chunkSize = 10;\n for (let i = 0; i < locations.length; i += chunkSize) {\n const chunk = locations.slice(i, i + chunkSize);\n console.log(`Processing chunk ${i/chunkSize + 1} of ${Math.ceil(locations.length/chunkSize)}`);\n \n await Promise.all(chunk.map(async (location) => {\n console.log(`Generating article for: Hotels near ${location}`);\n \n const prompt = `Write a concise blog post about hotels in ${location}. Include:\n1. Popular hotel districts\n2. Average prices\n3. Types of hotels\n4. Nearby attractions\n5. Transportation options\n6. Best times to visit\n7. Tips for finding deals\n\nFormat in HTML with headings and lists.`;\n\n const response = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: prompt }],\n temperature: 0.5,\n max_tokens: 1000\n });\n\n const content = response.choices[0].message.content;\n const fileName = `hotels-near-${location.toLowerCase().replace(/,?\\s+/g, '-')}.html`;\n \n const htmlContent = generateHTML({\n title: `Hotels Near ${location}`,\n slug: `hotels-near-${location.toLowerCase().replace(/,?\\s+/g, '-')}`,\n keywords: ['hotels', location, 'travel']\n }, content);\n\n fs.writeFileSync(path.join(__dirname, '../client/blog', fileName), htmlContent);\n console.log(`Saved article: ${fileName}`);\n }));\n }\n\n res.json({ message: 'Hotel location blog posts generated successfully' });\n } catch (error) {\n console.error('Error generating hotel location blog posts:', error);\n res.status(500).json({ error: 'Failed to generate hotel location blog posts' });\n }\n});\n"}, "b6ae40edde64ce980e56e50ec285de1ed2bf04b1": {"t": 1785766812.4847574, "n": 37596, "h": "f886d5ce36230987", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nconst fs = require(\"fs\");\n// Load environment variables from .env file\n// Ensure you have a .env file in the root directory with:\n// PROD_API_KEY=your_lite_api_key_here\n// DEEPSEEK_API_KEY=your_deepseek_api_key_here\n// FRONTEND_URL=http://localhost:your_frontend_port (if applicable for CORS in dev)\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\nconst { generateAllArticles } = require('./blog-generator');\nconst OpenAI = require('openai');\n\n// Initialize DeepSeek OpenAI client\nconst deepSeekApiKey = process.env.DEEPSEEK_API_KEY;\nlet openaiClient;\nif (deepSeekApiKey) {\n openaiClient = new OpenAI({\n apiKey: deepSeekApiKey,\n baseURL: 'https://api.deepseek.com/v1', // Or 'https://api.deepseek.com'\n });\n console.log(\"DeepSeek OpenAI client initialized.\");\n} else {\n console.warn(\"DEEPSEEK_API_KEY not found in .env. DeepSeek client not initialized.\");\n}\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\n// Add blog posts endpoint\napp.get('/api/blog-posts', (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const blogPosts = files.filter(file => file.endsWith('.html'));\n res.json(blogPosts);\n } catch (error) {\n console.error('Error reading blog posts:', error);\n res.status(500).json({ error: 'Failed to load blog posts' });\n }\n});\n\n// Serve individual blog posts\napp.get('/blog/:post', (req, res) => {\n const postFile = req.params.post;\n const blogDir = path.join(__dirname, '../client/blog');\n const postPath = path.join(blogDir, postFile);\n \n if (fs.existsSync(postPath)) {\n res.sendFile(postPath);\n } else {\n res.status(404).send('Blog post not found');\n }\n});\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\n\n/**\n * Fetches hotel list from LITE API based on city/country,\n * then gets full rates for these hotels based on checkin, checkout, and adults.\n * @param {object} searchParams - Contains city, countryCode, checkin, checkout, adults.\n * @returns {Promise<object>} Object containing rates array or empty if not found/error.\n */\nasync function fetchHotelsAndRates(searchParams) {\n const { city, countryCode, checkin, checkout, adults } = searchParams;\n console.log(\"Refactored search: Searching hotels with params:\", { countryCode, city, checkin, checkout, adults });\n const apiKey = process.env.PROD_API_KEY; // Ensure this is accessible\n if (!apiKey) {\n throw new Error(\"LITE_API_KEY (PROD_API_KEY) missing\");\n }\n const sdk = liteApi(apiKey);\n\n try {\n const hotelListResponse = await sdk.getHotels(countryCode, city, 0, 10); // Limit to 10 for now\n if (!hotelListResponse || !hotelListResponse.data || !Array.isArray(hotelListResponse.data)) {\n console.error(\"Invalid hotel list response from LITE API:\", hotelListResponse);\n return { rates: [] }; // Or throw specific error\n }\n const hotelsData = hotelListResponse.data;\n if (hotelsData.length === 0) {\n console.log(\"No hotels found by LITE API for the given criteria.\");\n return { rates: [] };\n }\n const hotelIds = hotelsData.map((hotel) => hotel.id);\n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\", // Or make configurable\n guestNationality: \"US\", // Or make configurable\n checkin: checkin,\n checkout: checkout,\n });\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response from LITE API:\", ratesResponse);\n return { rates: [] }; // Or throw specific error\n }\n const ratesData = ratesResponse.data;\n ratesData.forEach((rate) => {\n rate.hotel = hotelsData.find((hotel) => hotel.id === rate.hotelId);\n });\n return { rates: ratesData };\n } catch (error) {\n console.error(\"Error in fetchHotelsAndRates:\", error);\n throw error; // Re-throw to be caught by caller\n }\n}\n\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit via GET\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n try {\n const result = await fetchHotelsAndRates({ city, countryCode, checkin, checkout, adults });\n res.json(result);\n } catch (error) {\n console.error(\"Error in /api/search-hotels GET endpoint:\", error);\n res.status(500).json({ error: \"Internal server error\", details: error.message });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve sitemap.xml with correct content type\napp.get('/sitemap.xml', (req, res) => {\n res.type('application/xml');\n res.sendFile(path.join(__dirname, '../client/sitemap.xml'));\n});\n\n// New Chatbot API endpoint\napp.post(\"/api/chatbot/converse\", async (req, res) => {\n const { message, history = [] } = req.body;\n\n if (!openaiClient) {\n return res.status(500).json({ reply: \"Chatbot is not configured (missing API key).\" });\n }\n\n // System prompt to guide the DeepSeek model\n // It instructs the model on its role, required parameters for hotel search,\n // and how/when to use the 'get_hotel_availability' function.\n const messages = [\n {\n role: \"system\",\n content: \"You are a helpful assistant for booking hotels. Your goal is to gather necessary information (city, countryCode, check-in date, check-out date, and number of adults) from the user to search for hotels. If any of these details are missing, ask the user for them. Once you have all details, use the 'get_hotel_availability' function. Do not make up information if the user asks for something you don't know; instead, say you don't know or ask them to clarify. Dates should be in YYYY-MM-DD format.\"\n },\n ...history, // Spread previous messages\n { role: \"user\", content: message }\n ];\n\n // Define the tool(s) that the DeepSeek model can request to call.\n // 'get_hotel_availability' allows the model to ask our server to fetch hotel data.\n const tools = [\n {\n type: \"function\",\n function: {\n name: \"get_hotel_availability\",\n description: \"Get hotel availability and rates based on user criteria.\",\n parameters: {\n type: \"object\",\n properties: {\n city: { type: \"string\", description: \"The city for the hotel search, e.g., London\" },\n countryCode: { type: \"string\", description: \"The ISO-2 country code for the city, e.g., GB for United Kingdom. Infer this if possible from the city or ask the user.\" },\n checkin: { type: \"string\", description: \"Check-in date in YYYY-MM-DD format.\" },\n checkout: { type: \"string\", description: \"Check-out date in YYYY-MM-DD format.\" },\n adults: { type: \"integer\", description: \"Number of adults.\" }\n },\n required: [\"city\", \"countryCode\", \"checkin\", \"checkout\", \"adults\"]\n }\n }\n }\n ];\n\n try {\n console.log(\"Sending to DeepSeek:\", JSON.stringify(messages, null, 2));\n // First call to DeepSeek: send conversation history and user message,\n // allowing the model to either respond directly or request a tool call.\n const deepSeekResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\", // Or \"deepseek-reasoner\"\n messages: messages,\n tools: tools,\n tool_choice: \"auto\",\n });\n\n const responseMessage = deepSeekResponse.choices[0].message;\n console.log(\"Received from DeepSeek:\", JSON.stringify(responseMessage, null, 2));\n\n if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {\n const toolCall = responseMessage.tool_calls[0];\n if (toolCall.function.name === \"get_hotel_availability\") {\n // DeepSeek requested to call our 'get_hotel_availability' function.\n // Extract arguments provided by DeepSeek.\n const args = JSON.parse(toolCall.function.arguments);\n console.log(\"DeepSeek requested tool call: get_hotel_availability with args:\", args);\n\n // Call our internal LITE API hotel search function.\n const hotelResults = await fetchHotelsAndRates(args);\n\n // Add DeepSeek's message (requesting tool call) to history\n messages.push(responseMessage);\n\n if (hotelResults.rates && hotelResults.rates.length > 0) {\n let hotelSummaryText = \"Okay, I found some hotels for you:\\n\";\n hotelResults.rates.slice(0, 3).forEach(rate => {\n hotelSummaryText += `- ${rate.hotel.name} (ID: ${rate.hotel.id})\\n`;\n });\n if (hotelResults.rates.length > 3) {\n hotelSummaryText += \"And a few more. Ask for details on any hotel by its ID or name if you're interested!\\n\";\n }\n\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n // Send summary for DeepSeek to use in its response, not the full hotel data\n content: JSON.stringify(hotelResults.rates.slice(0,3).map(r => ({id: r.hotel.id, name: r.hotel.name, city: r.hotel.city, country: r.hotel.countryCode })))\n });\n\n // Send the tool's execution results back to DeepSeek.\n // This allows DeepSeek to formulate a natural language response based on the hotel data found (or not found).\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup response:\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n // The final reply to the client comes from this follow-up response.\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, hotel_data: hotelResults.rates, is_hotel_list: true, history: messages });\n\n } else {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n content: JSON.stringify({error: \"No hotels found for the given criteria. You could try different dates or a different city.\"})\n });\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup (no hotels):\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, is_hotel_list: false, history: messages });\n }\n } else {\n // Unexpected tool called\n console.warn(\"DeepSeek called an unexpected tool:\", toolCall.function.name);\n messages.push(responseMessage); // Add DeepSeek's message\n // Fallback to a simple response without tool processing\n res.json({ reply: \"An unexpected tool was called by the assistant.\", is_hotel_list: false, history: messages });\n }\n } else {\n // No tool call, just a regular message from DeepSeek\n messages.push(responseMessage);\n res.json({ reply: responseMessage.content, is_hotel_list: false, history: messages });\n }\n } catch (error) {\n console.error(\"Error in /api/chatbot/converse:\", error);\n if (error.response) {\n console.error(\"DeepSeek API Error Response Status:\", error.status);\n console.error(\"DeepSeek API Error Response Headers:\", error.headers);\n console.error(\"DeepSeek API Error Response Data:\", error.error); // OpenAI Node SDK nests error details here\n res.status(error.status || 500).json({ error: \"Error calling DeepSeek API\", details: error.error });\n } else {\n res.status(500).json({ error: \"Internal server error while processing chat\", details: error.message });\n }\n }\n});\n\n// Blog generation API endpoint\napp.post(\"/api/generate-blog\", async (req, res) => {\n try {\n await generateAllArticles();\n res.json({ success: true, message: \"All blog articles and sitemap generated successfully\" });\n } catch (error) {\n console.error(\"Error generating blog articles:\", error);\n res.status(500).json({ \n error: \"Failed to generate blog articles\", \n details: error.message \n });\n }\n});\n\n/**\n * API endpoint to fetch hotel reviews from LITE API.\n * Expects a 'hotelId' query parameter.\n */\napp.get(\"/api/hotel-reviews\", async (req, res) => {\n const { hotelId } = req.query;\n console.log(`Fetching reviews for hotelId: ${hotelId}`);\n\n if (!hotelId) {\n return res.status(400).json({ error: \"hotelId is required\" });\n }\n\n const currentApiKey = process.env.PROD_API_KEY; // Using currentApiKey to avoid conflict with global apiKey\n if (!currentApiKey) {\n console.error(\"LITE_API_KEY (PROD_API_KEY) missing for reviews endpoint\");\n return res.status(500).json({ error: \"API key not configured on server.\" });\n }\n const sdk = liteApi(currentApiKey);\n\n try {\n // Assuming the sdk.getHotelReviews(hotelId, limit) maps to GET /data/reviews\n // Fetch up to 20 reviews.\n const reviewsResponse = await sdk.getHotelReviews(hotelId, 20);\n\n if (!reviewsResponse || !reviewsResponse.data) {\n console.warn(`No reviews found or invalid response for hotelId: ${hotelId}`, reviewsResponse);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.json({ data: reviewsResponse.data });\n } catch (error) {\n console.error(`Error fetching reviews for hotelId ${hotelId}:`, error);\n // Check if the error is from the LiteAPI SDK due to \"No Content\" or similar\n if (error.response && error.response.status === 204) {\n console.warn(`No reviews found (204 No Content) for hotelId: ${hotelId}`);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.status(500).json({ error: \"Failed to fetch hotel reviews\", details: error.message });\n }\n});\n\n// Blog posts API endpoint\napp.get(\"/api/blog-posts\", (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const htmlFiles = files.filter(file => file.endsWith('.html'));\n res.json(htmlFiles);\n } catch (error) {\n console.error(\"Error reading blog posts:\", error);\n res.status(500).json({ \n error: \"Failed to read blog posts\", \n details: error.message \n });\n }\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Catch-all route for SPA - THIS MUST BE LAST for client-side routing to work\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n\n// Generate blog posts for hotel locations\napp.post('/api/generate-hotel-locations', async (req, res) => {\n console.log('Starting hotel location blog generation...');\n \n const locations = [\n 'New York, NY', 'Los Angeles, CA', 'Chicago, IL', 'Houston, TX', 'Phoenix, AZ',\n 'Philadelphia, PA', 'San Antonio, TX', 'San Diego, CA', 'Dallas, TX', 'San Jose, CA',\n 'Austin, TX', 'Jacksonville, FL', 'San Francisco, CA', 'Columbus, OH', 'Indianapolis, IN',\n 'Fort Worth, TX', 'Charlotte, NC', 'Seattle, WA', 'Denver, CO', 'Washington, DC',\n 'Alexandria, VA', 'Fairfax, VA', 'Herndon, VA', 'Rockville, MD', 'Richmond, VA',\n 'Boston, MA', 'Nashville, TN', 'El Paso, TX', 'Detroit, MI', 'Memphis, TN',\n 'Portland, OR', 'Oklahoma City, OK', 'Las Vegas, NV', 'Louisville, KY', 'Baltimore, MD',\n 'Milwaukee, WI', 'Albuquerque, NM', 'Tucson, AZ', 'Fresno, CA', 'Sacramento, CA',\n 'Kansas City, MO', 'Mesa, AZ', 'Atlanta, GA', 'Long Beach, CA', 'Raleigh, NC',\n 'Miami, FL', 'Virginia Beach, VA', 'Omaha, NE', 'Oakland, CA', 'Minneapolis, MN',\n 'Tulsa, OK', 'Arlington, TX', 'Tampa, FL', 'New Orleans, LA', 'Cleveland, OH',\n 'Honolulu, HI', 'Anaheim, CA', 'Lexington, KY', 'Stockton, CA', 'Corpus Christi, TX',\n 'Henderson, NV', 'Riverside, CA', 'Newark, NJ', 'Saint Paul, MN', 'Santa Ana, CA',\n 'Cincinnati, OH', 'Irvine, CA', 'Orlando, FL', 'Pittsburgh, PA', 'St. Louis, MO',\n 'Greensboro, NC', 'Jersey City, NJ', 'Anchorage, AK', 'Lincoln, NE', 'Plano, TX',\n 'Durham, NC', 'Buffalo, NY', 'Chandler, AZ', 'Chula Vista, CA', 'Toledo, OH',\n 'Madison, WI', 'Gilbert, AZ', 'Reno, NV', 'Fort Wayne, IN', 'North Las Vegas, NV',\n 'St. Petersburg, FL', 'Lubbock, TX', 'Irving, TX', 'Laredo, TX', 'Winston-Salem, NC',\n 'Chesapeake, VA', 'Glendale, AZ', 'Garland, TX', 'Scottsdale, AZ', 'Norfolk, VA',\n 'Boise, ID', 'Fremont, CA', 'Spokane, WA', 'Montgomery, AL', 'Yonkers, NY',\n 'Augusta, GA', 'Baton Rouge, LA', 'Des Moines, IA', 'Grand Rapids, MI', 'Salt Lake City, UT',\n 'Birmingham, AL', 'Huntsville, AL', 'Mobile, AL', 'Little Rock, AR', 'Providence, RI',\n 'Sioux Falls, SD', 'Savannah, GA', 'Chattanooga, TN', 'Jackson, MS', 'Fort Lauderdale, FL',\n 'Burlington, VT', 'Fargo, ND', 'Columbia, SC', 'Wilmington, NC', 'Albany, NY',\n 'Hartford, CT', 'Springfield, MO', 'Rochester, NY', 'Tallahassee, FL', 'Asheville, NC',\n 'Knoxville, TN', 'Dayton, OH', 'Syracuse, NY', 'Worcester, MA',\n 'San Juan, PR', 'Ponce, PR', 'Mayaguez, PR', 'Arecibo, PR', 'Caguas, PR',\n 'Fajardo, PR', 'Guaynabo, PR', 'Humacao, PR', 'Bayamon, PR', 'Carolina, PR',\n 'Anchorage, AK', 'Fairbanks, AK', 'Juneau, AK', 'Sitka, AK', 'Kodiak, AK',\n 'Bethel, AK', 'Ketchikan, AK', 'Palmer, AK', 'Kenai, AK', 'Kodiak, AK',\n 'Honolulu, HI', 'Pearl City, HI', 'Hilo, HI', 'Kailua, HI', 'Waipahu, HI',\n 'Mililani Town, HI', 'Kaneohe, HI', 'Ewa Gentry, HI', 'Kihei, HI', 'Makakilo, HI',\n 'Las Vegas, NV', 'Reno, NV', 'Henderson, NV', 'North Las Vegas, NV', 'Sparks, NV',\n 'Carson City, NV', 'Fernley, NV', 'Elko, NV', 'Mesquite, NV', 'Boulder City, NV',\n 'Phoenix, AZ', 'Tucson, AZ', 'Mesa, AZ', 'Scottsdale, AZ', 'Glendale, AZ',\n 'Chandler, AZ', 'Gilbert, AZ', 'Tempe, AZ', 'Peoria, AZ', 'Surprise, AZ',\n 'San Diego, CA', 'Los Angeles, CA', 'San Jose, CA', 'San Francisco, CA', 'Fresno, CA',\n 'Sacramento, CA', 'Long Beach, CA', 'Oakland, CA', 'Anaheim, CA', 'Santa Ana, CA',\n 'Miami, FL', 'Orlando, FL', 'Tampa, FL', 'St. Petersburg, FL', 'Hialeah, FL',\n 'Fort Lauderdale, FL', 'Port St. Lucie, FL', 'Cape Coral, FL', 'Gainesville, FL', 'Palm Bay, FL',\n 'New York, NY', 'Buffalo, NY', 'Rochester, NY', 'Yonkers, NY', 'Syracuse, NY',\n 'Albany, NY', 'New Rochelle, NY', 'Mount Vernon, NY', 'Schenectady, NY', 'Utica, NY',\n 'Chicago, IL', 'Aurora, IL', 'Rockford, IL', 'Joliet, IL', 'Naperville, IL',\n 'Springfield, IL', 'Peoria, IL', 'Elgin, IL', 'Waukegan, IL', 'Champaign, IL',\n 'Houston, TX', 'San Antonio, TX', 'Dallas, TX', 'Austin, TX', 'Fort Worth, TX',\n 'El Paso, TX', 'Arlington, TX', 'Corpus Christi, TX', 'Plano, TX', 'Laredo, TX',\n 'Seattle, WA', 'Spokane, WA', 'Tacoma, WA', 'Vancouver, WA', 'Bellevue, WA',\n 'Kent, WA', 'Everett, WA', 'Renton, WA', 'Yakima, WA', 'Federal Way, WA',\n 'Portland, OR', 'Salem, OR', 'Eugene, OR', 'Gresham, OR', 'Hillsboro, OR',\n 'Beaverton, OR', 'Bend, OR', 'Medford, OR', 'Springfield, OR', 'Corvallis, OR',\n 'Denver, CO', 'Colorado Springs, CO', 'Aurora, CO', 'Fort Collins, CO', 'Lakewood, CO',\n 'Thornton, CO', 'Arvada, CO', 'Westminster, CO', 'Pueblo, CO', 'Boulder, CO',\n 'Minneapolis, MN', 'St. Paul, MN', 'Rochester, MN', 'Duluth, MN', 'Bloomington, MN',\n 'Brooklyn Park, MN', 'Plymouth, MN', 'St. Cloud, MN', 'Eagan, MN', 'Woodbury, MN',\n 'Detroit, MI', 'Grand Rapids, MI', 'Warren, MI', 'Sterling Heights, MI', 'Ann Arbor, MI',\n 'Lansing, MI', 'Flint, MI', 'Dearborn, MI', 'Livonia, MI', 'Westland, MI',\n 'Boston, MA', 'Worcester, MA', 'Springfield, MA', 'Cambridge, MA', 'Lowell, MA',\n 'Brockton, MA', 'New Bedford, MA', 'Quincy, MA', 'Lynn, MA', 'Fall River, MA',\n 'Philadelphia, PA', 'Pittsburgh, PA', 'Allentown, PA', 'Erie, PA', 'Reading, PA',\n 'Scranton, PA', 'Bethlehem, PA', 'Lancaster, PA', 'Harrisburg, PA', 'Altoona, PA',\n 'Baltimore, MD', 'Rockville, MD', 'Germantown, MD', 'Bowie, MD', 'Hagerstown, MD',\n 'Frederick, MD', 'Gaithersburg, MD', 'Bethesda, MD', 'Ellicott City, MD', 'Columbia, MD',\n 'Washington, DC', 'Arlington, VA', 'Alexandria, VA', 'Fairfax, VA', 'Richmond, VA',\n 'Virginia Beach, VA', 'Norfolk, VA', 'Chesapeake, VA', 'Newport News, VA', 'Hampton, VA'\n ];\n\n try {\n // Process cities in chunks of 5 for parallel processing\n const chunkSize = 5;\n for (let i = 0; i < locations.length; i += chunkSize) {\n const chunk = locations.slice(i, i + chunkSize);\n console.log(`Processing chunk ${i/chunkSize + 1} of ${Math.ceil(locations.length/chunkSize)}`);\n \n await Promise.all(chunk.map(async (location) => {\n console.log(`Generating article for: Hotels near ${location}`);\n \n const prompt = `Write a comprehensive blog post about hotels in ${location}. Include information about:\n1. Popular hotel districts and neighborhoods\n2. Average hotel prices and seasonal variations\n3. Types of hotels available (luxury, budget, boutique, etc.)\n4. Nearby attractions and points of interest\n5. Transportation options and accessibility\n6. Best times to visit\n7. Special considerations for travelers\n8. Tips for finding the best deals\n9. Local amenities and services\n10. Safety and security information\n\nFormat the content in HTML with proper headings, paragraphs, and lists.`;\n\n const response = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: prompt }],\n temperature: 0.7,\n max_tokens: 2000\n });\n\n const content = response.choices[0].message.content;\n const fileName = `hotels-near-${location.toLowerCase().replace(/,?\\s+/g, '-')}.html`;\n \n const htmlContent = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Hotels Near ${location} - Complete Guide</title>\n <link rel=\"stylesheet\" href=\"/styles.css\">\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css\">\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap\" rel=\"stylesheet\">\n</head>\n<body>\n <header>\n <nav>\n <a href=\"/\" class=\"header-logo\">\n <img src=\"/assets/aureavibe-logo.png\" alt=\"AureaVibe Logo\" />\n </a>\n <button class=\"mobile-menu-btn\" aria-label=\"Toggle menu\">\n <i class=\"fas fa-bars\"></i>\n </button>\n <ul class=\"nav-links\">\n <li><a href=\"/index.html\">Home</a></li>\n <li><a href=\"/#search\">Search</a></li>\n <li><a href=\"/#features\">Features</a></li>\n <li><a href=\"/about.html\">About</a></li>\n <li><a href=\"/contact.html\">Contact</a></li>\n </ul>\n </nav>\n </header>\n <main class=\"page-content\">\n <div class=\"container\">\n <article class=\"blog-post\">\n <h1>Hotels Near ${location}: Complete Guide</h1>\n <div class=\"blog-content\">\n ${content}\n </div>\n </article>\n </div>\n </main>\n <footer>\n <div class=\"footer-main\">\n <div class=\"footer-brand\">\n <div class=\"footer-logo-row\">\n <span class=\"footer-logo\"><i class=\"fa-solid fa-gem\"></i></span>\n <span class=\"footer-brand-name\">AureaVibe</span>\n </div>\n <p class=\"footer-desc\">Compare hotel rates across multiple platforms to find the best prices. Your trusted partner for discovering premium accommodations worldwide.</p>\n <div class=\"footer-social\">\n <a href=\"https://www.facebook.com/profile.php?id=61577051360915\" aria-label=\"Facebook\"><i class=\"fab fa-facebook-f\"></i></a>\n <a href=\"https://www.instagram.com/aureavibeapp\" aria-label=\"Instagram\"><i class=\"fab fa-instagram\"></i></a>\n <a href=\"https://www.linkedin.com/company/aureavibe\" aria-label=\"LinkedIn\"><i class=\"fab fa-linkedin-in\"></i></a>\n </div>\n </div>\n <div class=\"footer-links\">\n <div class=\"footer-section\">\n <h4>Company</h4>\n <ul>\n <li><a href=\"/about.html\">About Us</a></li>\n <li><a href=\"/careers.html\">Careers</a></li>\n <li><a href=\"/press.html\">Press</a></li>\n <li><a href=\"/blog.html\">Blog</a></li>\n <li><a href=\"/investors.html\">Investors</a></li>\n </ul>\n </div>\n <div class=\"footer-section\">\n <h4>Support</h4>\n <ul>\n <li><a href=\"/help.html\">Help Center</a></li>\n <li><a href=\"/faq.html\">FAQ</a></li>\n <li><a href=\"/contact.html\">Contact Us</a></li>\n <li><a href=\"/safety.html\">Safety Center</a></li>\n <li><a href=\"/cancellation.html\">Cancellation Policy</a></li>\n <li><a href=\"/payment.html\">Payment Options</a></li>\n </ul>\n </div>\n <div class=\"footer-section\">\n <h4>Legal</h4>\n <ul>\n <li><a href=\"/privacy.html\">Privacy Policy</a></li>\n <li><a href=\"/terms.html\">Terms of Service</a></li>\n </ul>\n </div>\n </div>\n </div>\n <div class=\"footer-bottom-bar\">\n <div class=\"footer-bottom-left\">© 2025 AureaVibe. All rights reserved. Your trusted hotel rate comparison platform.</div>\n <div class=\"footer-bottom-right\">\n <span class=\"trust-badge\"><i class=\"fa-solid fa-shield-halved\"></i> Secure Booking</span>\n <span class=\"trust-badge\"><i class=\"fa-solid fa-certificate\"></i> Verified Hotels</span>\n </div>\n </div>\n </footer>\n <script src=\"/js/navigation.js\"></script>\n</body>\n</html>`;\n\n fs.writeFileSync(path.join(__dirname, '../client/blog', fileName), htmlContent);\n console.log(`Saved article: ${fileName}`);\n }));\n }\n\n res.json({ message: 'Hotel location blog posts generated successfully' });\n } catch (error) {\n console.error('Error generating hotel location blog posts:', error);\n res.status(500).json({ error: 'Failed to generate hotel location blog posts' });\n }\n});\n"}, "7c0f64fe6727052dd0b3fced02c2c11c440373ae": {"t": 1785766813.9770904, "n": 33434, "h": "4eab41ca5d7cede1", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nconst fs = require(\"fs\");\n// Load environment variables from .env file\n// Ensure you have a .env file in the root directory with:\n// PROD_API_KEY=your_lite_api_key_here\n// DEEPSEEK_API_KEY=your_deepseek_api_key_here\n// FRONTEND_URL=http://localhost:your_frontend_port (if applicable for CORS in dev)\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\nconst { generateAllArticles } = require('./blog-generator');\nconst OpenAI = require('openai');\n\n// Initialize DeepSeek OpenAI client\nconst deepSeekApiKey = process.env.DEEPSEEK_API_KEY;\nlet openaiClient;\nif (deepSeekApiKey) {\n openaiClient = new OpenAI({\n apiKey: deepSeekApiKey,\n baseURL: 'https://api.deepseek.com/v1', // Or 'https://api.deepseek.com'\n });\n console.log(\"DeepSeek OpenAI client initialized.\");\n} else {\n console.warn(\"DEEPSEEK_API_KEY not found in .env. DeepSeek client not initialized.\");\n}\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\n// Add blog posts endpoint\napp.get('/api/blog-posts', (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const blogPosts = files.filter(file => file.endsWith('.html'));\n res.json(blogPosts);\n } catch (error) {\n console.error('Error reading blog posts:', error);\n res.status(500).json({ error: 'Failed to load blog posts' });\n }\n});\n\n// Serve individual blog posts\napp.get('/blog/:post', (req, res) => {\n const postFile = req.params.post;\n const blogDir = path.join(__dirname, '../client/blog');\n const postPath = path.join(blogDir, postFile);\n \n if (fs.existsSync(postPath)) {\n res.sendFile(postPath);\n } else {\n res.status(404).send('Blog post not found');\n }\n});\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\n\n/**\n * Fetches hotel list from LITE API based on city/country,\n * then gets full rates for these hotels based on checkin, checkout, and adults.\n * @param {object} searchParams - Contains city, countryCode, checkin, checkout, adults.\n * @returns {Promise<object>} Object containing rates array or empty if not found/error.\n */\nasync function fetchHotelsAndRates(searchParams) {\n const { city, countryCode, checkin, checkout, adults } = searchParams;\n console.log(\"Refactored search: Searching hotels with params:\", { countryCode, city, checkin, checkout, adults });\n const apiKey = process.env.PROD_API_KEY; // Ensure this is accessible\n if (!apiKey) {\n throw new Error(\"LITE_API_KEY (PROD_API_KEY) missing\");\n }\n const sdk = liteApi(apiKey);\n\n try {\n const hotelListResponse = await sdk.getHotels(countryCode, city, 0, 10); // Limit to 10 for now\n if (!hotelListResponse || !hotelListResponse.data || !Array.isArray(hotelListResponse.data)) {\n console.error(\"Invalid hotel list response from LITE API:\", hotelListResponse);\n return { rates: [] }; // Or throw specific error\n }\n const hotelsData = hotelListResponse.data;\n if (hotelsData.length === 0) {\n console.log(\"No hotels found by LITE API for the given criteria.\");\n return { rates: [] };\n }\n const hotelIds = hotelsData.map((hotel) => hotel.id);\n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\", // Or make configurable\n guestNationality: \"US\", // Or make configurable\n checkin: checkin,\n checkout: checkout,\n });\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response from LITE API:\", ratesResponse);\n return { rates: [] }; // Or throw specific error\n }\n const ratesData = ratesResponse.data;\n ratesData.forEach((rate) => {\n rate.hotel = hotelsData.find((hotel) => hotel.id === rate.hotelId);\n });\n return { rates: ratesData };\n } catch (error) {\n console.error(\"Error in fetchHotelsAndRates:\", error);\n throw error; // Re-throw to be caught by caller\n }\n}\n\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit via GET\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n try {\n const result = await fetchHotelsAndRates({ city, countryCode, checkin, checkout, adults });\n res.json(result);\n } catch (error) {\n console.error(\"Error in /api/search-hotels GET endpoint:\", error);\n res.status(500).json({ error: \"Internal server error\", details: error.message });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve sitemap.xml with correct content type\napp.get('/sitemap.xml', (req, res) => {\n res.type('application/xml');\n res.sendFile(path.join(__dirname, '../client/sitemap.xml'));\n});\n\n// New Chatbot API endpoint\napp.post(\"/api/chatbot/converse\", async (req, res) => {\n const { message, history = [] } = req.body;\n\n if (!openaiClient) {\n return res.status(500).json({ reply: \"Chatbot is not configured (missing API key).\" });\n }\n\n // System prompt to guide the DeepSeek model\n // It instructs the model on its role, required parameters for hotel search,\n // and how/when to use the 'get_hotel_availability' function.\n const messages = [\n {\n role: \"system\",\n content: \"You are a helpful assistant for booking hotels. Your goal is to gather necessary information (city, countryCode, check-in date, check-out date, and number of adults) from the user to search for hotels. If any of these details are missing, ask the user for them. Once you have all details, use the 'get_hotel_availability' function. Do not make up information if the user asks for something you don't know; instead, say you don't know or ask them to clarify. Dates should be in YYYY-MM-DD format.\"\n },\n ...history, // Spread previous messages\n { role: \"user\", content: message }\n ];\n\n // Define the tool(s) that the DeepSeek model can request to call.\n // 'get_hotel_availability' allows the model to ask our server to fetch hotel data.\n const tools = [\n {\n type: \"function\",\n function: {\n name: \"get_hotel_availability\",\n description: \"Get hotel availability and rates based on user criteria.\",\n parameters: {\n type: \"object\",\n properties: {\n city: { type: \"string\", description: \"The city for the hotel search, e.g., London\" },\n countryCode: { type: \"string\", description: \"The ISO-2 country code for the city, e.g., GB for United Kingdom. Infer this if possible from the city or ask the user.\" },\n checkin: { type: \"string\", description: \"Check-in date in YYYY-MM-DD format.\" },\n checkout: { type: \"string\", description: \"Check-out date in YYYY-MM-DD format.\" },\n adults: { type: \"integer\", description: \"Number of adults.\" }\n },\n required: [\"city\", \"countryCode\", \"checkin\", \"checkout\", \"adults\"]\n }\n }\n }\n ];\n\n try {\n console.log(\"Sending to DeepSeek:\", JSON.stringify(messages, null, 2));\n // First call to DeepSeek: send conversation history and user message,\n // allowing the model to either respond directly or request a tool call.\n const deepSeekResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\", // Or \"deepseek-reasoner\"\n messages: messages,\n tools: tools,\n tool_choice: \"auto\",\n });\n\n const responseMessage = deepSeekResponse.choices[0].message;\n console.log(\"Received from DeepSeek:\", JSON.stringify(responseMessage, null, 2));\n\n if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {\n const toolCall = responseMessage.tool_calls[0];\n if (toolCall.function.name === \"get_hotel_availability\") {\n // DeepSeek requested to call our 'get_hotel_availability' function.\n // Extract arguments provided by DeepSeek.\n const args = JSON.parse(toolCall.function.arguments);\n console.log(\"DeepSeek requested tool call: get_hotel_availability with args:\", args);\n\n // Call our internal LITE API hotel search function.\n const hotelResults = await fetchHotelsAndRates(args);\n\n // Add DeepSeek's message (requesting tool call) to history\n messages.push(responseMessage);\n\n if (hotelResults.rates && hotelResults.rates.length > 0) {\n let hotelSummaryText = \"Okay, I found some hotels for you:\\n\";\n hotelResults.rates.slice(0, 3).forEach(rate => {\n hotelSummaryText += `- ${rate.hotel.name} (ID: ${rate.hotel.id})\\n`;\n });\n if (hotelResults.rates.length > 3) {\n hotelSummaryText += \"And a few more. Ask for details on any hotel by its ID or name if you're interested!\\n\";\n }\n\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n // Send summary for DeepSeek to use in its response, not the full hotel data\n content: JSON.stringify(hotelResults.rates.slice(0,3).map(r => ({id: r.hotel.id, name: r.hotel.name, city: r.hotel.city, country: r.hotel.countryCode })))\n });\n\n // Send the tool's execution results back to DeepSeek.\n // This allows DeepSeek to formulate a natural language response based on the hotel data found (or not found).\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup response:\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n // The final reply to the client comes from this follow-up response.\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, hotel_data: hotelResults.rates, is_hotel_list: true, history: messages });\n\n } else {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n content: JSON.stringify({error: \"No hotels found for the given criteria. You could try different dates or a different city.\"})\n });\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup (no hotels):\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, is_hotel_list: false, history: messages });\n }\n } else {\n // Unexpected tool called\n console.warn(\"DeepSeek called an unexpected tool:\", toolCall.function.name);\n messages.push(responseMessage); // Add DeepSeek's message\n // Fallback to a simple response without tool processing\n res.json({ reply: \"An unexpected tool was called by the assistant.\", is_hotel_list: false, history: messages });\n }\n } else {\n // No tool call, just a regular message from DeepSeek\n messages.push(responseMessage);\n res.json({ reply: responseMessage.content, is_hotel_list: false, history: messages });\n }\n } catch (error) {\n console.error(\"Error in /api/chatbot/converse:\", error);\n if (error.response) {\n console.error(\"DeepSeek API Error Response Status:\", error.status);\n console.error(\"DeepSeek API Error Response Headers:\", error.headers);\n console.error(\"DeepSeek API Error Response Data:\", error.error); // OpenAI Node SDK nests error details here\n res.status(error.status || 500).json({ error: \"Error calling DeepSeek API\", details: error.error });\n } else {\n res.status(500).json({ error: \"Internal server error while processing chat\", details: error.message });\n }\n }\n});\n\n// Blog generation API endpoint\napp.post(\"/api/generate-blog\", async (req, res) => {\n try {\n await generateAllArticles();\n res.json({ success: true, message: \"All blog articles and sitemap generated successfully\" });\n } catch (error) {\n console.error(\"Error generating blog articles:\", error);\n res.status(500).json({ \n error: \"Failed to generate blog articles\", \n details: error.message \n });\n }\n});\n\n/**\n * API endpoint to fetch hotel reviews from LITE API.\n * Expects a 'hotelId' query parameter.\n */\napp.get(\"/api/hotel-reviews\", async (req, res) => {\n const { hotelId } = req.query;\n console.log(`Fetching reviews for hotelId: ${hotelId}`);\n\n if (!hotelId) {\n return res.status(400).json({ error: \"hotelId is required\" });\n }\n\n const currentApiKey = process.env.PROD_API_KEY; // Using currentApiKey to avoid conflict with global apiKey\n if (!currentApiKey) {\n console.error(\"LITE_API_KEY (PROD_API_KEY) missing for reviews endpoint\");\n return res.status(500).json({ error: \"API key not configured on server.\" });\n }\n const sdk = liteApi(currentApiKey);\n\n try {\n // Assuming the sdk.getHotelReviews(hotelId, limit) maps to GET /data/reviews\n // Fetch up to 20 reviews.\n const reviewsResponse = await sdk.getHotelReviews(hotelId, 20);\n\n if (!reviewsResponse || !reviewsResponse.data) {\n console.warn(`No reviews found or invalid response for hotelId: ${hotelId}`, reviewsResponse);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.json({ data: reviewsResponse.data });\n } catch (error) {\n console.error(`Error fetching reviews for hotelId ${hotelId}:`, error);\n // Check if the error is from the LiteAPI SDK due to \"No Content\" or similar\n if (error.response && error.response.status === 204) {\n console.warn(`No reviews found (204 No Content) for hotelId: ${hotelId}`);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.status(500).json({ error: \"Failed to fetch hotel reviews\", details: error.message });\n }\n});\n\n// Blog posts API endpoint\napp.get(\"/api/blog-posts\", (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const htmlFiles = files.filter(file => file.endsWith('.html'));\n res.json(htmlFiles);\n } catch (error) {\n console.error(\"Error reading blog posts:\", error);\n res.status(500).json({ \n error: \"Failed to read blog posts\", \n details: error.message \n });\n }\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Catch-all route for SPA - THIS MUST BE LAST for client-side routing to work\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n\n// Generate blog posts for hotel locations\napp.post('/api/generate-hotel-locations', async (req, res) => {\n console.log('Starting hotel location blog generation...');\n \n const locations = [\n 'New York, NY', 'Los Angeles, CA', 'Chicago, IL', 'Houston, TX', 'Phoenix, AZ',\n 'Philadelphia, PA', 'San Antonio, TX', 'San Diego, CA', 'Dallas, TX', 'San Jose, CA',\n 'Austin, TX', 'Jacksonville, FL', 'San Francisco, CA', 'Columbus, OH', 'Indianapolis, IN',\n 'Fort Worth, TX', 'Charlotte, NC', 'Seattle, WA', 'Denver, CO', 'Washington, DC',\n 'Alexandria, VA', 'Fairfax, VA', 'Herndon, VA', 'Rockville, MD', 'Richmond, VA',\n 'Boston, MA', 'Nashville, TN', 'El Paso, TX', 'Detroit, MI', 'Memphis, TN',\n 'Portland, OR', 'Oklahoma City, OK', 'Las Vegas, NV', 'Louisville, KY', 'Baltimore, MD',\n 'Milwaukee, WI', 'Albuquerque, NM', 'Tucson, AZ', 'Fresno, CA', 'Sacramento, CA',\n 'Kansas City, MO', 'Mesa, AZ', 'Atlanta, GA', 'Long Beach, CA', 'Raleigh, NC',\n 'Miami, FL', 'Virginia Beach, VA', 'Omaha, NE', 'Oakland, CA', 'Minneapolis, MN',\n 'Tulsa, OK', 'Arlington, TX', 'Tampa, FL', 'New Orleans, LA', 'Cleveland, OH',\n 'Honolulu, HI', 'Anaheim, CA', 'Lexington, KY', 'Stockton, CA', 'Corpus Christi, TX',\n 'Henderson, NV', 'Riverside, CA', 'Newark, NJ', 'Saint Paul, MN', 'Santa Ana, CA',\n 'Cincinnati, OH', 'Irvine, CA', 'Orlando, FL', 'Pittsburgh, PA', 'St. Louis, MO',\n 'Greensboro, NC', 'Jersey City, NJ', 'Anchorage, AK', 'Lincoln, NE', 'Plano, TX',\n 'Durham, NC', 'Buffalo, NY', 'Chandler, AZ', 'Chula Vista, CA', 'Toledo, OH',\n 'Madison, WI', 'Gilbert, AZ', 'Reno, NV', 'Fort Wayne, IN', 'North Las Vegas, NV',\n 'St. Petersburg, FL', 'Lubbock, TX', 'Irving, TX', 'Laredo, TX', 'Winston-Salem, NC',\n 'Chesapeake, VA', 'Glendale, AZ', 'Garland, TX', 'Scottsdale, AZ', 'Norfolk, VA',\n 'Boise, ID', 'Fremont, CA', 'Spokane, WA', 'Montgomery, AL', 'Yonkers, NY',\n 'Augusta, GA', 'Baton Rouge, LA', 'Des Moines, IA', 'Grand Rapids, MI', 'Salt Lake City, UT',\n 'Birmingham, AL', 'Huntsville, AL', 'Mobile, AL', 'Little Rock, AR', 'Providence, RI',\n 'Sioux Falls, SD', 'Savannah, GA', 'Chattanooga, TN', 'Jackson, MS', 'Fort Lauderdale, FL',\n 'Burlington, VT', 'Fargo, ND', 'Columbia, SC', 'Wilmington, NC', 'Albany, NY',\n 'Hartford, CT', 'Springfield, MO', 'Rochester, NY', 'Tallahassee, FL', 'Asheville, NC',\n 'Knoxville, TN', 'Dayton, OH', 'Syracuse, NY', 'Worcester, MA',\n 'San Juan, PR', 'Ponce, PR', 'Mayaguez, PR', 'Arecibo, PR', 'Caguas, PR',\n 'Fajardo, PR', 'Guaynabo, PR', 'Humacao, PR', 'Bayamon, PR', 'Carolina, PR',\n 'Anchorage, AK', 'Fairbanks, AK', 'Juneau, AK', 'Sitka, AK', 'Kodiak, AK',\n 'Bethel, AK', 'Ketchikan, AK', 'Palmer, AK', 'Kenai, AK', 'Kodiak, AK',\n 'Honolulu, HI', 'Pearl City, HI', 'Hilo, HI', 'Kailua, HI', 'Waipahu, HI',\n 'Mililani Town, HI', 'Kaneohe, HI', 'Ewa Gentry, HI', 'Kihei, HI', 'Makakilo, HI',\n 'Las Vegas, NV', 'Reno, NV', 'Henderson, NV', 'North Las Vegas, NV', 'Sparks, NV',\n 'Carson City, NV', 'Fernley, NV', 'Elko, NV', 'Mesquite, NV', 'Boulder City, NV',\n 'Phoenix, AZ', 'Tucson, AZ', 'Mesa, AZ', 'Scottsdale, AZ', 'Glendale, AZ',\n 'Chandler, AZ', 'Gilbert, AZ', 'Tempe, AZ', 'Peoria, AZ', 'Surprise, AZ',\n 'San Diego, CA', 'Los Angeles, CA', 'San Jose, CA', 'San Francisco, CA', 'Fresno, CA',\n 'Sacramento, CA', 'Long Beach, CA', 'Oakland, CA', 'Anaheim, CA', 'Santa Ana, CA',\n 'Miami, FL', 'Orlando, FL', 'Tampa, FL', 'St. Petersburg, FL', 'Hialeah, FL',\n 'Fort Lauderdale, FL', 'Port St. Lucie, FL', 'Cape Coral, FL', 'Gainesville, FL', 'Palm Bay, FL',\n 'New York, NY', 'Buffalo, NY', 'Rochester, NY', 'Yonkers, NY', 'Syracuse, NY',\n 'Albany, NY', 'New Rochelle, NY', 'Mount Vernon, NY', 'Schenectady, NY', 'Utica, NY',\n 'Chicago, IL', 'Aurora, IL', 'Rockford, IL', 'Joliet, IL', 'Naperville, IL',\n 'Springfield, IL', 'Peoria, IL', 'Elgin, IL', 'Waukegan, IL', 'Champaign, IL',\n 'Houston, TX', 'San Antonio, TX', 'Dallas, TX', 'Austin, TX', 'Fort Worth, TX',\n 'El Paso, TX', 'Arlington, TX', 'Corpus Christi, TX', 'Plano, TX', 'Laredo, TX',\n 'Seattle, WA', 'Spokane, WA', 'Tacoma, WA', 'Vancouver, WA', 'Bellevue, WA',\n 'Kent, WA', 'Everett, WA', 'Renton, WA', 'Yakima, WA', 'Federal Way, WA',\n 'Portland, OR', 'Salem, OR', 'Eugene, OR', 'Gresham, OR', 'Hillsboro, OR',\n 'Beaverton, OR', 'Bend, OR', 'Medford, OR', 'Springfield, OR', 'Corvallis, OR',\n 'Denver, CO', 'Colorado Springs, CO', 'Aurora, CO', 'Fort Collins, CO', 'Lakewood, CO',\n 'Thornton, CO', 'Arvada, CO', 'Westminster, CO', 'Pueblo, CO', 'Boulder, CO',\n 'Minneapolis, MN', 'St. Paul, MN', 'Rochester, MN', 'Duluth, MN', 'Bloomington, MN',\n 'Brooklyn Park, MN', 'Plymouth, MN', 'St. Cloud, MN', 'Eagan, MN', 'Woodbury, MN',\n 'Detroit, MI', 'Grand Rapids, MI', 'Warren, MI', 'Sterling Heights, MI', 'Ann Arbor, MI',\n 'Lansing, MI', 'Flint, MI', 'Dearborn, MI', 'Livonia, MI', 'Westland, MI',\n 'Boston, MA', 'Worcester, MA', 'Springfield, MA', 'Cambridge, MA', 'Lowell, MA',\n 'Brockton, MA', 'New Bedford, MA', 'Quincy, MA', 'Lynn, MA', 'Fall River, MA',\n 'Philadelphia, PA', 'Pittsburgh, PA', 'Allentown, PA', 'Erie, PA', 'Reading, PA',\n 'Scranton, PA', 'Bethlehem, PA', 'Lancaster, PA', 'Harrisburg, PA', 'Altoona, PA',\n 'Baltimore, MD', 'Rockville, MD', 'Germantown, MD', 'Bowie, MD', 'Hagerstown, MD',\n 'Frederick, MD', 'Gaithersburg, MD', 'Bethesda, MD', 'Ellicott City, MD', 'Columbia, MD',\n 'Washington, DC', 'Arlington, VA', 'Alexandria, VA', 'Fairfax, VA', 'Richmond, VA',\n 'Virginia Beach, VA', 'Norfolk, VA', 'Chesapeake, VA', 'Newport News, VA', 'Hampton, VA'\n ];\n\n try {\n // Process cities in chunks of 5 for parallel processing\n const chunkSize = 5;\n for (let i = 0; i < locations.length; i += chunkSize) {\n const chunk = locations.slice(i, i + chunkSize);\n console.log(`Processing chunk ${i/chunkSize + 1} of ${Math.ceil(locations.length/chunkSize)}`);\n \n await Promise.all(chunk.map(async (location) => {\n console.log(`Generating article for: Hotels near ${location}`);\n \n const prompt = `Write a comprehensive blog post about hotels in ${location}. Include information about:\n1. Popular hotel districts and neighborhoods\n2. Average hotel prices and seasonal variations\n3. Types of hotels available (luxury, budget, boutique, etc.)\n4. Nearby attractions and points of interest\n5. Transportation options and accessibility\n6. Best times to visit\n7. Special considerations for travelers\n8. Tips for finding the best deals\n9. Local amenities and services\n10. Safety and security information\n\nFormat the content in HTML with proper headings, paragraphs, and lists.`;\n\n const response = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: prompt }],\n temperature: 0.7,\n max_tokens: 2000\n });\n\n const content = response.choices[0].message.content;\n const fileName = `hotels-near-${location.toLowerCase().replace(/,?\\s+/g, '-')}.html`;\n \n const htmlContent = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Hotels Near ${location} - Complete Guide</title>\n <link rel=\"stylesheet\" href=\"/styles.css\">\n</head>\n<body>\n <div class=\"blog-post\">\n <h1>Hotels Near ${location}: Complete Guide</h1>\n <div class=\"blog-content\">\n ${content}\n </div>\n </div>\n</body>\n</html>`;\n\n fs.writeFileSync(path.join(__dirname, '../client/blog', fileName), htmlContent);\n console.log(`Saved article: ${fileName}`);\n }));\n }\n\n res.json({ message: 'Hotel location blog posts generated successfully' });\n } catch (error) {\n console.error('Error generating hotel location blog posts:', error);\n res.status(500).json({ error: 'Failed to generate hotel location blog posts' });\n }\n});\n"}, "c695b8c2411799b4a350f665bfb29896134ddb9d": {"t": 1785766815.523288, "n": 24126, "h": "712f16f02451e670", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nconst fs = require(\"fs\");\n// Load environment variables from .env file\n// Ensure you have a .env file in the root directory with:\n// PROD_API_KEY=your_lite_api_key_here\n// DEEPSEEK_API_KEY=your_deepseek_api_key_here\n// FRONTEND_URL=http://localhost:your_frontend_port (if applicable for CORS in dev)\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\nconst { generateAllArticles } = require('./blog-generator');\nconst OpenAI = require('openai');\n\n// Initialize DeepSeek OpenAI client\nconst deepSeekApiKey = process.env.DEEPSEEK_API_KEY;\nlet openaiClient;\nif (deepSeekApiKey) {\n openaiClient = new OpenAI({\n apiKey: deepSeekApiKey,\n baseURL: 'https://api.deepseek.com/v1', // Or 'https://api.deepseek.com'\n });\n console.log(\"DeepSeek OpenAI client initialized.\");\n} else {\n console.warn(\"DEEPSEEK_API_KEY not found in .env. DeepSeek client not initialized.\");\n}\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\n\n/**\n * Fetches hotel list from LITE API based on city/country,\n * then gets full rates for these hotels based on checkin, checkout, and adults.\n * @param {object} searchParams - Contains city, countryCode, checkin, checkout, adults.\n * @returns {Promise<object>} Object containing rates array or empty if not found/error.\n */\nasync function fetchHotelsAndRates(searchParams) {\n const { city, countryCode, checkin, checkout, adults } = searchParams;\n console.log(\"Refactored search: Searching hotels with params:\", { countryCode, city, checkin, checkout, adults });\n const apiKey = process.env.PROD_API_KEY; // Ensure this is accessible\n if (!apiKey) {\n throw new Error(\"LITE_API_KEY (PROD_API_KEY) missing\");\n }\n const sdk = liteApi(apiKey);\n\n try {\n const hotelListResponse = await sdk.getHotels(countryCode, city, 0, 10); // Limit to 10 for now\n if (!hotelListResponse || !hotelListResponse.data || !Array.isArray(hotelListResponse.data)) {\n console.error(\"Invalid hotel list response from LITE API:\", hotelListResponse);\n return { rates: [] }; // Or throw specific error\n }\n const hotelsData = hotelListResponse.data;\n if (hotelsData.length === 0) {\n console.log(\"No hotels found by LITE API for the given criteria.\");\n return { rates: [] };\n }\n const hotelIds = hotelsData.map((hotel) => hotel.id);\n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\", // Or make configurable\n guestNationality: \"US\", // Or make configurable\n checkin: checkin,\n checkout: checkout,\n });\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response from LITE API:\", ratesResponse);\n return { rates: [] }; // Or throw specific error\n }\n const ratesData = ratesResponse.data;\n ratesData.forEach((rate) => {\n rate.hotel = hotelsData.find((hotel) => hotel.id === rate.hotelId);\n });\n return { rates: ratesData };\n } catch (error) {\n console.error(\"Error in fetchHotelsAndRates:\", error);\n throw error; // Re-throw to be caught by caller\n }\n}\n\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit via GET\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n try {\n const result = await fetchHotelsAndRates({ city, countryCode, checkin, checkout, adults });\n res.json(result);\n } catch (error) {\n console.error(\"Error in /api/search-hotels GET endpoint:\", error);\n res.status(500).json({ error: \"Internal server error\", details: error.message });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve sitemap.xml with correct content type\napp.get('/sitemap.xml', (req, res) => {\n res.type('application/xml');\n res.sendFile(path.join(__dirname, '../client/sitemap.xml'));\n});\n\n// New Chatbot API endpoint\napp.post(\"/api/chatbot/converse\", async (req, res) => {\n const { message, history = [] } = req.body;\n\n if (!openaiClient) {\n return res.status(500).json({ reply: \"Chatbot is not configured (missing API key).\" });\n }\n\n // System prompt to guide the DeepSeek model\n // It instructs the model on its role, required parameters for hotel search,\n // and how/when to use the 'get_hotel_availability' function.\n const messages = [\n {\n role: \"system\",\n content: \"You are a helpful assistant for booking hotels. Your goal is to gather necessary information (city, countryCode, check-in date, check-out date, and number of adults) from the user to search for hotels. If any of these details are missing, ask the user for them. Once you have all details, use the 'get_hotel_availability' function. Do not make up information if the user asks for something you don't know; instead, say you don't know or ask them to clarify. Dates should be in YYYY-MM-DD format.\"\n },\n ...history, // Spread previous messages\n { role: \"user\", content: message }\n ];\n\n // Define the tool(s) that the DeepSeek model can request to call.\n // 'get_hotel_availability' allows the model to ask our server to fetch hotel data.\n const tools = [\n {\n type: \"function\",\n function: {\n name: \"get_hotel_availability\",\n description: \"Get hotel availability and rates based on user criteria.\",\n parameters: {\n type: \"object\",\n properties: {\n city: { type: \"string\", description: \"The city for the hotel search, e.g., London\" },\n countryCode: { type: \"string\", description: \"The ISO-2 country code for the city, e.g., GB for United Kingdom. Infer this if possible from the city or ask the user.\" },\n checkin: { type: \"string\", description: \"Check-in date in YYYY-MM-DD format.\" },\n checkout: { type: \"string\", description: \"Check-out date in YYYY-MM-DD format.\" },\n adults: { type: \"integer\", description: \"Number of adults.\" }\n },\n required: [\"city\", \"countryCode\", \"checkin\", \"checkout\", \"adults\"]\n }\n }\n }\n ];\n\n try {\n console.log(\"Sending to DeepSeek:\", JSON.stringify(messages, null, 2));\n // First call to DeepSeek: send conversation history and user message,\n // allowing the model to either respond directly or request a tool call.\n const deepSeekResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\", // Or \"deepseek-reasoner\"\n messages: messages,\n tools: tools,\n tool_choice: \"auto\",\n });\n\n const responseMessage = deepSeekResponse.choices[0].message;\n console.log(\"Received from DeepSeek:\", JSON.stringify(responseMessage, null, 2));\n\n if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {\n const toolCall = responseMessage.tool_calls[0];\n if (toolCall.function.name === \"get_hotel_availability\") {\n // DeepSeek requested to call our 'get_hotel_availability' function.\n // Extract arguments provided by DeepSeek.\n const args = JSON.parse(toolCall.function.arguments);\n console.log(\"DeepSeek requested tool call: get_hotel_availability with args:\", args);\n\n // Call our internal LITE API hotel search function.\n const hotelResults = await fetchHotelsAndRates(args);\n\n // Add DeepSeek's message (requesting tool call) to history\n messages.push(responseMessage);\n\n if (hotelResults.rates && hotelResults.rates.length > 0) {\n let hotelSummaryText = \"Okay, I found some hotels for you:\\n\";\n hotelResults.rates.slice(0, 3).forEach(rate => {\n hotelSummaryText += `- ${rate.hotel.name} (ID: ${rate.hotel.id})\\n`;\n });\n if (hotelResults.rates.length > 3) {\n hotelSummaryText += \"And a few more. Ask for details on any hotel by its ID or name if you're interested!\\n\";\n }\n\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n // Send summary for DeepSeek to use in its response, not the full hotel data\n content: JSON.stringify(hotelResults.rates.slice(0,3).map(r => ({id: r.hotel.id, name: r.hotel.name, city: r.hotel.city, country: r.hotel.countryCode })))\n });\n\n // Send the tool's execution results back to DeepSeek.\n // This allows DeepSeek to formulate a natural language response based on the hotel data found (or not found).\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup response:\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n // The final reply to the client comes from this follow-up response.\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, hotel_data: hotelResults.rates, is_hotel_list: true, history: messages });\n\n } else {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n content: JSON.stringify({error: \"No hotels found for the given criteria. You could try different dates or a different city.\"})\n });\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup (no hotels):\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, is_hotel_list: false, history: messages });\n }\n } else {\n // Unexpected tool called\n console.warn(\"DeepSeek called an unexpected tool:\", toolCall.function.name);\n messages.push(responseMessage); // Add DeepSeek's message\n // Fallback to a simple response without tool processing\n res.json({ reply: \"An unexpected tool was called by the assistant.\", is_hotel_list: false, history: messages });\n }\n } else {\n // No tool call, just a regular message from DeepSeek\n messages.push(responseMessage);\n res.json({ reply: responseMessage.content, is_hotel_list: false, history: messages });\n }\n } catch (error) {\n console.error(\"Error in /api/chatbot/converse:\", error);\n if (error.response) {\n console.error(\"DeepSeek API Error Response Status:\", error.status);\n console.error(\"DeepSeek API Error Response Headers:\", error.headers);\n console.error(\"DeepSeek API Error Response Data:\", error.error); // OpenAI Node SDK nests error details here\n res.status(error.status || 500).json({ error: \"Error calling DeepSeek API\", details: error.error });\n } else {\n res.status(500).json({ error: \"Internal server error while processing chat\", details: error.message });\n }\n }\n});\n\n// Blog generation API endpoint\napp.post(\"/api/generate-blog\", async (req, res) => {\n try {\n await generateAllArticles();\n res.json({ success: true, message: \"All blog articles and sitemap generated successfully\" });\n } catch (error) {\n console.error(\"Error generating blog articles:\", error);\n res.status(500).json({ \n error: \"Failed to generate blog articles\", \n details: error.message \n });\n }\n});\n\n/**\n * API endpoint to fetch hotel reviews from LITE API.\n * Expects a 'hotelId' query parameter.\n */\napp.get(\"/api/hotel-reviews\", async (req, res) => {\n const { hotelId } = req.query;\n console.log(`Fetching reviews for hotelId: ${hotelId}`);\n\n if (!hotelId) {\n return res.status(400).json({ error: \"hotelId is required\" });\n }\n\n const currentApiKey = process.env.PROD_API_KEY; // Using currentApiKey to avoid conflict with global apiKey\n if (!currentApiKey) {\n console.error(\"LITE_API_KEY (PROD_API_KEY) missing for reviews endpoint\");\n return res.status(500).json({ error: \"API key not configured on server.\" });\n }\n const sdk = liteApi(currentApiKey);\n\n try {\n // Assuming the sdk.getHotelReviews(hotelId, limit) maps to GET /data/reviews\n // Fetch up to 20 reviews.\n const reviewsResponse = await sdk.getHotelReviews(hotelId, 20);\n\n if (!reviewsResponse || !reviewsResponse.data) {\n console.warn(`No reviews found or invalid response for hotelId: ${hotelId}`, reviewsResponse);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.json({ data: reviewsResponse.data });\n } catch (error) {\n console.error(`Error fetching reviews for hotelId ${hotelId}:`, error);\n // Check if the error is from the LiteAPI SDK due to \"No Content\" or similar\n if (error.response && error.response.status === 204) {\n console.warn(`No reviews found (204 No Content) for hotelId: ${hotelId}`);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.status(500).json({ error: \"Failed to fetch hotel reviews\", details: error.message });\n }\n});\n\n// Blog posts API endpoint\napp.get(\"/api/blog-posts\", (req, res) => {\n try {\n const blogDir = path.join(__dirname, '../client/blog');\n const files = fs.readdirSync(blogDir);\n const htmlFiles = files.filter(file => file.endsWith('.html'));\n res.json(htmlFiles);\n } catch (error) {\n console.error(\"Error reading blog posts:\", error);\n res.status(500).json({ \n error: \"Failed to read blog posts\", \n details: error.message \n });\n }\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Catch-all route for SPA - THIS MUST BE LAST for client-side routing to work\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n"}, "2d9c292b926acb2bb4bbdaa38b12e4731c8e8d3a": {"t": 1785766819.1093442, "n": 23633, "h": "4266ec998726363a", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\n// Load environment variables from .env file\n// Ensure you have a .env file in the root directory with:\n// PROD_API_KEY=your_lite_api_key_here\n// DEEPSEEK_API_KEY=your_deepseek_api_key_here\n// FRONTEND_URL=http://localhost:your_frontend_port (if applicable for CORS in dev)\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\nconst { generateAllArticles } = require('./blog-generator');\nconst OpenAI = require('openai');\n\n// Initialize DeepSeek OpenAI client\nconst deepSeekApiKey = process.env.DEEPSEEK_API_KEY;\nlet openaiClient;\nif (deepSeekApiKey) {\n openaiClient = new OpenAI({\n apiKey: deepSeekApiKey,\n baseURL: 'https://api.deepseek.com/v1', // Or 'https://api.deepseek.com'\n });\n console.log(\"DeepSeek OpenAI client initialized.\");\n} else {\n console.warn(\"DEEPSEEK_API_KEY not found in .env. DeepSeek client not initialized.\");\n}\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\n\n/**\n * Fetches hotel list from LITE API based on city/country,\n * then gets full rates for these hotels based on checkin, checkout, and adults.\n * @param {object} searchParams - Contains city, countryCode, checkin, checkout, adults.\n * @returns {Promise<object>} Object containing rates array or empty if not found/error.\n */\nasync function fetchHotelsAndRates(searchParams) {\n const { city, countryCode, checkin, checkout, adults } = searchParams;\n console.log(\"Refactored search: Searching hotels with params:\", { countryCode, city, checkin, checkout, adults });\n const apiKey = process.env.PROD_API_KEY; // Ensure this is accessible\n if (!apiKey) {\n throw new Error(\"LITE_API_KEY (PROD_API_KEY) missing\");\n }\n const sdk = liteApi(apiKey);\n\n try {\n const hotelListResponse = await sdk.getHotels(countryCode, city, 0, 10); // Limit to 10 for now\n if (!hotelListResponse || !hotelListResponse.data || !Array.isArray(hotelListResponse.data)) {\n console.error(\"Invalid hotel list response from LITE API:\", hotelListResponse);\n return { rates: [] }; // Or throw specific error\n }\n const hotelsData = hotelListResponse.data;\n if (hotelsData.length === 0) {\n console.log(\"No hotels found by LITE API for the given criteria.\");\n return { rates: [] };\n }\n const hotelIds = hotelsData.map((hotel) => hotel.id);\n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\", // Or make configurable\n guestNationality: \"US\", // Or make configurable\n checkin: checkin,\n checkout: checkout,\n });\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response from LITE API:\", ratesResponse);\n return { rates: [] }; // Or throw specific error\n }\n const ratesData = ratesResponse.data;\n ratesData.forEach((rate) => {\n rate.hotel = hotelsData.find((hotel) => hotel.id === rate.hotelId);\n });\n return { rates: ratesData };\n } catch (error) {\n console.error(\"Error in fetchHotelsAndRates:\", error);\n throw error; // Re-throw to be caught by caller\n }\n}\n\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit via GET\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n try {\n const result = await fetchHotelsAndRates({ city, countryCode, checkin, checkout, adults });\n res.json(result);\n } catch (error) {\n console.error(\"Error in /api/search-hotels GET endpoint:\", error);\n res.status(500).json({ error: \"Internal server error\", details: error.message });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve sitemap.xml with correct content type\napp.get('/sitemap.xml', (req, res) => {\n res.type('application/xml');\n res.sendFile(path.join(__dirname, '../client/sitemap.xml'));\n});\n\n// New Chatbot API endpoint\napp.post(\"/api/chatbot/converse\", async (req, res) => {\n const { message, history = [] } = req.body;\n\n if (!openaiClient) {\n return res.status(500).json({ reply: \"Chatbot is not configured (missing API key).\" });\n }\n\n // System prompt to guide the DeepSeek model\n // It instructs the model on its role, required parameters for hotel search,\n // and how/when to use the 'get_hotel_availability' function.\n const messages = [\n {\n role: \"system\",\n content: \"You are a helpful assistant for booking hotels. Your goal is to gather necessary information (city, countryCode, check-in date, check-out date, and number of adults) from the user to search for hotels. If any of these details are missing, ask the user for them. Once you have all details, use the 'get_hotel_availability' function. Do not make up information if the user asks for something you don't know; instead, say you don't know or ask them to clarify. Dates should be in YYYY-MM-DD format.\"\n },\n ...history, // Spread previous messages\n { role: \"user\", content: message }\n ];\n\n // Define the tool(s) that the DeepSeek model can request to call.\n // 'get_hotel_availability' allows the model to ask our server to fetch hotel data.\n const tools = [\n {\n type: \"function\",\n function: {\n name: \"get_hotel_availability\",\n description: \"Get hotel availability and rates based on user criteria.\",\n parameters: {\n type: \"object\",\n properties: {\n city: { type: \"string\", description: \"The city for the hotel search, e.g., London\" },\n countryCode: { type: \"string\", description: \"The ISO-2 country code for the city, e.g., GB for United Kingdom. Infer this if possible from the city or ask the user.\" },\n checkin: { type: \"string\", description: \"Check-in date in YYYY-MM-DD format.\" },\n checkout: { type: \"string\", description: \"Check-out date in YYYY-MM-DD format.\" },\n adults: { type: \"integer\", description: \"Number of adults.\" }\n },\n required: [\"city\", \"countryCode\", \"checkin\", \"checkout\", \"adults\"]\n }\n }\n }\n ];\n\n try {\n console.log(\"Sending to DeepSeek:\", JSON.stringify(messages, null, 2));\n // First call to DeepSeek: send conversation history and user message,\n // allowing the model to either respond directly or request a tool call.\n const deepSeekResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\", // Or \"deepseek-reasoner\"\n messages: messages,\n tools: tools,\n tool_choice: \"auto\",\n });\n\n const responseMessage = deepSeekResponse.choices[0].message;\n console.log(\"Received from DeepSeek:\", JSON.stringify(responseMessage, null, 2));\n\n if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {\n const toolCall = responseMessage.tool_calls[0];\n if (toolCall.function.name === \"get_hotel_availability\") {\n // DeepSeek requested to call our 'get_hotel_availability' function.\n // Extract arguments provided by DeepSeek.\n const args = JSON.parse(toolCall.function.arguments);\n console.log(\"DeepSeek requested tool call: get_hotel_availability with args:\", args);\n\n // Call our internal LITE API hotel search function.\n const hotelResults = await fetchHotelsAndRates(args);\n\n // Add DeepSeek's message (requesting tool call) to history\n messages.push(responseMessage);\n\n if (hotelResults.rates && hotelResults.rates.length > 0) {\n let hotelSummaryText = \"Okay, I found some hotels for you:\\n\";\n hotelResults.rates.slice(0, 3).forEach(rate => {\n hotelSummaryText += `- ${rate.hotel.name} (ID: ${rate.hotel.id})\\n`;\n });\n if (hotelResults.rates.length > 3) {\n hotelSummaryText += \"And a few more. Ask for details on any hotel by its ID or name if you're interested!\\n\";\n }\n\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n // Send summary for DeepSeek to use in its response, not the full hotel data\n content: JSON.stringify(hotelResults.rates.slice(0,3).map(r => ({id: r.hotel.id, name: r.hotel.name, city: r.hotel.city, country: r.hotel.countryCode })))\n });\n\n // Send the tool's execution results back to DeepSeek.\n // This allows DeepSeek to formulate a natural language response based on the hotel data found (or not found).\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup response:\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n // The final reply to the client comes from this follow-up response.\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, hotel_data: hotelResults.rates, is_hotel_list: true, history: messages });\n\n } else {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCall.id,\n name: toolCall.function.name,\n content: JSON.stringify({error: \"No hotels found for the given criteria. You could try different dates or a different city.\"})\n });\n const followupResponse = await openaiClient.chat.completions.create({\n model: \"deepseek-chat\",\n messages: messages,\n });\n console.log(\"DeepSeek followup (no hotels):\", JSON.stringify(followupResponse.choices[0].message, null, 2));\n // Add DeepSeek's final response to history for the next turn\n messages.push(followupResponse.choices[0].message);\n res.json({ reply: followupResponse.choices[0].message.content, is_hotel_list: false, history: messages });\n }\n } else {\n // Unexpected tool called\n console.warn(\"DeepSeek called an unexpected tool:\", toolCall.function.name);\n messages.push(responseMessage); // Add DeepSeek's message\n // Fallback to a simple response without tool processing\n res.json({ reply: \"An unexpected tool was called by the assistant.\", is_hotel_list: false, history: messages });\n }\n } else {\n // No tool call, just a regular message from DeepSeek\n messages.push(responseMessage);\n res.json({ reply: responseMessage.content, is_hotel_list: false, history: messages });\n }\n } catch (error) {\n console.error(\"Error in /api/chatbot/converse:\", error);\n if (error.response) {\n console.error(\"DeepSeek API Error Response Status:\", error.status);\n console.error(\"DeepSeek API Error Response Headers:\", error.headers);\n console.error(\"DeepSeek API Error Response Data:\", error.error); // OpenAI Node SDK nests error details here\n res.status(error.status || 500).json({ error: \"Error calling DeepSeek API\", details: error.error });\n } else {\n res.status(500).json({ error: \"Internal server error while processing chat\", details: error.message });\n }\n }\n});\n\n// Blog generation API endpoint\napp.post(\"/api/generate-blog\", async (req, res) => {\n try {\n await generateAllArticles();\n res.json({ success: true, message: \"All blog articles and sitemap generated successfully\" });\n } catch (error) {\n console.error(\"Error generating blog articles:\", error);\n res.status(500).json({ \n error: \"Failed to generate blog articles\", \n details: error.message \n });\n }\n});\n\n/**\n * API endpoint to fetch hotel reviews from LITE API.\n * Expects a 'hotelId' query parameter.\n */\napp.get(\"/api/hotel-reviews\", async (req, res) => {\n const { hotelId } = req.query;\n console.log(`Fetching reviews for hotelId: ${hotelId}`);\n\n if (!hotelId) {\n return res.status(400).json({ error: \"hotelId is required\" });\n }\n\n const currentApiKey = process.env.PROD_API_KEY; // Using currentApiKey to avoid conflict with global apiKey\n if (!currentApiKey) {\n console.error(\"LITE_API_KEY (PROD_API_KEY) missing for reviews endpoint\");\n return res.status(500).json({ error: \"API key not configured on server.\" });\n }\n const sdk = liteApi(currentApiKey);\n\n try {\n // Assuming the sdk.getHotelReviews(hotelId, limit) maps to GET /data/reviews\n // Fetch up to 20 reviews.\n const reviewsResponse = await sdk.getHotelReviews(hotelId, 20);\n\n if (!reviewsResponse || !reviewsResponse.data) {\n console.warn(`No reviews found or invalid response for hotelId: ${hotelId}`, reviewsResponse);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.json({ data: reviewsResponse.data });\n } catch (error) {\n console.error(`Error fetching reviews for hotelId ${hotelId}:`, error);\n // Check if the error is from the LiteAPI SDK due to \"No Content\" or similar\n if (error.response && error.response.status === 204) {\n console.warn(`No reviews found (204 No Content) for hotelId: ${hotelId}`);\n return res.json({ data: [] }); // Send empty array if no reviews\n }\n res.status(500).json({ error: \"Failed to fetch hotel reviews\", details: error.message });\n }\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Catch-all route for SPA - THIS MUST BE LAST for client-side routing to work\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n"}, "cd0020b12c93f31f5761fd7df7a857e7515b5cc0": {"t": 1785766820.4269996, "n": 13365, "h": "63c3f8b61840d498", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\nconst { generateAllArticles } = require('./blog-generator');\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n console.log(\"Search parameters:\", { checkin, checkout, adults, city, countryCode });\n \n if (!apiKey) {\n console.error(\"API key missing\");\n return res.status(500).json({ \n error: \"API key not configured. Please set up your PROD_API_KEY in the .env file\" \n });\n }\n\n const sdk = liteApi(apiKey);\n\n try {\n console.log(\"Searching hotels with params:\", { countryCode, city });\n const response = await sdk.getHotels(countryCode, city, 0, 10);\n console.log(\"Hotel search response:\", JSON.stringify(response, null, 2));\n \n if (!response || !response.data || !Array.isArray(response.data)) {\n console.error(\"Invalid hotel response:\", response);\n return res.status(500).json({ \n error: \"Invalid response from hotel search API\",\n details: response \n });\n }\n\n const data = response.data;\n if (data.length === 0) {\n console.log(\"No hotels found for the given criteria\");\n return res.json({ rates: [] });\n }\n\n const hotelIds = data.map((hotel) => hotel.id);\n console.log(\"Found hotel IDs:\", hotelIds);\n \n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n });\n\n console.log(\"Rates response:\", JSON.stringify(ratesResponse, null, 2));\n\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response:\", ratesResponse);\n return res.status(500).json({ \n error: \"Invalid response from rates API\",\n details: ratesResponse \n });\n }\n\n const rates = ratesResponse.data;\n rates.forEach((rate) => {\n rate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n });\n\n res.json({ rates });\n } catch (error) {\n console.error(\"Error searching for hotels:\", error);\n res.status(500).json({ \n error: \"Internal server error\", \n details: error.message \n });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve sitemap.xml with correct content type\napp.get('/sitemap.xml', (req, res) => {\n res.type('application/xml');\n res.sendFile(path.join(__dirname, '../client/sitemap.xml'));\n});\n\n// Catch-all route for SPA\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Add this before the app.listen call\napp.post(\"/api/generate-blog\", async (req, res) => {\n try {\n await generateAllArticles();\n res.json({ success: true, message: \"All blog articles and sitemap generated successfully\" });\n } catch (error) {\n console.error(\"Error generating blog articles:\", error);\n res.status(500).json({ \n error: \"Failed to generate blog articles\", \n details: error.message \n });\n }\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n"}, "a250840a1fb4e1cc3efd145acd11aa0fe027e5bb": {"t": 1785766822.8654351, "n": 12867, "h": "b9695cfa2d361119", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n console.log(\"Search parameters:\", { checkin, checkout, adults, city, countryCode });\n \n if (!apiKey) {\n console.error(\"API key missing\");\n return res.status(500).json({ \n error: \"API key not configured. Please set up your PROD_API_KEY in the .env file\" \n });\n }\n\n const sdk = liteApi(apiKey);\n\n try {\n console.log(\"Searching hotels with params:\", { countryCode, city });\n const response = await sdk.getHotels(countryCode, city, 0, 10);\n console.log(\"Hotel search response:\", JSON.stringify(response, null, 2));\n \n if (!response || !response.data || !Array.isArray(response.data)) {\n console.error(\"Invalid hotel response:\", response);\n return res.status(500).json({ \n error: \"Invalid response from hotel search API\",\n details: response \n });\n }\n\n const data = response.data;\n if (data.length === 0) {\n console.log(\"No hotels found for the given criteria\");\n return res.json({ rates: [] });\n }\n\n const hotelIds = data.map((hotel) => hotel.id);\n console.log(\"Found hotel IDs:\", hotelIds);\n \n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n });\n\n console.log(\"Rates response:\", JSON.stringify(ratesResponse, null, 2));\n\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response:\", ratesResponse);\n return res.status(500).json({ \n error: \"Invalid response from rates API\",\n details: ratesResponse \n });\n }\n\n const rates = ratesResponse.data;\n rates.forEach((rate) => {\n rate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n });\n\n res.json({ rates });\n } catch (error) {\n console.error(\"Error searching for hotels:\", error);\n res.status(500).json({ \n error: \"Internal server error\", \n details: error.message \n });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve sitemap.xml with correct content type\napp.get('/sitemap.xml', (req, res) => {\n res.type('application/xml');\n res.sendFile(path.join(__dirname, '../client/sitemap.xml'));\n});\n\n// Catch-all route for SPA\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n"}, "ca201c572cc0d39d504dfa0b70ed5054cd0216f1": {"t": 1785766824.3267968, "n": 12691, "h": "637fa1da8ca07ab7", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n console.log(\"Search parameters:\", { checkin, checkout, adults, city, countryCode });\n \n if (!apiKey) {\n console.error(\"API key missing\");\n return res.status(500).json({ \n error: \"API key not configured. Please set up your PROD_API_KEY in the .env file\" \n });\n }\n\n const sdk = liteApi(apiKey);\n\n try {\n console.log(\"Searching hotels with params:\", { countryCode, city });\n const response = await sdk.getHotels(countryCode, city, 0, 10);\n console.log(\"Hotel search response:\", JSON.stringify(response, null, 2));\n \n if (!response || !response.data || !Array.isArray(response.data)) {\n console.error(\"Invalid hotel response:\", response);\n return res.status(500).json({ \n error: \"Invalid response from hotel search API\",\n details: response \n });\n }\n\n const data = response.data;\n if (data.length === 0) {\n console.log(\"No hotels found for the given criteria\");\n return res.json({ rates: [] });\n }\n\n const hotelIds = data.map((hotel) => hotel.id);\n console.log(\"Found hotel IDs:\", hotelIds);\n \n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n });\n\n console.log(\"Rates response:\", JSON.stringify(ratesResponse, null, 2));\n\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response:\", ratesResponse);\n return res.status(500).json({ \n error: \"Invalid response from rates API\",\n details: ratesResponse \n });\n }\n\n const rates = ratesResponse.data;\n rates.forEach((rate) => {\n rate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n });\n\n res.json({ rates });\n } catch (error) {\n console.error(\"Error searching for hotels:\", error);\n res.status(500).json({ \n error: \"Internal server error\", \n details: error.message \n });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve the client-side application\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3001;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n"}, "58668b4d2ea12e27d1918244a9baf4ac0ea256a2": {"t": 1785766825.6878285, "n": 12691, "h": "8da96a043b4d7593", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\n// CORS configuration\napp.use(cors({\n origin: process.env.FRONTEND_URL || \"*\",\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n}));\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\nconst apiKey = process.env.PROD_API_KEY;\n\n// Log API key status\nconsole.log(\"API Key configured:\", apiKey ? \"Present\" : \"Missing\");\n\napp.use(bodyParser.json());\n\n// Prefix all API routes with /api\napp.get(\"/api/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit\");\n const { checkin, checkout, adults, city, countryCode } = req.query;\n console.log(\"Search parameters:\", { checkin, checkout, adults, city, countryCode });\n \n if (!apiKey) {\n console.error(\"API key missing\");\n return res.status(500).json({ \n error: \"API key not configured. Please set up your PROD_API_KEY in the .env file\" \n });\n }\n\n const sdk = liteApi(apiKey);\n\n try {\n console.log(\"Searching hotels with params:\", { countryCode, city });\n const response = await sdk.getHotels(countryCode, city, 0, 10);\n console.log(\"Hotel search response:\", JSON.stringify(response, null, 2));\n \n if (!response || !response.data || !Array.isArray(response.data)) {\n console.error(\"Invalid hotel response:\", response);\n return res.status(500).json({ \n error: \"Invalid response from hotel search API\",\n details: response \n });\n }\n\n const data = response.data;\n if (data.length === 0) {\n console.log(\"No hotels found for the given criteria\");\n return res.json({ rates: [] });\n }\n\n const hotelIds = data.map((hotel) => hotel.id);\n console.log(\"Found hotel IDs:\", hotelIds);\n \n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n });\n\n console.log(\"Rates response:\", JSON.stringify(ratesResponse, null, 2));\n\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response:\", ratesResponse);\n return res.status(500).json({ \n error: \"Invalid response from rates API\",\n details: ratesResponse \n });\n }\n\n const rates = ratesResponse.data;\n rates.forEach((rate) => {\n rate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n });\n\n res.json({ rates });\n } catch (error) {\n console.error(\"Error searching for hotels:\", error);\n res.status(500).json({ \n error: \"Internal server error\", \n details: error.message \n });\n }\n});\n\napp.get(\"/api/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId } = req.query;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/api/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, voucherCode } = req.body;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/api/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId } =\n req.query;\n\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve the client-side application\napp.get(\"*\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\n// Export the app for Vercel\nmodule.exports = app;\n\n// Start the server if we're not in Vercel\nif (process.env.NODE_ENV !== 'production') {\n const PORT = process.env.PORT || 3000;\n app.listen(PORT, () => {\n console.log(`Server is running on port ${PORT}`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n}\n"}, "c0b5df689b253c85c59b4f602d753ad1d354ccbc": {"t": 1785766828.1213515, "n": 13595, "h": "11ecea3a43c9a07c", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire(\"dotenv\").config();\nconst { sendBookingConfirmation } = require('./email');\n\n// Request logging middleware\napp.use((req, res, next) => {\n console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);\n next();\n});\n\napp.use(\n cors({\n origin: \"*\",\n })\n);\n\n// Serve static files from the client directory\napp.use(express.static(path.join(__dirname, '../client')));\n\nconst prod_apiKey = process.env.PROD_API_KEY;\nconst sandbox_apiKey = process.env.SAND_API_KEY;\n\n// Log API keys (without showing full values)\nconsole.log(\"API Keys configured:\", {\n sandbox: sandbox_apiKey ? \"Present\" : \"Missing\",\n production: prod_apiKey ? \"Present\" : \"Missing\"\n});\n\napp.use(bodyParser.json());\n\napp.get(\"/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit\");\n const { checkin, checkout, adults, city, countryCode, environment } = req.query;\n console.log(\"Search parameters:\", { checkin, checkout, adults, city, countryCode, environment });\n \n const apiKey = environment == \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n \n if (!apiKey) {\n console.error(\"API key missing for environment:\", environment);\n return res.status(500).json({ \n error: \"API key not configured. Please set up your SAND_API_KEY and PROD_API_KEY in the .env file\" \n });\n }\n\n const sdk = liteApi(apiKey);\n\n try {\n console.log(\"Searching hotels with params:\", { countryCode, city });\n const response = await sdk.getHotels(countryCode, city, 0, 10);\n console.log(\"Hotel search response:\", JSON.stringify(response, null, 2));\n \n if (!response || !response.data || !Array.isArray(response.data)) {\n console.error(\"Invalid hotel response:\", response);\n return res.status(500).json({ \n error: \"Invalid response from hotel search API\",\n details: response \n });\n }\n\n const data = response.data;\n if (data.length === 0) {\n console.log(\"No hotels found for the given criteria\");\n return res.json({ rates: [] });\n }\n\n const hotelIds = data.map((hotel) => hotel.id);\n console.log(\"Found hotel IDs:\", hotelIds);\n \n const ratesResponse = await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n });\n\n console.log(\"Rates response:\", JSON.stringify(ratesResponse, null, 2));\n\n if (!ratesResponse || !ratesResponse.data) {\n console.error(\"Invalid rates response:\", ratesResponse);\n return res.status(500).json({ \n error: \"Invalid response from rates API\",\n details: ratesResponse \n });\n }\n\n const rates = ratesResponse.data;\n rates.forEach((rate) => {\n rate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n });\n\n res.json({ rates });\n } catch (error) {\n console.error(\"Error searching for hotels:\", error);\n res.status(500).json({ \n error: \"Internal server error\", \n details: error.message \n });\n }\n});\n\napp.get(\"/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId, environment } = req.query;\n const apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, environment, voucherCode } = req.body;\n const apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId, environment } =\n req.query;\n\n const apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then(async (data) => {\n if (!data || data.error) {\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n // Send booking confirmation email\n try {\n const emailHtml = `\n <h1>Booking Confirmation</h1>\n <p>Thank you for booking with AureaVibe!</p>\n <p><strong>Booking ID:</strong> ${data.data.bookingId}</p>\n <p><strong>Hotel:</strong> ${data.data.hotel.name}</p>\n <p><strong>Check-in:</strong> ${data.data.checkin}</p>\n <p><strong>Check-out:</strong> ${data.data.checkout}</p>\n <p><strong>Status:</strong> ${data.data.status}</p>\n <p>If you have any questions, reply to this email or contact our support team.</p>\n `;\n await sendBookingConfirmation(\n guestEmail,\n 'Your Hotel Booking Confirmation',\n emailHtml\n );\n console.log('Booking confirmation email sent to', guestEmail);\n } catch (emailErr) {\n console.error('Failed to send booking confirmation email:', emailErr);\n }\n\n res.send(`\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n </head>\n <body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n <div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n </div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n </body>\n </html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve the client-side application\napp.get(\"/\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\n// Serve static pretty URLs for footer pages\nconst staticPages = [\n 'about', 'careers', 'press', 'blog', 'investors',\n 'help', 'faq', 'contact', 'safety', 'cancellation', 'payment',\n 'privacy', 'terms', 'cookies', 'accessibility'\n];\nstaticPages.forEach(page => {\n app.get(`/${page}`, (req, res) => {\n res.sendFile(path.join(__dirname, '../client', `${page}.html`));\n });\n});\n\nconst startServer = () => {\n const ports = [3000, 3001, 3002, 3003];\n \n const tryPort = (index) => {\n if (index >= ports.length) {\n console.error('No available ports found');\n process.exit(1);\n return;\n }\n \n const port = ports[index];\n const server = app.listen(port)\n .on('error', (err) => {\n if (err.code === 'EADDRINUSE') {\n console.log(`Port ${port} is busy, trying next port...`);\n server.close();\n tryPort(index + 1);\n } else {\n console.error('Server error:', err);\n }\n })\n .on('listening', () => {\n console.log(`Server is running on port ${port}`);\n console.log(`Open http://localhost:${port} in your browser`);\n console.log(`Static files are being served from: ${path.join(__dirname, '../client')}`);\n });\n };\n\n tryPort(0);\n};\n\nstartServer();\n"}, "6a9af188eca1f7fe6d27ca147bb68ad9dd7f6b02": {"t": 1785766829.858158, "n": 9238, "h": "f6e8c0bce01b8274", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire(\"dotenv\").config();\n\napp.use(\n cors({\n origin: \"*\",\n })\n);\n\nconst prod_apiKey = process.env.PROD_API_KEY;\nconst sandbox_apiKey = process.env.SAND_API_KEY;\n\napp.use(bodyParser.json());\n\napp.get(\"/search-hotels\", async (req, res) => {\n console.log(\"Search endpoint hit\");\n const { checkin, checkout, adults, city, countryCode, environment } = req.query;\n const apiKey = environment == \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n const sdk = liteApi(apiKey);\n\n try {\n const response = await sdk.getHotels(countryCode, city, 0, 10);\n const data = (await response).data;\n const hotelIds = data.map((hotel) => hotel.id);\n const rates = (\n await sdk.getFullRates({\n hotelIds: hotelIds,\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n rates.forEach((rate) => {\n rate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n });\n\n res.json({ rates });\n } catch (error) {\n console.error(\"Error searching for hotels:\", error);\n res.status(500).json({ error: \"Internal server error\" });\n }\n});\n\napp.get(\"/search-rates\", async (req, res) => {\n console.log(\"Rate endpoint hit\");\n const { checkin, checkout, adults, hotelId, environment } = req.query;\n const apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n const sdk = liteApi(apiKey);\n\n try {\n // Fetch rates only for the specified hotel\n const rates = (\n await sdk.getFullRates({\n hotelIds: [hotelId],\n occupancies: [{ adults: parseInt(adults, 10) }],\n currency: \"USD\",\n guestNationality: \"US\",\n checkin: checkin,\n checkout: checkout,\n })\n ).data;\n\n // Fetch hotel details\n const hotelsResponse = await sdk.getHotelDetails(hotelId);\n const hotelInfo = hotelsResponse.data;\n\n // Prepare the response data\n const rateInfo = rates.map((hotel) =>\n hotel.roomTypes.flatMap((roomType) => {\n // Define the board types we're interested in\n const boardTypes = [\"RO\", \"BI\"];\n\n // Filter rates by board type and sort by refundable tag\n return boardTypes\n .map((boardType) => {\n const filteredRates = roomType.rates.filter((rate) => rate.boardType === boardType);\n\n // Sort to prioritize 'RFN' over 'NRFN'\n const sortedRates = filteredRates.sort((a, b) => {\n if (\n a.cancellationPolicies.refundableTag === \"RFN\" &&\n b.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return -1; // a before b\n } else if (\n b.cancellationPolicies.refundableTag === \"RFN\" &&\n a.cancellationPolicies.refundableTag !== \"RFN\"\n ) {\n return 1; // b before a\n }\n return 0; // no change in order\n });\n\n // Return the first rate meeting the criteria if it exists\n if (sortedRates.length > 0) {\n const rate = sortedRates[0];\n return {\n rateName: rate.name,\n offerId: roomType.offerId,\n board: rate.boardName,\n refundableTag: rate.cancellationPolicies.refundableTag,\n retailRate: rate.retailRate.total[0].amount,\n originalRate: rate.retailRate.suggestedSellingPrice[0].amount,\n };\n }\n return null; // or some default object if no rates meet the criteria\n })\n .filter((rate) => rate !== null); // Filter out null values if no rates meet the criteria\n })\n );\n res.json({ hotelInfo, rateInfo });\n } catch (error) {\n console.error(\"Error fetching rates:\", error);\n res.status(500).json({ error: \"No availability found\" });\n }\n});\n\napp.post(\"/prebook\", async (req, res) => {\n //console.log(req.body);\n const { rateId, environment, voucherCode } = req.body;\n const apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n const sdk = liteApi(apiKey);\n //console.log(apiKey, \"apiKey\");\n const bodyData = {\n offerId: rateId,\n usePaymentSdk: true,\n };\n\n // Conditionally add the voucherCode if it exists in the request body\n if (voucherCode) {\n bodyData.voucherCode = voucherCode;\n }\n\n try {\n // Call the SDK's prebook method and handle the response\n sdk\n .preBook(bodyData)\n .then((response) => {\n res.json({ success: response }); // Send response back to the client\n })\n .catch((err) => {\n console.error(\"Error:\", err); // Print the error if any\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n });\n } catch (err) {\n console.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n res.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n }\n});\n\napp.get(\"/book\", (req, res) => {\n console.log(req.query);\n const { prebookId, guestFirstName, guestLastName, guestEmail, transactionId, environment } =\n req.query;\n\n const apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n const sdk = liteApi(apiKey);\n\n\t// Prepare the booking data\n const bodyData = {\n holder: {\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n payment: {\n method: \"TRANSACTION_ID\",\n transactionId: transactionId,\n },\n prebookId: prebookId,\n guests: [\n {\n occupancyNumber: 1,\n remarks: \"\",\n firstName: guestFirstName,\n lastName: guestLastName,\n email: guestEmail,\n },\n ],\n };\n\n console.log(bodyData);\n\n sdk\n .book(bodyData)\n .then((data) => {\n if (!data || data.error) {\n // Validate if there's any error in the data\n throw new Error(\n \"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\")\n );\n }\n\n console.log(data);\n\n res.send(`\n <!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n</head>\n<body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${\n data.data.bookedRooms[0].rate.retailRate.total.currency\n }</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${\n data.data.bookedRooms[0].children\n } Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${\n data.data.bookedRooms[0].lastName\n }</p>\n </div>\n<div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n : \"Not specified\"\n }</p>\n <p>Cancellation Fee: ${\n data.data.cancellationPolicies &&\n data.data.cancellationPolicies.cancelPolicyInfos &&\n data.data.cancellationPolicies.cancelPolicyInfos[0]\n ? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n : \"Not specified\"\n }</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n</div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n</body>\n</html>\n `);\n })\n .catch((err) => {\n console.error(\"Error during booking:\", err);\n res.status(500).send(`Failed to book: ${err.message}`);\n });\n});\n\n// Serve the client-side application\napp.get(\"/\", (req, res) => {\n res.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\napp.use(express.static(path.join(__dirname, \"../client\")));\n\nconst port = 3000;\n\napp.listen(port, () => {\n console.log(`Server is running on port ${port}`);\n});\n"}, "7ca151d723dacd60a413b6842557db2e559a39e3": {"t": 1785766831.8953862, "n": 8693, "h": "b44ee68f077302e6", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire('dotenv').config()\n\napp.use(\n\tcors({\n\t\torigin: \"*\",\n\t})\n);\n\nconst prod_apiKey = process.env.PROD_API_KEY; // Replace with your LiteAPI key\nconst sandbox_apiKey = process.env.SAND_API_KEY; // Replace with your LiteAPI key\napp.use(bodyParser.json());\n\n\napp.get(\"/search-hotels\", async (req, res) => {\n\tconsole.log(\"Search endpoint hit\");\n\tconst { checkin, checkout, adults, city, countryCode, environment } = req.query;\n\tconst apiKey = environment == \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\tconst sdk = liteApi(apiKey);\n\n\ttry {\n\t\tconst response = await sdk.getHotels(countryCode, city, 0, 10);\n\t\tconst data = (await response).data;\n\t\tconst hotelIds = data.map((hotel) => hotel.id);\n\t\tconst rates = (\n\t\t\tawait sdk.getFullRates({\n\t\t\t\thotelIds: hotelIds,\n\t\t\t\toccupancies: [{ adults: parseInt(adults, 10) }],\n\t\t\t\tcurrency: 'USD',\n\t\t\t\tguestNationality: 'US',\n\t\t\t\tcheckin: checkin,\n\t\t\t\tcheckout: checkout\n\t\t\t})\n\t\t).data;\n\t\trates.forEach((rate) => {\n\t\t\trate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n\t\t});\n\n\t\tres.json({ rates });\n\t} catch (error) {\n\t\tconsole.error(\"Error searching for hotels:\", error);\n\t\tres.status(500).json({ error: \"Internal server error\" });\n\t}\n});\n\napp.get(\"/search-rates\", async (req, res) => {\n\tconsole.log(\"Rate endpoint hit\");\n\tconst { checkin, checkout, adults, hotelId, environment } = req.query;\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\tconst sdk = liteApi(apiKey);\n\n\ttry {\n\t\t// Fetch rates only for the specified hotel\n\t\tconst rates = (\n\t\t\tawait sdk.getFullRates({\n\t\t\t\thotelIds: [hotelId],\n\t\t\t\toccupancies: [{ adults: parseInt(adults, 10) }],\n\t\t\t\tcurrency: 'USD',\n\t\t\t\tguestNationality: 'US',\n\t\t\t\tcheckin: checkin,\n\t\t\t\tcheckout: checkout\n\t\t\t})\n\t\t).data;\n\n\t\t// Fetch hotel details\n\t\tconst hotelsResponse = await sdk.getHotelDetails(hotelId);\n\t\tconst hotelInfo = hotelsResponse.data;\n\n\t\t// Prepare the response data\n\t\tconst rateInfo = rates.map(hotel =>\n\t\t\thotel.roomTypes.flatMap(roomType => {\n\t\t\t\t// Define the board types we're interested in\n\t\t\t\tconst boardTypes = [\"RO\", \"BI\"];\n\n\t\t\t\t// Filter rates by board type and sort by refundable tag\n\t\t\t\treturn boardTypes.map(boardType => {\n\t\t\t\t\tconst filteredRates = roomType.rates.filter(rate =>\n\t\t\t\t\t\trate.boardType === boardType\n\t\t\t\t\t);\n\n\t\t\t\t\t// Sort to prioritize 'RFN' over 'NRFN'\n\t\t\t\t\tconst sortedRates = filteredRates.sort((a, b) => {\n\t\t\t\t\t\tif (a.cancellationPolicies.refundableTag === 'RFN' && b.cancellationPolicies.refundableTag !== 'RFN') {\n\t\t\t\t\t\t\treturn -1; // a before b\n\t\t\t\t\t\t} else if (b.cancellationPolicies.refundableTag === 'RFN' && a.cancellationPolicies.refundableTag !== 'RFN') {\n\t\t\t\t\t\t\treturn 1; // b before a\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn 0; // no change in order\n\t\t\t\t\t});\n\n\t\t\t\t\t// Return the first rate meeting the criteria if it exists\n\t\t\t\t\tif (sortedRates.length > 0) {\n\t\t\t\t\t\tconst rate = sortedRates[0];\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\trateName: rate.name,\n\t\t\t\t\t\t\tofferId: roomType.offerId,\n\t\t\t\t\t\t\tboard: rate.boardName,\n\t\t\t\t\t\t\trefundableTag: rate.cancellationPolicies.refundableTag,\n\t\t\t\t\t\t\tretailRate: rate.retailRate.total[0].amount,\n\t\t\t\t\t\t\toriginalRate:rate.retailRate.suggestedSellingPrice[0].amount\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\treturn null; // or some default object if no rates meet the criteria\n\t\t\t\t}).filter(rate => rate !== null); // Filter out null values if no rates meet the criteria\n\t\t\t})\n\t\t);\n\t\tres.json({ hotelInfo, rateInfo });\n\t} catch (error) {\n\t\tconsole.error(\"Error fetching rates:\", error);\n\t\tres.status(500).json({ error: \"No availability found\" });\n\t}\n});\n\napp.post(\"/prebook\", async (req, res) => {\n\t//console.log(req.body);\n\tconst { rateId, environment, voucherCode } = req.body;\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\tconst sdk = liteApi(apiKey);\n\t//console.log(apiKey, \"apiKey\");\n\tconst bodyData = {\n\t\tofferId: rateId,\n\t\tusePaymentSdk: true\n\t};\n\n\t// Conditionally add the voucherCode if it exists in the request body\n\tif (voucherCode) {\n\t\tbodyData.voucherCode = voucherCode;\n\t}\n\n/* \tconst options = {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\taccept: \"application/json\",\n\t\t\t\"content-type\": \"application/json\",\n\t\t\t\"X-API-Key\": apiKey,\n\t\t},\n\t\tbody: JSON.stringify(bodyData),\n\t}; */\n\ttry {\n\t\t// Call the SDK's prebook method and handle the response\n\t\tsdk.preBook(bodyData)\n\t\t\t.then(response => {\n\t\t\t\tres.json({ success: response }); // Send response back to the client\n\t\t\t})\n\t\t\t.catch(err => {\n\t\t\t\tconsole.error(\"Error:\", err); // Print the error if any\n\t\t\t\tres.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n\t\t\t});\n\t} catch (err) {\n\t\tconsole.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n\t\tres.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n\t}\n});\n\n\napp.get(\"/book\", (req, res) => {\n\tconsole.log(req.query);\n\tconst {\n\t\tprebookId,\n\t\tguestFirstName,\n\t\tguestLastName,\n\t\tguestEmail,\n\t\ttransactionId,\n\t\tenvironment\n\t} = req.query;\n\n\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\tconst sdk = liteApi(apiKey);\n\n\n\tconst bodyData = {\n\t\tholder: {\n\t\t\tfirstName: guestFirstName,\n\t\t\tlastName: guestLastName,\n\t\t\temail: guestEmail,\n\t\t},\n\t\tpayment: {\n\t\t\tmethod: \"TRANSACTION_ID\",\n\t\t\ttransactionId: transactionId,\n\t\t},\n\t\tprebookId: prebookId,\n\t\tguests: [\n\t\t\t{\n\t\t\t\toccupancyNumber: 1,\n\t\t\t\tremarks: \"\",\n\t\t\t\tfirstName: guestFirstName,\n\t\t\t\tlastName: guestLastName,\n\t\t\t\temail: guestEmail,\n\t\t\t},\n\t\t],\n\t};\n\n\tconsole.log(bodyData)\n\n\tsdk.book(bodyData)\n\t\t.then((data) => {\n\t\t\tif (!data || data.error) { // Validate if there's any error in the data\n\t\t\t\tthrow new Error(\"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\"));\n\t\t\t}\n\t\t\tconsole.log(data);\n\t\t\tres.send(`\n <!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n</head>\n<body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${data.data.bookedRooms[0].rate.retailRate.total.currency}</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${data.data.bookedRooms[0].children} Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${data.data.bookedRooms[0].lastName}</p>\n </div>\n<div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${data.data.cancellationPolicies && data.data.cancellationPolicies.cancelPolicyInfos && data.data.cancellationPolicies.cancelPolicyInfos[0]\n\t\t\t\t\t? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n\t\t\t\t\t: \"Not specified\"\n\t\t\t\t}</p>\n <p>Cancellation Fee: ${data.data.cancellationPolicies && data.data.cancellationPolicies.cancelPolicyInfos && data.data.cancellationPolicies.cancelPolicyInfos[0]\n\t\t\t\t\t? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n\t\t\t\t\t: \"Not specified\"\n\t\t\t\t}</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n</div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n</body>\n</html>\n `);\n\t\t})\n\t\t.catch((err) => {\n\t\t\tconsole.error(\"Error during booking:\", err);\n\t\t\tres.status(500).send(`Failed to book: ${err.message}`);\n\t\t});\n});\n\napp.get(\"/\", (req, res) => {\n\tres.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\napp.use(express.static(path.join(__dirname, \"../client\")));\n\nconst port = 3000;\napp.listen(port, () => {\n\tconsole.log(`Server is running on port ${port}`);\n});\n"}, "d531b0d834d30aca5f6f7e71f45e3e369c2cc1d5": {"t": 1785766833.102332, "n": 8608, "h": "b7722e530e160798", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-node-sdk\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire('dotenv').config()\n\napp.use(\n\tcors({\n\t\torigin: \"*\",\n\t})\n);\n\nconst prod_apiKey = process.env.PROD_API_KEY; // Replace with your LiteAPI key\nconst sandbox_apiKey = process.env.SAND_API_KEY; // Replace with your LiteAPI key\napp.use(bodyParser.json());\n\n\napp.get(\"/search-hotels\", async (req, res) => {\n\tconsole.log(\"Search endpoint hit\");\n\tconst { checkin, checkout, adults, city, countryCode, environment } = req.query;\n\tconst apiKey = environment == \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\tconst sdk = liteApi(apiKey);\n\n\ttry {\n\t\tconst response = await sdk.getHotels(countryCode, city, 0, 10);\n\t\tconst data = (await response).data;\n\t\tconst hotelIds = data.map((hotel) => hotel.id);\n\t\tconst rates = (\n\t\t\tawait sdk.getFullRates({\n\t\t\t\thotelIds: hotelIds,\n\t\t\t\toccupancies: [{ adults: parseInt(adults, 10) }],\n\t\t\t\tcurrency: 'USD',\n\t\t\t\tguestNationality: 'US',\n\t\t\t\tcheckin: checkin,\n\t\t\t\tcheckout: checkout\n\t\t\t})\n\t\t).data;\n\t\trates.forEach((rate) => {\n\t\t\trate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n\t\t});\n\n\t\tres.json({ rates });\n\t} catch (error) {\n\t\tconsole.error(\"Error searching for hotels:\", error);\n\t\tres.status(500).json({ error: \"Internal server error\" });\n\t}\n});\n\napp.get(\"/search-rates\", async (req, res) => {\n\tconsole.log(\"Rate endpoint hit\");\n\tconst { checkin, checkout, adults, hotelId, environment } = req.query;\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\tconst sdk = liteApi(apiKey);\n\n\ttry {\n\t\t// Fetch rates only for the specified hotel\n\t\tconst ratesResponse = await sdk.getFullRates(checkin, checkout, \"USD\", \"US\", [hotelId], adults);\n\t\tconst rates = ratesResponse.data; // This should contain the rate details\n\n\t\t// Assuming you have a function or method to fetch the hotel data\n\t\tconst hotelsResponse = await sdk.getHotelDetails(hotelId);\n\t\tconst hotelInfo = hotelsResponse.data;\n\n\t\t// Prepare the response data\n\t\tconst rateInfo = rates.map(hotel =>\n\t\t\thotel.roomTypes.flatMap(roomType => {\n\t\t\t\t// Define the board types we're interested in\n\t\t\t\tconst boardTypes = [\"RO\", \"BI\"];\n\n\t\t\t\t// Filter rates by board type and sort by refundable tag\n\t\t\t\treturn boardTypes.map(boardType => {\n\t\t\t\t\tconst filteredRates = roomType.rates.filter(rate =>\n\t\t\t\t\t\trate.boardType === boardType\n\t\t\t\t\t);\n\n\t\t\t\t\t// Sort to prioritize 'RFN' over 'NRFN'\n\t\t\t\t\tconst sortedRates = filteredRates.sort((a, b) => {\n\t\t\t\t\t\tif (a.cancellationPolicies.refundableTag === 'RFN' && b.cancellationPolicies.refundableTag !== 'RFN') {\n\t\t\t\t\t\t\treturn -1; // a before b\n\t\t\t\t\t\t} else if (b.cancellationPolicies.refundableTag === 'RFN' && a.cancellationPolicies.refundableTag !== 'RFN') {\n\t\t\t\t\t\t\treturn 1; // b before a\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn 0; // no change in order\n\t\t\t\t\t});\n\n\t\t\t\t\t// Return the first rate meeting the criteria if it exists\n\t\t\t\t\tif (sortedRates.length > 0) {\n\t\t\t\t\t\tconst rate = sortedRates[0];\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\trateName: rate.name,\n\t\t\t\t\t\t\tofferId: roomType.offerId,\n\t\t\t\t\t\t\tboard: rate.boardName,\n\t\t\t\t\t\t\trefundableTag: rate.cancellationPolicies.refundableTag,\n\t\t\t\t\t\t\tretailRate: rate.retailRate.total[0].amount\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\treturn null; // or some default object if no rates meet the criteria\n\t\t\t\t}).filter(rate => rate !== null); // Filter out null values if no rates meet the criteria\n\t\t\t})\n\t\t);\n\t\tres.json({ hotelInfo, rateInfo });\n\t} catch (error) {\n\t\tconsole.error(\"Error fetching rates:\", error);\n\t\tres.status(500).json({ error: \"No availability found\" });\n\t}\n});\n\napp.post(\"/prebook\", async (req, res) => {\n\t//console.log(req.body);\n\tconst { rateId, environment, voucherCode } = req.body;\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\tconst sdk = liteApi(apiKey);\n\t//console.log(apiKey, \"apiKey\");\n\tconst bodyData = {\n\t\tofferId: rateId,\n\t\tusePaymentSdk: true\n\t};\n\n\t// Conditionally add the voucherCode if it exists in the request body\n\tif (voucherCode) {\n\t\tbodyData.voucherCode = voucherCode;\n\t}\n\n/* \tconst options = {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\taccept: \"application/json\",\n\t\t\t\"content-type\": \"application/json\",\n\t\t\t\"X-API-Key\": apiKey,\n\t\t},\n\t\tbody: JSON.stringify(bodyData),\n\t}; */\n\ttry {\n\t\t// Call the SDK's prebook method and handle the response\n\t\tsdk.preBook(bodyData)\n\t\t\t.then(response => {\n\t\t\t\tres.json({ success: response }); // Send response back to the client\n\t\t\t})\n\t\t\t.catch(err => {\n\t\t\t\tconsole.error(\"Error:\", err); // Print the error if any\n\t\t\t\tres.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n\t\t\t});\n\t} catch (err) {\n\t\tconsole.error(\" Prebook error:\", err); // Handle errors related to SDK usage\n\t\tres.status(500).json({ error: \"Internal Server Error\" }); // Send error response\n\t}\n});\n\n\napp.get(\"/book\", (req, res) => {\n\tconsole.log(req.query);\n\tconst {\n\t\tprebookId,\n\t\tguestFirstName,\n\t\tguestLastName,\n\t\tguestEmail,\n\t\ttransactionId,\n\t\tenvironment\n\t} = req.query;\n\n\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\tconst sdk = liteApi(apiKey);\n\n\n\tconst bodyData = {\n\t\tholder: {\n\t\t\tfirstName: guestFirstName,\n\t\t\tlastName: guestLastName,\n\t\t\temail: guestEmail,\n\t\t},\n\t\tpayment: {\n\t\t\tmethod: \"TRANSACTION_ID\",\n\t\t\ttransactionId: transactionId,\n\t\t},\n\t\tprebookId: prebookId,\n\t\tguests: [\n\t\t\t{\n\t\t\t\toccupancyNumber: 1,\n\t\t\t\tremarks: \"\",\n\t\t\t\tfirstName: guestFirstName,\n\t\t\t\tlastName: guestLastName,\n\t\t\t\temail: guestEmail,\n\t\t\t},\n\t\t],\n\t};\n\n\tconsole.log(bodyData)\n\n\tsdk.book(bodyData)\n\t\t.then((data) => {\n\t\t\tif (!data || data.error) { // Validate if there's any error in the data\n\t\t\t\tthrow new Error(\"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\"));\n\t\t\t}\n\t\t\tconsole.log(data);\n\t\t\tres.send(`\n <!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n</head>\n<body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${data.data.bookedRooms[0].rate.retailRate.total.currency}</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${data.data.bookedRooms[0].children} Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${data.data.bookedRooms[0].lastName}</p>\n </div>\n<div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${data.data.cancellationPolicies && data.data.cancellationPolicies.cancelPolicyInfos && data.data.cancellationPolicies.cancelPolicyInfos[0]\n\t\t\t\t\t? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n\t\t\t\t\t: \"Not specified\"\n\t\t\t\t}</p>\n <p>Cancellation Fee: ${data.data.cancellationPolicies && data.data.cancellationPolicies.cancelPolicyInfos && data.data.cancellationPolicies.cancelPolicyInfos[0]\n\t\t\t\t\t? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n\t\t\t\t\t: \"Not specified\"\n\t\t\t\t}</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n</div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n</body>\n</html>\n `);\n\t\t})\n\t\t.catch((err) => {\n\t\t\tconsole.error(\"Error during booking:\", err);\n\t\t\tres.status(500).send(`Failed to book: ${err.message}`);\n\t\t});\n});\n\napp.get(\"/\", (req, res) => {\n\tres.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\napp.use(express.static(path.join(__dirname, \"../client\")));\n\nconst port = 3000;\napp.listen(port, () => {\n\tconsole.log(`Server is running on port ${port}`);\n});\n"}, "723ac79c9681e37272e6433ced99c9316978d0b5": {"t": 1785766835.6652782, "n": 8685, "h": "b856eb2b85244fe0", "c": "const express = require(\"express\");\nconst app = express();\nconst bodyParser = require(\"body-parser\");\nconst liteApi = require(\"liteapi-travel\");\nconst cors = require(\"cors\");\nconst path = require(\"path\");\nrequire('dotenv').config()\n\napp.use(\n\tcors({\n\t\torigin: \"*\",\n\t})\n);\n\nconst prod_apiKey = process.env.PROD_API_KEY; // Replace with your LiteAPI key\nconst sandbox_apiKey = process.env.SAND_API_KEY; // Replace with your LiteAPI key\nconsole.log(process.env.SAND_API_KEY);\n\napp.use(bodyParser.json());\n\napp.get(\"/search-hotels\", async (req, res) => {\n\tconsole.log(\"Search endpoint hit\");\n\tconst { checkin, checkout, adults, city, countryCode, environment } = req.query;\n\tconst apiKey = environment == \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\n\t//console.log (apiKey, \"apiKey\");\n\tconst sdk = liteApi(apiKey);\n\n\ttry {\n\t\tconst response = await sdk.getHotels(countryCode, city, 0, 10);\n\t\tconst data = (await response).data;\n\t\tconst hotelIds = data.map((hotel) => hotel.id);\n\t\tconst rates = (\n\t\t\tawait sdk.getFullRates(checkin, checkout, \"USD\", \"US\", hotelIds, adults)\n\t\t).data;\n\n\t\trates.forEach((rate) => {\n\t\t\trate.hotel = data.find((hotel) => hotel.id === rate.hotelId);\n\t\t});\n\n\t\tres.json({ rates });\n\t} catch (error) {\n\t\tconsole.error(\"Error searching for hotels:\", error);\n\t\tres.status(500).json({ error: \"Internal server error\" });\n\t}\n});\n\napp.get(\"/search-rates\", async (req, res) => {\n\tconsole.log(\"Rate endpoint hit\");\n\tconsole.log(req.query);\n\tconst { checkin, checkout, adults, hotelId, environment } = req.query;\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\n\tconst sdk = liteApi(apiKey);\n\n\ttry {\n\t\t// Fetch rates only for the specified hotel\n\t\tconst ratesResponse = await sdk.getFullRates(checkin, checkout, \"USD\", \"US\", [hotelId], adults);\n\t\tconst rates = ratesResponse.data; // This should contain the rate details\n\n\t\t// Assuming you have a function or method to fetch the hotel data\n\t\tconst hotelsResponse = await sdk.getHotelDetails(hotelId);\n\t\tconst hotelInfo = hotelsResponse.data;\n\n\t\t// Prepare the response data\n\t\tconst rateInfo = rates.map(hotel =>\n\t\t\thotel.roomTypes.flatMap(roomType => {\n\t\t\t\t// Define the board types we're interested in\n\t\t\t\tconst boardTypes = [\"RO\", \"BI\"];\n\n\t\t\t\t// Filter rates by board type and sort by refundable tag\n\t\t\t\treturn boardTypes.map(boardType => {\n\t\t\t\t\tconst filteredRates = roomType.rates.filter(rate =>\n\t\t\t\t\t\trate.boardType === boardType\n\t\t\t\t\t);\n\n\t\t\t\t\t// Sort to prioritize 'RFN' over 'NRFN'\n\t\t\t\t\tconst sortedRates = filteredRates.sort((a, b) => {\n\t\t\t\t\t\tif (a.cancellationPolicies.refundableTag === 'RFN' && b.cancellationPolicies.refundableTag !== 'RFN') {\n\t\t\t\t\t\t\treturn -1; // a before b\n\t\t\t\t\t\t} else if (b.cancellationPolicies.refundableTag === 'RFN' && a.cancellationPolicies.refundableTag !== 'RFN') {\n\t\t\t\t\t\t\treturn 1; // b before a\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn 0; // no change in order\n\t\t\t\t\t});\n\n\t\t\t\t\t// Return the first rate meeting the criteria if it exists\n\t\t\t\t\tif (sortedRates.length > 0) {\n\t\t\t\t\t\tconst rate = sortedRates[0];\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\trateName: rate.name,\n\t\t\t\t\t\t\tofferId: roomType.offerId,\n\t\t\t\t\t\t\tboard: rate.boardName,\n\t\t\t\t\t\t\trefundableTag: rate.cancellationPolicies.refundableTag,\n\t\t\t\t\t\t\tretailRate: rate.retailRate.total[0].amount\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\treturn null; // or some default object if no rates meet the criteria\n\t\t\t\t}).filter(rate => rate !== null); // Filter out null values if no rates meet the criteria\n\t\t\t})\n\t\t);\n\n\t\tconsole.log(rateInfo);\n\n\t\tres.json({ hotelInfo, rateInfo });\n\t} catch (error) {\n\t\tconsole.error(\"Error fetching rates:\", error);\n\t\tres.status(500).json({ error: \"No availability found\" });\n\t}\n});\n\napp.post(\"/prebook\", async (req, res) => {\n\tconst { rateId, environment, voucherCode } = req.body;\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\t//console.log(apiKey, \"apiKey\");\n\tconst bodyData = {\n\t\tofferId: rateId,\n\t\tusePaymentSdk: true\n\t};\n\n\t// Conditionally add the voucherCode if it exists in the request body\n\tif (voucherCode) {\n\t\tbodyData.voucherCode = voucherCode;\n\t}\n\n\tconst options = {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\taccept: \"application/json\",\n\t\t\t\"content-type\": \"application/json\",\n\t\t\t\"X-API-Key\": apiKey,\n\t\t},\n\t\tbody: JSON.stringify(bodyData),\n\t};\n\tconsole.log(options);\n\ttry {\n\t\tfetch(\"https://book.liteapi.travel/v3.0/rates/prebook?timeout=4\", options)\n\t\t\t.then((response) => response.json())\n\t\t\t.then((response) => {\n\t\t\t\tconsole.log(\"Response:\", response); // Print the response\n\t\t\t\tres.json({ success: response });\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tconsole.error(\"Error:\", err); // Print the error if any\n\t\t\t});\n\t} catch (err) {\n\t\tconsole.error(\"Fetch error:\", err); // Handle fetch errors\n\t}\n});\n\n\napp.get(\"/book\", (req, res) => {\n\tconsole.log(req.query);\n\tconst {\n\t\tprebookId,\n\t\tguestFirstName,\n\t\tguestLastName,\n\t\tguestEmail,\n\t\ttransactionId,\n\t\tenvironment\n\t} = req.query;\n\n\n\tconst apiKey = environment === \"sandbox\" ? sandbox_apiKey : prod_apiKey;\n\n\tconst options = {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\taccept: \"application/json\",\n\t\t\t\"content-type\": \"application/json\",\n\t\t\t\"X-API-Key\": apiKey,\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tholder: {\n\t\t\t\tfirstName: guestFirstName,\n\t\t\t\tlastName: guestLastName,\n\t\t\t\temail: guestEmail,\n\t\t\t},\n\t\t\tpayment: {\n\t\t\t\tmethod: \"TRANSACTION_ID\",\n\t\t\t\ttransactionId: transactionId,\n\t\t\t},\n\t\t\tguests: [\n\t\t\t\t{\n\t\t\t\t\toccupancyNumber: 1,\n\t\t\t\t\tremarks: \"\",\n\t\t\t\t\tfirstName: guestFirstName,\n\t\t\t\t\tlastName: guestLastName,\n\t\t\t\t\temail: guestEmail,\n\t\t\t\t},\n\t\t\t],\n\t\t\tprebookId: prebookId,\n\t\t}),\n\t};\n\n\tconsole.log(options);\n\n\tfetch(\"https://book.liteapi.travel/v3.0/rates/book?timeout=4\", options)\n\t\t.then((response) => response.json())\n\t\t.then((data) => {\n\t\t\tif (!data || data.error) { // Validate if there's any error in the data\n\t\t\t\tthrow new Error(\"Error in booking data: \" + (data.error ? data.error.message : \"Unknown error\"));\n\t\t\t}\n\t\t\tconsole.log(data);\n\t\t\tres.send(`\n <!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Booking Confirmation</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n margin: 20px;\n }\n h1 {\n color: #333;\n }\n .booking-details, .room-details, .policy-details {\n margin-bottom: 20px;\n padding: 10px;\n border: 1px solid #ccc;\n border-radius: 5px;\n }\n .header {\n font-weight: bold;\n color: #444;\n }\n </style>\n</head>\n<body>\n <h1>Booking Confirmation</h1>\n <div class=\"booking-details\">\n <div class=\"header\">Booking Information:</div>\n <p>Booking ID: ${data.data.bookingId}</p>\n <p>Supplier Name: ${data.data.supplierBookingName} (${data.data.supplier})</p>\n <p>Status: ${data.data.status}</p>\n <p>Check-in: ${data.data.checkin}</p>\n <p>Check-out: ${data.data.checkout}</p>\n <p>Hotel: ${data.data.hotel.name} (ID: ${data.data.hotel.hotelId})</p>\n </div>\n\n <div class=\"room-details\">\n <div class=\"header\">Room Details:</div>\n <p>Room Type: ${data.data.bookedRooms[0].roomType.name}</p>\n <p>Rate (Total): $${data.data.bookedRooms[0].rate.retailRate.total.amount} ${data.data.bookedRooms[0].rate.retailRate.total.currency}</p>\n <p>Occupancy: ${data.data.bookedRooms[0].adults} Adult(s), ${data.data.bookedRooms[0].children} Child(ren)</p>\n <p>Guest Name: ${data.data.bookedRooms[0].firstName} ${data.data.bookedRooms[0].lastName}</p>\n </div>\n<div class=\"policy-details\">\n <div class=\"header\">Cancellation Policy:</div>\n <p>Cancel By: ${data.data.cancellationPolicies && data.data.cancellationPolicies.cancelPolicyInfos && data.data.cancellationPolicies.cancelPolicyInfos[0]\n\t\t\t\t\t? data.data.cancellationPolicies.cancelPolicyInfos[0].cancelTime\n\t\t\t\t\t: \"Not specified\"\n\t\t\t\t}</p>\n <p>Cancellation Fee: ${data.data.cancellationPolicies && data.data.cancellationPolicies.cancelPolicyInfos && data.data.cancellationPolicies.cancelPolicyInfos[0]\n\t\t\t\t\t? `$${data.data.cancellationPolicies.cancelPolicyInfos[0].amount}`\n\t\t\t\t\t: \"Not specified\"\n\t\t\t\t}</p>\n <p>Remarks: ${data.data.remarks || \"No additional remarks.\"}</p>\n</div>\n\n <a href=\"/\"><button>Back to Hotels</button></a>\n</body>\n</html>\n `);\n\t\t})\n\t\t.catch((err) => {\n\t\t\tconsole.error(\"Error during booking:\", err);\n\t\t\tres.status(500).send(`Failed to book: ${err.message}`);\n\t\t});\n});\n\napp.get(\"/\", (req, res) => {\n\tres.sendFile(path.join(__dirname, \"../client/index.html\"));\n});\n\napp.use(express.static(path.join(__dirname, \"../client\")));\n\nconst port = 3000;\napp.listen(port, () => {\n\tconsole.log(`Server is running on port ${port}`);\n});\n"}}}} |