// Copyright (C) 2023 Vaughn Nugent // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { defaultTo, startsWith } from 'lodash-es'; import { ChannelApi, createScopedChannelApi } from "./channels"; import { CMNextApi, CMNextAutoConfig, CMNextIndex, PostMeta } from "./types"; export interface PostApi extends CMNextApi { /** * Gets the content for a post by its Id * @param postId The id of the post to fetch content for * @returns A promise that resolves to the post content as a string */ getPostContent: (post: string | PostMeta, extension?:string) => Promise; /** * Gets the index file path for the channel */ getIndexFilePath(): Promise } /** * A post api created from an automatic configuration */ export interface AutoPostApi extends PostApi, CMNextAutoConfig { /** * The channel api pass-thru */ readonly channelApi: ChannelApi; } /** * A post api created from a manual configuration */ export interface ManualPostApi extends PostApi, PostApiManualConfig { } export interface PostApiManualConfig { /** * The root directory path of the desired channel */ readonly channelRootDir: string; /** * The relative path to the channel's post index file */ readonly postIndexPath: string; /** * The relative path to the channel's content directory */ readonly contentDir: string; } /** * Creates a post api around the channel index file and the desired channel id * to get posts from. This method requires an additional fetch to get the channel * information before it can fetch posts, so it will be slower, but its * discovery is automatic. * @param channelUrl The url to the channel index file * @param channelId The id of the channel to get posts from * @returns A post api that can be used to get posts from the channel */ export const createAutoPostApi = ({ cmsChannelIndexPath, channelId }: CMNextAutoConfig): AutoPostApi => { //Use scoped channel api const channelApi = createScopedChannelApi(cmsChannelIndexPath, channelId); const getIndex = async (): Promise> => { //Await the selected channel index const indexUrl = await channelApi.getPostIndexPath(); if (!indexUrl){ //Return empty index return { date: 0, records: [], version: "0.0.0" } } //Fetch the index file const res = await fetch(indexUrl) return await res.json() } const getPostContent = async (post: PostMeta | string, extension = '.html'): Promise => { //Get the selected channel const contentDir = await channelApi.getContentDir(); const baseDir = await channelApi.getBaseDir(); if (!contentDir || !baseDir){ //Return empty content return "" } const itemId = defaultTo(post.id, post) //Fetch the content as text because it is html const res = await fetch(`${baseDir}${contentDir}/${itemId}${extension}`) return await res.text() } const getIndexFilePath = async () : Promise => { const indexUrl = await channelApi.getPostIndexPath(); return indexUrl || "" } return{ channelApi, getPostContent, channelId, getIndexFilePath, getIndex, cmsChannelIndexPath, } } /** * Creates a post api around known channel information to avoid additional fetch * requests to get the channel information. This method is faster, but requires * the channel information to be known and remain constant. * @param baseUrl The base url of the desired channel * @param indexFilePath The path to the index file within the channel * @param contentDir The path to the content directory within the channel * @returns A post api that can be used to get posts from the channel */ export const createManualPostApi = ({ channelRootDir, contentDir, postIndexPath }: PostApiManualConfig): ManualPostApi => { //Make sure inedx file has a leading slash postIndexPath = startsWith(postIndexPath, '/') ? postIndexPath : `/${postIndexPath}` contentDir = startsWith(contentDir, '/') ? contentDir : `/${contentDir}` const getItemPath = (path: string) => `${channelRootDir}${path}` const getIndex = async (): Promise> => { //Fetch the index file const res = await fetch(getItemPath(postIndexPath)) return await res.json() } const getPostContent = async (post: PostMeta | string, extension = '.html'): Promise => { //Get the content url const contentUrl = `${getItemPath(contentDir)}/${defaultTo(post.id, post)}${extension}` //Fetch the content as text because it is html const res = await fetch(contentUrl) return await res.text() } const getIndexFilePath = async () : Promise => { return getItemPath(postIndexPath) } return{ getIndex, channelRootDir, contentDir, postIndexPath, getPostContent, getIndexFilePath } }