Skip to content
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.16.0
4 changes: 2 additions & 2 deletions docs/tutorials/oidc/example-integration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ const bookmarks = await response.json();

Once authenticated, explore the [User APIs](/docs/category/user-related-apis):

- **[Collections](/docs/user_related_apis_versioned/auth-get-v-1-collections)** — Create and manage verse collections
- **[Bookmarks](/docs/user_related_apis_versioned/auth-get-v-1-bookmarks)** — Save and retrieve bookmarked verses
- **[Collections](/docs/user_related_apis_versioned/1.0.0/get-all-collections/)** — Create and manage verse collections
- **[Bookmarks](/docs/user_related_apis_versioned/1.0.0/get-user-bookmarks/)** — Save and retrieve bookmarked verses
- **Reading Sessions** — Track reading progress
- **Goals & Streaks** — User reading goals

Expand Down
10 changes: 5 additions & 5 deletions docs/tutorials/sync/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ When the user creates data offline, push mutations to the server using `POST /v1

### API References

- [GET /v1/sync (Get mutations)](/docs/user_related_apis_prelive/auth-get-v-1-sync)
- [POST /v1/sync (Sync local mutations)](/docs/user_related_apis_prelive/auth-post-v-1-sync)
- [GET /v1/sync (Get mutations)](/docs/user_related_apis_prelive/get-mutations/)
- [POST /v1/sync (Sync local mutations)](/docs/user_related_apis_prelive/sync-local-mutations/)

## Using metadataOnly for Efficiency

Expand All @@ -139,7 +139,7 @@ This is significantly faster than a full sync (single DB query vs multiple joins

For the full request/response schema, see:

- [GET /v1/sync (Get mutations)](/docs/user_related_apis_prelive/auth-get-v-1-sync)
- [GET /v1/sync (Get mutations)](/docs/user_related_apis_prelive/get-mutations/)

## Filtering & Pagination

Expand All @@ -157,5 +157,5 @@ When `metadataOnly=true`, the response includes only `lastMutationAt` (no pagina

## Next Steps

- [Handling Conflicts](./handling-conflicts) — Learn how to handle 409 errors and resolve conflicts
- [Offline-First Patterns](./offline-first-patterns) — Best practices for client-side architecture
- [Handling Conflicts](/docs/tutorials/sync/handling-conflicts/) — Learn how to handle 409 errors and resolve conflicts
- [Offline-First Patterns](/docs/tutorials/sync/offline-first-patterns/) — Best practices for client-side architecture
4 changes: 2 additions & 2 deletions docs/tutorials/sync/handling-conflicts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ This quickly retrieves the current `lastMutationAt` without fetching all mutatio

See the full request/response schema in the API reference:

- [GET /v1/sync (Get mutations)](/docs/user_related_apis_prelive/auth-get-v-1-sync)
- [GET /v1/sync (Get mutations)](/docs/user_related_apis_prelive/get-mutations/)

## Best Practices

Expand All @@ -129,4 +129,4 @@ See the full request/response schema in the API reference:

## Next Steps

- [Offline-First Patterns](./offline-first-patterns) — Architecture for robust offline support
- [Offline-First Patterns](/docs/tutorials/sync/offline-first-patterns/) — Architecture for robust offline support
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ Tracks daily activity/progress (by date + type). This powers streaks, goals, and

## Related API Docs (v1.0.0)

- [Add or update user reading session](/docs/user_related_apis_versioned/1.0.0/auth-post-v-1-reading-sessions)
- [Get user reading sessions](/docs/user_related_apis_versioned/1.0.0/auth-get-v-1-reading-sessions)
- [Add/update activity day](/docs/user_related_apis_versioned/1.0.0/auth-post-v-1-activity-days)
- [Get activity days](/docs/user_related_apis_versioned/1.0.0/auth-get-v-1-activity-days)
- [Estimate reading time](/docs/user_related_apis_versioned/1.0.0/auth-get-v-1-activity-days-estimate-reading-time)
- [Add or update user reading session](/docs/user_related_apis_versioned/1.0.0/add-or-update-user-reading-session/)
- [Get user reading sessions](/docs/user_related_apis_versioned/1.0.0/get-user-reading-sessions/)
- [Add/update activity day](/docs/user_related_apis_versioned/1.0.0/add-update-activity-day/)
- [Get activity days](/docs/user_related_apis_versioned/1.0.0/get-activity-days/)
- [Estimate reading time](/docs/user_related_apis_versioned/1.0.0/estimate-reading-time/)

10 changes: 5 additions & 5 deletions docs/user-related-apis/reading-sessions-vs-activity-days.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ Tracks daily activity/progress (by date + type). This powers streaks, goals, and

## Related API Docs

- [Add or update user reading session](/docs/user_related_apis_versioned/auth-post-v-1-reading-sessions)
- [Get user reading sessions](/docs/user_related_apis_versioned/auth-get-v-1-reading-sessions)
- [Add/update activity day](/docs/user_related_apis_versioned/auth-post-v-1-activity-days)
- [Get activity days](/docs/user_related_apis_versioned/auth-get-v-1-activity-days)
- [Estimate reading time](/docs/user_related_apis_versioned/auth-get-v-1-activity-days-estimate-reading-time)
- [Add or update user reading session](/docs/user_related_apis_versioned/1.0.0/add-or-update-user-reading-session/)
- [Get user reading sessions](/docs/user_related_apis_versioned/1.0.0/get-user-reading-sessions/)
- [Add/update activity day](/docs/user_related_apis_versioned/1.0.0/add-update-activity-day/)
- [Get activity days](/docs/user_related_apis_versioned/1.0.0/get-activity-days/)
- [Estimate reading time](/docs/user_related_apis_versioned/1.0.0/estimate-reading-time/)

18 changes: 10 additions & 8 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const config = {
// Set the production url of your site here
url: "https://api-docs.quran.foundation",
baseUrl: "/",
trailingSlash: true,
// Load a tiny client script to suppress noisy ResizeObserver errors in dev
scripts: [
{ src: "/js/ignore-resizeobserver-error.js", async: true },
Expand Down Expand Up @@ -50,6 +51,7 @@ const config = {
changefreq: "monthly",
priority: 0.5,
filename: "sitemap.xml",
ignorePatterns: ["/search", "/request-scopes"],
},
...(enableGtag
? {
Expand Down Expand Up @@ -240,23 +242,23 @@ const config = {
items: [
{
label: "Content APIs",
to: "/docs/content_apis_versioned/content-apis",
to: "/docs/content_apis_versioned/4.0.0/content-apis/",
},
{
label: "Search APIs",
to: "/docs/search_apis_versioned/quran-foundation-search-api",
to: "/docs/search_apis_versioned/1.0.0/quran-foundation-search-api/",
},
{
label: "User-related APIs",
to: "/docs/user_related_apis_versioned/user-related-apis",
to: "/docs/user_related_apis_versioned/1.0.0/user-related-apis/",
},
{
label: "User-related APIs (Pre-live)",
to: "docs/category/user-related-apis-pre-live",
},
{
label: "OAuth2 APIs",
to: "/docs/oauth2_apis_versioned/oauth-2-apis",
to: "/docs/oauth2_apis_versioned/1.0.0/oauth-2-apis/",
},
],
},
Expand All @@ -282,23 +284,23 @@ const config = {
items: [
{
label: "Content APIs",
to: "/docs/content_apis_versioned/content-apis",
to: "/docs/content_apis_versioned/4.0.0/content-apis/",
},
{
label: "OAuth2 / OIDC APIs",
to: "/docs/oauth2_apis_versioned/oauth-2-apis",
to: "/docs/oauth2_apis_versioned/1.0.0/oauth-2-apis/",
},
{
label: "User-related APIs",
to: "/docs/user_related_apis_versioned/user-related-apis",
to: "/docs/user_related_apis_versioned/1.0.0/user-related-apis/",
},
{
label: "User-related APIs (Pre-live)",
to: "/docs/category/user-related-apis-pre-live",
},
{
label: "Search APIs",
to: "/docs/search_apis_versioned/quran-foundation-search-api",
to: "/docs/search_apis_versioned/1.0.0/quran-foundation-search-api/",
},
],
},
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
"deploy:pages": "yarn run build && wrangler pages deploy build",
"docusaurus": "npx docusaurus",
"llms:sync": "node scripts/sync-static-llms-txt.js",
"postbuild:seo": "node scripts/postbuild-seo.js",
"postgen-api-cleanup": "node scripts/prune-generated-api-aliases.js",
"postgen-api-sidebars": "node scripts/set-api-displayed-sidebars.js",
"start": "yarn gen-all && npx docusaurus start",
"build": "yarn gen-all && npx docusaurus build",
"build": "yarn gen-all && npx docusaurus build && yarn postbuild:seo",
"swizzle": "npx docusaurus swizzle",
"deploy": "npx docusaurus deploy",
"clear": "npx docusaurus clear",
Expand All @@ -21,7 +23,7 @@
"clean-api-docs": "npx docusaurus clean-api-docs",
"gen-api-docs:version": "npx docusaurus gen-api-docs:version",
"clean-api-docs:version": "npx docusaurus clean-api-docs:version",
"gen-all": "npx docusaurus gen-api-docs all && npx docusaurus gen-api-docs:version content_apis_versioned:all && npx docusaurus gen-api-docs:version user_related_apis_versioned:all && npx docusaurus gen-api-docs:version oauth2_apis_versioned:all && npx docusaurus gen-api-docs:version search_apis_versioned:all && yarn postgen-api-sidebars",
"gen-all": "npx docusaurus gen-api-docs all && npx docusaurus gen-api-docs:version content_apis_versioned:all && npx docusaurus gen-api-docs:version user_related_apis_versioned:all && npx docusaurus gen-api-docs:version oauth2_apis_versioned:all && npx docusaurus gen-api-docs:version search_apis_versioned:all && yarn postgen-api-cleanup && yarn postgen-api-sidebars",
"clean-all": "npx docusaurus clean-api-docs all && npx docusaurus clean-api-docs:version content_apis_versioned:all && npx docusaurus clean-api-docs:version user_related_apis_versioned:all && npx docusaurus clean-api-docs:version oauth2_apis_versioned:all && npx docusaurus clean-api-docs:version search_apis_versioned:all",
"re-gen": "yarn clean-all && yarn gen-all"
},
Expand Down
179 changes: 179 additions & 0 deletions scripts/postbuild-seo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"use strict";

const fs = require("fs");
const path = require("path");

const BUILD_DIR = path.join(__dirname, "..", "build");
const SITEMAP_PATH = path.join(BUILD_DIR, "sitemap.xml");
const SITE_ORIGIN = "https://api-docs.quran.foundation";
const versionedApiRoots = {
content_apis_versioned: "4.0.0",
oauth2_apis_versioned: "1.0.0",
search_apis_versioned: "1.0.0",
user_related_apis_versioned: "1.0.0",
};

const dropPathsFromSitemap = new Set([
"/search",
"/search/",
"/request-scopes",
"/request-scopes/",
]);

function isStaticAsset(pathname) {
return /\.[a-z0-9]+$/i.test(pathname);
}

function normalizePathname(pathname) {
if (pathname !== "/" && !pathname.endsWith("/") && !isStaticAsset(pathname)) {
return `${pathname}/`;
}

return pathname;
}

function normalizeSiteUrl(rawUrl, pathnameOverride) {
const url = new URL(rawUrl);

if (url.origin !== SITE_ORIGIN) {
return rawUrl;
}

url.pathname = normalizePathname(pathnameOverride || url.pathname);

return url.toString();
}

function getCanonicalPathOverride(pathname) {
const singleSegmentApiFamily = pathname.match(
/^\/docs\/(content_apis_versioned|user_related_apis_versioned|oauth2_apis_versioned|search_apis_versioned)\/([^/]+)\/?$/,
);

if (!singleSegmentApiFamily) {
return null;
}

const [, family, slug] = singleSegmentApiFamily;

if (family === "user_related_apis_versioned" && slug === "scopes") {
return normalizePathname(pathname);
}

return `/docs/${family}/${versionedApiRoots[family]}/${slug}/`;
}

function shouldDropSitemapPath(pathname) {
if (dropPathsFromSitemap.has(pathname)) {
return true;
}

const singleSegmentApiFamily = pathname.match(
/^\/docs\/(content_apis_versioned|user_related_apis_versioned|oauth2_apis_versioned|search_apis_versioned)\/([^/]+)\/?$/,
);

if (!singleSegmentApiFamily) {
return false;
}

const [, family, slug] = singleSegmentApiFamily;

if (family === "user_related_apis_versioned" && slug === "scopes") {
return false;
}

return true;
}

function getBuiltHtmlPathname(htmlPath) {
const relativePath = path.relative(BUILD_DIR, htmlPath).split(path.sep).join("/");

if (relativePath === "index.html") {
return "/";
}

const pathname = `/${relativePath}`
.replace(/index\.html$/i, "")
.replace(/\.html$/i, "");

return normalizePathname(pathname);
}

function rewriteHtmlCanonicals() {
const htmlFiles = [];

function walk(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(fullPath);
continue;
}

if (entry.isFile() && entry.name.endsWith(".html")) {
htmlFiles.push(fullPath);
}
}
}

walk(BUILD_DIR);

const linkPattern =
/(<link[^>]+rel="(?:canonical|alternate)"[^>]+href=")(https:\/\/api-docs\.quran\.foundation[^"]*)(")/g;
const ogUrlPattern =
/(<meta[^>]+property="og:url"[^>]+content=")(https:\/\/api-docs\.quran\.foundation[^"]*)(")/g;

for (const htmlPath of htmlFiles) {
const source = fs.readFileSync(htmlPath, "utf8");
const builtPathname = getBuiltHtmlPathname(htmlPath);
const canonicalPathOverride = getCanonicalPathOverride(builtPathname);
const rewritten = source
.replace(linkPattern, (_, prefix, url, suffix) => {
return `${prefix}${normalizeSiteUrl(url, canonicalPathOverride)}${suffix}`;
})
.replace(ogUrlPattern, (_, prefix, url, suffix) => {
return `${prefix}${normalizeSiteUrl(url, canonicalPathOverride)}${suffix}`;
});

if (rewritten !== source) {
fs.writeFileSync(htmlPath, rewritten);
}
}
}

function rewriteSitemap() {
const source = fs.readFileSync(SITEMAP_PATH, "utf8");
const urlEntries = [...source.matchAll(/<url><loc>([^<]+)<\/loc>[\s\S]*?<\/url>/g)];

const filteredEntries = urlEntries
.map((match) => {
const [entry, rawUrl] = match;
const normalizedUrl = normalizeSiteUrl(rawUrl);
const pathname = new URL(normalizedUrl).pathname;

if (shouldDropSitemapPath(pathname)) {
return null;
}

return entry.replace(rawUrl, normalizedUrl);
})
.filter(Boolean);

const rewritten =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">' +
filteredEntries.join("") +
"</urlset>";

fs.writeFileSync(SITEMAP_PATH, rewritten);
}

function main() {
if (!fs.existsSync(BUILD_DIR) || !fs.existsSync(SITEMAP_PATH)) {
throw new Error("Build output is missing; run the Docusaurus build first.");
}

rewriteHtmlCanonicals();
rewriteSitemap();
}

main();
Loading