const express = require('express'); const cors = require('cors'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const { calculatePlanetaryLines, getTimezone } = require('./astrology-engine'); const { geocodeLocation } = require('./geocode'); const PDFDocument = require('pdfkit'); const path = require('path'); const app = express(); app.use(cors()); app.use(express.json()); app.use(express.static('.')); // Database simulation (use PostgreSQL in production) const readings = new Map(); const payments = new Map(); // Calculate astrocartography using real Swiss Ephemeris calculations app.post('/api/calculate-astrocartography', async (req, res) => { try { const { date, time, location, email } = req.body; // 1. Geocode birth location const geoData = await geocodeLocation(location); if (!geoData || !geoData.lat || !geoData.lng) { throw new Error('Failed to geocode location'); } // 2. Create proper UTC date from birth data // Parse date and time const [year, month, day] = date.split('-').map(Number); const [hours, minutes] = time.split(':').map(Number); // Create date in local timezone then convert to UTC equivalent // For precise calculations, we need the exact moment in UTC const birthDate = new Date(Date.UTC(year, month - 1, day, hours - geoData.offset, minutes)); // 3. Calculate planetary lines using Swiss Ephemeris console.log(`Calculating chart for ${email} - ${date} ${time} at ${geoData.lat},${geoData.lng}`); const planetaryData = await calculatePlanetaryLines({ date: birthDate, lat: geoData.lat, lng: geoData.lng, timezone: geoData.timezone }); // 4. Generate summary recommendations const recommendations = generateRecommendations(planetaryData, geoData); // 5. Save reading to database const readingId = Date.now().toString(36) + Math.random().toString(36).substr(2); readings.set(readingId, { email, date: new Date(), birthData: { date, time, location, lat: geoData.lat, lng: geoData.lng }, planetaryData, recommendations }); // 6. Format response for frontend const formattedLines = planetaryData.map(planet => ({ planet: planet.planet, color: planet.color, longitude: planet.longitude, latitude: planet.latitude, retrograde: planet.retrograde, strongestAngle: planet.strongestAngle, closestPoint: planet.coordinates, meaning: planet.meaning, nearbyCities: planet.nearbyCities, interpretations: planet.interpretations, // Include line coordinates for mapping lines: { MC: planet.lines.MC.slice(0, 35), // Sample every 5 degrees from 85 to -85 IC: planet.lines.IC.slice(0, 35), ASC: planet.lines.ASC, // Variable length based on visibility DSC: planet.lines.DSC } })); res.json({ success: true, reportId: readingId, birthLocation: { name: geoData.displayName, lat: geoData.lat, lng: geoData.lng, timezone: geoData.timezone }, planetaryLines: formattedLines, recommendations, calculationMethod: 'Swiss Ephemeris 2.10', ephemerisDate: new Date().toISOString() }); } catch (error) { console.error('Calculation error:', error); res.status(500).json({ success: false, error: 'Calculation failed: ' + error.message }); } }); // Stripe payment intent creation app.post('/api/create-payment-intent', async (req, res) => { try { const { amount, email, readingId } = req.body; const paymentIntent = await stripe.paymentIntents.create({ amount: amount, // amount in cents (2900 for $29) currency: 'usd', receipt_email: email, metadata: { service: 'astrocartography', readingId: readingId || 'pending' } }); // Store payment intent mapping payments.set(paymentIntent.id, { email, readingId: readingId || null, status: 'pending' }); res.json({ success: true, clientSecret: paymentIntent.client_secret }); } catch (error) { console.error('Payment error:', error); res.status(500).json({ success: false, error: error.message }); } }); // Stripe webhook for payment confirmation app.post('/api/webhook', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); } catch (err) { return res.status(400).send(`Webhook Error: ${err.message}`); } if (event.type === 'payment_intent.succeeded') { const paymentIntent = event.data.object; const paymentRecord = payments.get(paymentIntent.id); if (paymentRecord && paymentRecord.readingId) { // Update reading as paid const reading = readings.get(paymentRecord.readingId); if (reading) { reading.paid = true; reading.paymentDate = new Date(); } } } res.json({received: true}); }); // Generate and download PDF report app.get('/api/download-report/:id', async (req, res) => { const reading = readings.get(req.params.id); if (!reading) return res.status(404).send('Reading not found'); if (!reading.paid) return res.status(403).send('Payment required'); const doc = new PDFDocument({ margin: 50 }); res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `attachment; filename=cosmic-map-${req.params.id}.pdf`); doc.pipe(res); // Header doc.fontSize(28).fillColor('#1e1b4b').text('My Cosmic Map', 50, 50); doc.fontSize(16).fillColor('#666').text('Personal Astrocartography Report', 50, 85); doc.moveTo(50, 110).lineTo(550, 110).stroke('#8b5cf6'); // Meta info doc.fontSize(12).fillColor('#333'); doc.text(`Generated: ${reading.date.toLocaleDateString()}`, 50, 130); doc.text(`Birth Location: ${reading.birthData.location}`, 50, 145); doc.text(`Birth Time: ${reading.birthData.date} ${reading.birthData.time}`, 50, 160); doc.text(`Calculation System: Swiss Ephemeris 2.10`, 50, 175); let y = 210; // Planetary Lines reading.planetaryData.forEach((planet, index) => { if (y > 700) { doc.addPage(); y = 50; } // Planet header with color indicator doc.circle(60, y + 8, 5).fill(planet.color || '#666'); doc.fontSize(18).fillColor('#1e1b4b').text(`${planet.planet} Line`, 75, y); doc.fontSize(10).fillColor('#666').text(`Ecliptic: ${planet.longitude}°${planet.retrograde ? ' (R)' : ''}`, 200, y + 5); y += 30; // Strongest angle doc.fontSize(12).fillColor('#8b5cf6').text(`Strongest Influence: ${planet.strongestAngle} Angle`, 75, y); y += 20; // Nearby cities if (planet.nearbyCities && planet.nearbyCities.length > 0) { doc.fontSize(11).fillColor('#333').text('Key Locations:', 75, y); y += 15; planet.nearbyCities.forEach(city => { doc.fontSize(10).fillColor('#666').text(`• ${city.name} (${city.angle} line, ${city.distance}° away)`, 90, y); y += 12; }); y += 10; } // Interpretation for strongest angle const interp = planet.interpretations[planet.strongestAngle]; doc.fontSize(10).fillColor('#333').text(interp, 75, y, { width: 450, align: 'left' }); y += 60; // Separator if (index < reading.planetaryData.length - 1) { doc.moveTo(50, y - 10).lineTo(550, y - 10).stroke('#eee'); } }); // Recommendations if (y > 650) { doc.addPage(); y = 50; } doc.fontSize(18).fillColor('#1e1b4b').text('Summary Recommendations', 50, y); y += 30; const recs = reading.recommendations; doc.fontSize(12).fillColor('#333'); doc.text('Career & Public Life:', 50, y); doc.fontSize(10).fillColor('#666').text(recs.bestForCareer, 70, y + 15, { width: 480 }); y += 50; doc.fontSize(12).fillColor('#333').text('Love & Relationships:', 50, y); doc.fontSize(10).fillColor('#666').text(recs.bestForLove, 70, y + 15, { width: 480 }); y += 50; doc.fontSize(12).fillColor('#333').text('Energy & Vitality:', 50, y); doc.fontSize(10).fillColor('#666').text(recs.bestForEnergy, 70, y + 15, { width: 480 }); // Disclaimer doc.addPage(); doc.fontSize(10).fillColor('#999').text( 'Disclaimer: This astrocartography report is for entertainment and self-exploration purposes only. ' + 'Astrological insights should not replace professional legal, medical, or financial advice. ' + 'Relocation decisions should consider practical factors alongside astrological indications.', 50, 50, { width: 500, align: 'justify' } ); doc.end(); }); // Get reading status app.get('/api/reading/:id', (req, res) => { const reading = readings.get(req.params.id); if (!reading) return res.status(404).json({ error: 'Not found' }); res.json({ paid: reading.paid || false, created: reading.date, planetaryData: reading.planetaryData ? true : false }); }); function generateRecommendations(planetaryData, geoData) { // Find best planets for each life area based on angles const sunData = planetaryData.find(p => p.planet === 'Sun'); const venusData = planetaryData.find(p => p.planet === 'Venus'); const marsData = planetaryData.find(p => p.planet === 'Mars'); const jupiterData = planetaryData.find(p => p.planet === 'Jupiter'); const saturnData = planetaryData.find(p => p.planet === 'Saturn'); const careerPlanet = jupiterData || sunData; const lovePlanet = venusData; const energyPlanet = marsData || sunData; // Find best cities for each const getBestCity = (planetData) => { if (!planetData || !planetData.nearbyCities || planetData.nearbyCities.length === 0) { return null; } return planetData.nearbyCities[0]; }; const careerCity = getBestCity(careerPlanet); const loveCity = getBestCity(lovePlanet); const energyCity = getBestCity(energyPlanet); return { bestForCareer: careerCity ? `Your ${careerPlanet.planet} line (${careerPlanet.strongestAngle}) runs near ${careerCity.name}. ` + `${careerPlanet.interpretations[careerPlanet.strongestAngle]}` : 'Major metropolitan areas align with your career potential. Look for cities where you feel naturally confident.', bestForLove: loveCity ? `Your Venus line (${venusData.strongestAngle}) influences ${loveCity.name}. ` + `${venusData.interpretations[venusData.strongestAngle]}` : 'Coastal and culturally rich cities may enhance your romantic connections and artistic expression.', bestForEnergy: energyCity ? `Your ${energyPlanet.planet} line (${energyPlanet.strongestAngle}) activates ${energyCity.name}. ` + `${energyPlanet.interpretations[energyPlanet.strongestAngle]}` : 'Locations with active lifestyles and outdoor recreation align with your Mars energy.', cautionaryNote: saturnData && saturnData.nearbyCities.length > 0 ? `Note: Your Saturn line (${saturnData.strongestAngle}) passes near ${saturnData.nearbyCities[0].name}. ` + 'This brings lessons and maturity but may feel heavy initially.' : null }; } const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`🌟 Cosmic Map Server running on port ${PORT}`); console.log(`📡 Swiss Ephemeris path: ${path.join(__dirname, 'ephemeris')}`); });