astro-loader-pocketbase
Version:
A content loader for Astro that uses the PocketBase API
117 lines (102 loc) • 3.7 kB
text/typescript
import type { LoaderContext } from "astro/loaders";
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
import { parseEntry } from "./parse-entry";
/**
* Load (modified) entries from a PocketBase collection.
*
* @param options Options for the loader.
* @param context Context of the loader.
* @param superuserToken Superuser token to access all resources.
* @param lastModified Date of the last fetch to only update changed entries.
*
* @returns `true` if the collection has an updated column, `false` otherwise.
*/
export async function loadEntries(
options: PocketBaseLoaderOptions,
context: LoaderContext,
superuserToken: string | undefined,
lastModified: string | undefined
): Promise<void> {
// Build the URL for the collections endpoint
const collectionUrl = new URL(
`api/collections/${options.collectionName}/records`,
options.url
).href;
// Create the headers for the request to append the superuser token (if available)
const collectionHeaders = new Headers();
if (superuserToken) {
collectionHeaders.set("Authorization", superuserToken);
}
// Log the fetching of the entries
context.logger.info(
`Fetching${lastModified ? " modified" : ""} data${
lastModified ? ` starting at ${lastModified}` : ""
}${superuserToken ? " as superuser" : ""}`
);
// Prepare pagination variables
let page = 0;
let totalPages = 0;
let entries = 0;
// Fetch all (modified) entries
do {
// Build search parameters
const searchParams = new URLSearchParams({
page: `${++page}`,
perPage: "100"
});
const filters = [];
if (lastModified && options.updatedField) {
// If `lastModified` is set, only fetch entries that have been modified since the last fetch
filters.push(`(${options.updatedField}>"${lastModified}")`);
// Sort by the updated field and id
searchParams.set("sort", `-${options.updatedField},id`);
}
if (options.filter) {
filters.push(`(${options.filter})`);
}
// Add filters to search parameters
if (filters.length > 0) {
searchParams.set("filter", filters.join("&&"));
}
// Fetch entries from the collection
const collectionRequest = await fetch(
`${collectionUrl}?${searchParams.toString()}`,
{
headers: collectionHeaders
}
);
// If the request was not successful, print the error message and return
if (!collectionRequest.ok) {
// If the collection is locked, an superuser token is required
if (collectionRequest.status === 403) {
throw new Error(
`The collection is not accessible without superuser rights. Please provide superuser credentials in the config.`
);
}
// Get the reason for the error
const reason = await collectionRequest
.json()
.then((data) => data.message);
const errorMessage = `Fetching data failed with status code ${collectionRequest.status}.\nReason: ${reason}`;
throw new Error(errorMessage);
}
// Get the data from the response
const response = await collectionRequest.json();
// Parse and store the entries
for (const entry of response.items) {
await parseEntry(entry, context, options);
}
// Update the page and total pages
page = response.page;
totalPages = response.totalPages;
entries += response.items.length;
} while (page < totalPages);
// Log the number of fetched entries
if (lastModified) {
context.logger.info(
`Updated ${entries}/${context.store.keys().length} entries.`
);
} else {
context.logger.info(`Fetched ${entries} entries.`);
}
}