my-cosmic-map / server.js
Katiebuuugg's picture
🐳 13/03 - 03:18 - Let’s tackle phase 1/the real math first
904eb3f verified
Raw
History Blame Contribute Delete
12.8 kB
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')}`);
});