credentials.json
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "web": { "client_id": "", "project_id": "", "auth_uri": "", "token_uri": "", "auth_provider_x509_cert_url": "", "client_secret": "", "redirect_uris": [""], "javascript_origins": [""] } } |
views/index.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Google Search Console Mini Clone</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" /> </head> <body> <div class="container mt-5"> <h1 class="mb-4">Google Search Console Mini Clone</h1> <% if (error) { %> <div class="alert alert-danger"><%= error %></div> <% } %> <a href="<%= authUrl %>" class="btn btn-primary">Login with Google</a> </div> </body> </html> |
views/result.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Search Console Analytics</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" /> </head> <body> <div class="container mt-5"> <h1 class="mb-4">Search Console Analytics</h1> <form method="POST" action="/analytics" class="mb-4"> <div class="row g-3"> <div class="col-md-5"> <label for="siteUrl" class="form-label">Select Site</label> <select id="siteUrl" name="siteUrl" class="form-select" required> <% sites.forEach(site => { %> <option value="<%= site.siteUrl %>" <%= selectedSite === site.siteUrl ? 'selected' : '' %>> <%= site.siteUrl.replace('sc-domain:', '') %> </option> <% }); %> </select> </div> <div class="col-md-5"> <label for="startDate" class="form-label">Start Date</label> <input type="date" id="startDate" name="startDate" class="form-control" required /> </div> <div class="col-md-5"> <label for="endDate" class="form-label">End Date</label> <input type="date" id="endDate" name="endDate" class="form-control" required /> </div> <div class="col-md-2 align-self-end"> <button type="submit" class="btn btn-primary w-100">Fetch Data</button> </div> </div> </form> <% if (error) { %> <div class="alert alert-danger"><%= error %></div> <% } else if (performanceData && performanceData.length > 0) { %> <table class="table table-striped"> <thead> <tr> <th>Page</th> <th>Query</th> <th>Clicks</th> <th>Impressions</th> <th>CTR (%)</th> <th>Position</th> </tr> </thead> <tbody> <% performanceData.forEach((row) => { %> <tr> <td><%= row.page %></td> <td><%= row.query %></td> <td><%= row.clicks %></td> <td><%= row.impressions %></td> <td><%= row.ctr %></td> <td><%= row.position %></td> </tr> <% }); %> </tbody> </table> <% } else { %> <div class="alert alert-info">No data available for the selected date range.</div> <% } %> </div> </body> </html> |
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
const express = require("express"); const bodyParser = require("body-parser"); const { google } = require("googleapis"); const fs = require("fs"); const app = express(); // Middleware app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static("public")); // Static files app.set("view engine", "ejs"); // EJS for templating app.set("views", "./views"); // Load credentials const credentials = JSON.parse(fs.readFileSync("credentials.json", "utf-8")); const oauth2Client = new google.auth.OAuth2( credentials.web.client_id, credentials.web.client_secret, credentials.web.redirect_uris[0] ); // Authorize URL const authUrl = oauth2Client.generateAuthUrl({ access_type: "offline", scope: "https://www.googleapis.com/auth/webmasters.readonly", }); // Globals let tokens = null; // Routes app.get("/", (req, res) => { res.render("index", { authUrl, error: null }); }); app.get("/oauth2callback", async (req, res) => { try { const { code } = req.query; const { tokens: accessTokens } = await oauth2Client.getToken(code); tokens = accessTokens; oauth2Client.setCredentials(tokens); // Fetch the list of verified sites const searchConsole = google.webmasters({ version: "v3", auth: oauth2Client }); const siteListResponse = await searchConsole.sites.list(); userSites = siteListResponse.data.siteEntry || []; res.redirect("/dashboard"); } catch (error) { console.error("OAuth2 Error:", error); res.render("index", { authUrl, error: "Authentication failed." }); } }); app.get("/dashboard", async (req, res) => { if (!tokens) return res.redirect("/"); // Assuming you're using Google Search Console API to fetch the list of sites const searchConsole = google.webmasters({ version: "v3", auth: oauth2Client }); try { const response = await searchConsole.sites.list(); // Assuming you want the first site in the list as the selected site const selectedSite = response.data.siteEntry[0].siteUrl; // Pass sites and selectedSite to the view res.render("results", { sites: response.data.siteEntry, selectedSite, performanceData: null, error: null }); } catch (error) { console.error("Error fetching sites:", error); res.render("results", { performanceData: null, error: "Failed to fetch sites." }); } }); async function getSites() { const searchConsole = google.webmasters({ version: "v3", auth: oauth2Client }); const response = await searchConsole.sites.list(); return response.data.siteEntry; } app.post("/analytics", async (req, res) => { if (!tokens) return res.redirect("/"); const { startDate, endDate, siteUrl } = req.body; // Get siteUrl from the form const searchConsole = google.webmasters({ version: "v3", auth: oauth2Client }); try { const response = await searchConsole.searchanalytics.query({ siteUrl: siteUrl, // Use the selected site requestBody: { startDate, endDate, dimensions: ["page", "query"], rowLimit: 50, // Fetch top 50 rows }, }); const rows = response.data.rows || []; const performanceData = rows.map((row) => ({ page: row.keys[0], query: row.keys[1], clicks: row.clicks || 0, impressions: row.impressions || 0, ctr: row.ctr ? (row.ctr * 100).toFixed(2) : 0, // Convert to percentage position: row.position ? row.position.toFixed(1) : "N/A", })); // Render results with selected site res.render("results", { sites: await getSites(), // Call a function to get the list of sites again selectedSite: siteUrl, // Pass the selected site performanceData, error: null }); } catch (error) { console.error("Error fetching Search Console data:", error); res.render("results", { sites: await getSites(), // Ensure sites are available selectedSite: siteUrl, performanceData: null, error: "Failed to fetch data." }); } }); // Start server const PORT = 3000; app.listen(PORT, () => { console.log(`Server is running at http://localhost:${PORT}`); }); |