#!/bin/bash # Create project directories mkdir -p project-root/{config,src/{models,services,utils,controllers},assets,data,scripts} # Create .env file cat < project-root/.env # MongoDB configuration MONGODB_URI=mongodb://localhost:27017/seo_data # PostgreSQL configuration PGUSER=yourusername PGHOST=localhost PGDATABASE=yourdatabase PGPASSWORD=yourpassword PGPORT=5432 # Path to Client folder One Drive CLIENT_ONEDRIVE_PATH=/path/to/onedrive/folder EOL # Create config/db.js cat < project-root/config/db.js import { Client } from 'pg'; import mongoose from 'mongoose'; import dotenv from 'dotenv'; dotenv.config(); // MongoDB Configuration mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log('MongoDB connected')) .catch(err => console.error('MongoDB connection error:', err)); // PostgreSQL Configuration const pgClient = new Client({ user: process.env.PGUSER, host: process.env.PGHOST, database: process.env.PGDATABASE, password: process.env.PGPASSWORD, port: process.env.PGPORT, }); pgClient.connect() .then(() => console.log('PostgreSQL connected')) .catch(err => console.error('PostgreSQL connection error:', err)); export { mongoose, pgClient }; EOL # Create config/puppeteer.js cat < project-root/config/puppeteer.js import puppeteer from 'puppeteer'; const launchOptions = { headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], }; export const getBrowserInstance = async () => { const browser = await puppeteer.launch(launchOptions); return browser; }; EOL # Create src/models/SeoData.js cat < project-root/src/models/SeoData.js import mongoose from 'mongoose'; const seoSchema = new mongoose.Schema({ url: String, title: String, seoScore: Number, colors: [String], colorAccessibility: Boolean, isWP: Boolean, wpThemeDeveloper: String, wpPlugins: [String], imgAltDescriptions: [String], }); const SeoData = mongoose.model("SeoData", seoSchema); export default SeoData; EOL # Create src/services/scrapeService.js cat < project-root/src/services/scrapeService.js import puppeteer from 'puppeteer'; import fs from 'fs'; import path from 'path'; import { saveToMongoDB, saveToPostgres } from './dbService'; import { calculateSeoScore } from './seoScore'; import { checkColorAccessibility } from '../utils/helpers'; import { getBrowserInstance } from '../../config/puppeteer'; import dotenv from 'dotenv'; dotenv.config(); async function scrapeSite(site) { const browser = await getBrowserInstance(); const page = await browser.newPage(); try { await page.goto(site.url, { waitUntil: 'networkidle2' }); // Take a PDF of the site const pdfPath = path.join('assets', \`\${site.url.replace(/https?:\\/\\//, '').replace(/\\//g, '_')}.pdf\`); await page.pdf({ path: pdfPath, format: 'A4' }); // Extract data from the site const data = await page.evaluate(() => { const title = document.title; const colors = Array.from(new Set(Array.from(document.querySelectorAll('*')).map(el => getComputedStyle(el).color))); const isWP = !!document.querySelector('meta[name="generator"][content*="WordPress"]'); const wpThemeDeveloper = isWP ? document.querySelector('meta[name="generator"]').content : null; const wpPlugins = isWP ? Array.from(document.querySelectorAll('[class*="plugin"]')).map(el => el.className) : []; const imgAltDescriptions = Array.from(document.querySelectorAll('img')).map(img => img.alt); const ariaUsage = !!document.querySelector('[role]') || !!document.querySelector('[aria-*]'); const brokenLinks = Array.from(document.querySelectorAll('a')).filter(a => !a.href || a.href === '#').length; return { title, colors, isWP, wpThemeDeveloper, wpPlugins, imgAltDescriptions, ariaUsage, brokenLinks }; }); // Calculate SEO score data.seoScore = calculateSeoScore(data); data.url = site.url; // Save data to MongoDB and PostgreSQL await saveToMongoDB(data); await saveToPostgres(data); console.log(\`Scraped and saved data for \${site.url}\`); } catch (error) { console.error(\`Error scraping \${site.url}: \`, error); } finally { await browser.close(); } } export { scrapeSite }; EOL # Create src/services/seoScore.js cat < project-root/src/services/seoScore.js const weights = { bestPractices: 0.15, mobile: 0.10, analyticsAndMonitoring: 0.10, performance: 0.15, usability: 0.10, semantics: 0.10, seo: 0.20, codeQuality: 0.05, accessibility: 0.10, security: 0.05, socialMediaAndStructuredData: 0.05, }; const maxScore = { required: { bestPractices: 4, mobile: 4, analyticsAndMonitoring: 2, performance: 3, usability: 5, semantics: 2, seo: 4, codeQuality: 5, accessibility: 3, security: 2, socialMediaAndStructuredData: 0, // No required tasks }, optional: { usability: 2, security: 3, socialMediaAndStructuredData: 4, andFinally: 1, } }; function calculateSeoScore(data) { let totalScore = 0; // Calculate required scores Object.keys(weights).forEach(category => { if (data[category] && data[category].required) { const completedTasks = data[category].required.filter(task => task.completed).length; totalScore += (completedTasks / maxScore.required[category]) * weights[category]; } if (data[category] && data[category].optional) { const completedTasks =