Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const config = [
...unicorn.configs["flat/recommended"].rules, // neon disables a lot of unicorn rules so this reenables defaults
"react/no-danger": "warn",
"react/jsx-sort-props": "off",
"@stylistic/jsx/jsx-sort-props": "off",
"unicorn/prevent-abbreviations": [
"error",
{
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"downshift": "^9.0.8",
"graphql": "^16.8.1",
"html-to-text": "^9.0.5",
"http-status-codes": "^2.3.0",
"lodash.debounce": "^4.0.8",
"next": "^15.0.1",
"react": "^18.3.1",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/lib/gtag.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// eslint-disable-next-line no-restricted-globals, n/prefer-global/process
import process from "process"; // eslint-disable-line unicorn/prefer-node-protocol

export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_KEY;

// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
export const logPageview = (url) => {
window.gtag("config", GA_TRACKING_ID, {
globalThis.gtag("config", GA_TRACKING_ID, {
page_path: url,
});
};
91 changes: 45 additions & 46 deletions src/lib/smart-search-plugin.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { hash } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { cwd } from "node:process";
import { htmlToText } from "html-to-text";

function smartSearchPlugin({ endpoint, accessToken }) {
let isPluginExecuted = false;
let isPluginExecuted = false;

function smartSearchPlugin({ endpoint, accessToken }) {
return {
apply: (compiler) => {
compiler.hooks.done.tapPromise("SmartSearchPlugin", async () => {
Expand All @@ -25,8 +26,8 @@ function smartSearchPlugin({ endpoint, accessToken }) {

console.log("Docs Pages collected for indexing:", pages.length);

await deleteExistingDocs(endpoint, accessToken);
await sendPagesToEndpoint(pages, endpoint, accessToken);
await deleteOldDocs({ endpoint, accessToken }, pages);
await sendPagesToEndpoint({ endpoint, accessToken }, pages);
} catch (error) {
console.error("Error in smartSearchPlugin:", error);
}
Expand Down Expand Up @@ -54,12 +55,9 @@ async function collectPages(directory) {

let metadata = {};

if (
metadataMatch &&
metadataMatch.groups &&
metadataMatch.groups.metadata
) {
if (metadataMatch?.groups?.metadata) {
try {
// eslint-disable-next-line no-eval
metadata = eval(`(${metadataMatch.groups.metadata})`);
} catch (error) {
console.error("Error parsing metadata:", error);
Expand All @@ -74,9 +72,7 @@ async function collectPages(directory) {

const cleanedPath = cleanPath(entryPath);

const id = `mdx:${cleanedPath}`;

console.log(`Indexing document with ID: ${id}, path: ${cleanedPath}`);
const id = hash("sha-1", `mdx:${cleanedPath}`);

pages.push({
id,
Expand Down Expand Up @@ -105,16 +101,28 @@ function cleanPath(filePath) {
);
}

async function deleteExistingDocs(endpoint, accessToken) {
const queryDocuments = `
query FindDocumentsToDelete($query: String!) {
find(query: $query) {
documents {
id
}
const queryDocuments = `
query FindIndexedMdxDocs($query: String!) {
find(query: $query) {
documents {
id
}
}
`;
}
}
`;

const deleteMutation = `
mutation DeleteDocument($id: ID!) {
delete(id: $id) {
code
message
success
}
}
`;

async function deleteOldDocs({ endpoint, accessToken }, pages) {
const currentMdxDocuments = new Set(pages.map((page) => page.id));

const variablesForQuery = {
query: 'content_type:"mdx_doc"',
Expand All @@ -136,30 +144,25 @@ async function deleteExistingDocs(endpoint, accessToken) {
const result = await response.json();

if (result.errors) {
console.error("Error fetching documents to delete:", result.errors);
console.error("Error fetching existing documents:", result.errors);
return;
}

const documentsToDelete = result.data.find.documents;
const existingIndexedDocuments = new Set(
result.data.find.documents.map((doc) => doc.id),
);

const documentsToDelete =
existingIndexedDocuments.difference(currentMdxDocuments);

if (!documentsToDelete || documentsToDelete.length === 0) {
if (documentsToDelete?.size === 0) {
console.log("No documents to delete.");
return;
}

for (const doc of documentsToDelete) {
const deleteMutation = `
mutation DeleteDocument($id: ID!) {
delete(id: $id) {
code
message
success
}
}
`;

for (const doc of documentsToDelete.values()) {
const variablesForDelete = {
id: doc.id,
id: doc,
};

try {
Expand All @@ -179,14 +182,11 @@ async function deleteExistingDocs(endpoint, accessToken) {

if (deleteResult.errors) {
console.error(
`Error deleting document ID ${doc.id}:`,
`Error deleting document ID ${doc}:`,
deleteResult.errors,
);
} else {
console.log(
`Deleted document ID ${doc.id}:`,
deleteResult.data.delete,
);
console.log(`Deleted document ID ${doc}:`, deleteResult.data.delete);
}
} catch (error) {
console.error(`Network error deleting document ID ${doc.id}:`, error);
Expand All @@ -198,18 +198,17 @@ async function deleteExistingDocs(endpoint, accessToken) {
}

const bulkIndexQuery = `
mutation BulkIndex($documents: [DocumentInput!]!) {
bulkIndex(input: { documents: $documents }) {
mutation BulkIndex($input: BulkIndexInput!) {
bulkIndex(input: $input) {
code
documents {
id
data
}
}
}
`;

async function sendPagesToEndpoint(pages, endpoint, accessToken) {
async function sendPagesToEndpoint({ endpoint, accessToken }, pages) {
if (pages.length === 0) {
console.warn("No documents found for indexing.");
return;
Expand All @@ -220,7 +219,7 @@ async function sendPagesToEndpoint(pages, endpoint, accessToken) {
data: page.data,
}));

const variables = { documents };
const variables = { input: { documents } };

try {
const response = await fetch(endpoint, {
Expand Down
8 changes: 4 additions & 4 deletions src/pages/_app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import { WordPressBlocksProvider } from "@faustwp/blocks";
import { FaustProvider } from "@faustwp/core";
import { useRouter } from "next/router";
import { useEffect } from "react";
import Layout from "@/components/layout";
import { logPageview } from "@/lib/gtag.js";
import blocks from "@/wp-blocks";
import "../../faust.config";
import "./global.css";
import Layout from "@/components/layout";
import "@faustwp/core/dist/css/toolbar.css";
import * as gtag from "@/lib/gtag";
import blocks from "@/wp-blocks";

export default function MyApp({ Component, pageProps }) {
const router = useRouter();

// Record a Google Analytics pageview on route change
useEffect(() => {
const handleRouteChange = (url) => gtag.logPageview(url);
const handleRouteChange = (url) => logPageview(url);

router.events.on("routeChangeComplete", handleRouteChange);
return () => {
Expand Down
98 changes: 58 additions & 40 deletions src/pages/api/search.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import process from "node:process";
import { ReasonPhrases, StatusCodes } from "http-status-codes";

function cleanPath(filePath) {
return (
Expand All @@ -15,8 +16,16 @@ export default async function handler(req, res) {
const accessToken = process.env.NEXT_SEARCH_ACCESS_TOKEN;
const { query } = req.query;

if (req.method !== "GET") {
return res
.status(StatusCodes.METHOD_NOT_ALLOWED)
.json({ error: ReasonPhrases.METHOD_NOT_ALLOWED });
}

if (!query) {
return res.status(400).json({ error: "Search query is required." });
return res
.status(StatusCodes.BAD_REQUEST)
.json({ error: "Search query is required." });
}

const graphqlQuery = `
Expand Down Expand Up @@ -44,56 +53,65 @@ export default async function handler(req, res) {
}),
});

if (!response.ok) {
return res
.status(StatusCodes.SERVICE_UNAVAILABLE)
.json({ error: ReasonPhrases.SERVICE_UNAVAILABLE });
}

const result = await response.json();

if (result.errors) {
return res.status(500).json({ errors: result.errors });
return res
.status(StatusCodes.INTERNAL_SERVER_ERROR)
.json({ errors: result.errors });
}

const formattedResults = result.data.find.documents
.map((content) => {
const contentType =
content.data.content_type || content.data.post_type || "mdx_doc";

if (contentType === "mdx_doc" && content.data.title) {
const path = content.data.path ? cleanPath(content.data.path) : "/";
return {
id: content.id,
title: content.data.title,
path,
type: "mdx_doc",
};
}

if (
(contentType === "wp_post" || contentType === "post") &&
content.data.post_title &&
content.data.post_name
) {
return {
id: content.id,
title: content.data.post_title,
path: `/blog/${content.data.post_name}`,
type: "post",
};
}

return null;
})
.filter((item) => item !== null);

const seenIds = new Set();
const uniqueResults = formattedResults.filter((item) => {
const formattedResults = [];

for (const content of result.data.find.documents) {
const contentType =
content.data.content_type || content.data.post_type || "mdx_doc";

let item = {};

if (contentType === "mdx_doc" && content.data.title) {
const path = content.data.path ? cleanPath(content.data.path) : "/";
item = {
id: content.id,
title: content.data.title,
path,
type: "mdx_doc",
};
}

if (
(contentType === "wp_post" || contentType === "post") &&
content.data.post_title &&
content.data.post_name
) {
item = {
id: content.id,
title: content.data.post_title,
path: `/blog/${content.data.post_name}`,
type: "post",
};
}

if (seenIds.has(item.id)) {
return false;
continue;
}

seenIds.add(item.id);
return true;
});
formattedResults.push(item);
}

return res.status(200).json(uniqueResults);
return res.status(StatusCodes.OK).json(formattedResults);
} catch (error) {
console.error("Error fetching search data:", error);
return res.status(500).json({ error: "Internal server error" });
return res
.status(StatusCodes.Inter)
.json({ error: ReasonPhrases.INTERNAL_SERVER_ERROR });
}
}
Loading