Skip to content

Instantly share code, notes, and snippets.

@gaurangrshah
Created December 15, 2020 17:35
Show Gist options
  • Save gaurangrshah/8fe6330e872944603a35050b63ada12a to your computer and use it in GitHub Desktop.
Save gaurangrshah/8fe6330e872944603a35050b63ada12a to your computer and use it in GitHub Desktop.
local-markdown-cms
import { promises as fsPromises } from 'fs';
import os from 'os';
import path from 'path';
// used to render and work with local markdown files
export async function pathExists(filePath) {
try {
const stat = await fsPromises.stat(filePath);
return stat.isFile();
} catch (err) {
return false;
}
}
export async function getPostList({ ownerId }) {
// getAllUserBlogPosts called if ownerId is provided
const getAllUserBlogPosts = async () => {
// create posts directory -- if none exists?
const postPath = await genPostsPath();
// get file names for each file in posts directory
const filenames = await fsPromises.readdir(postPath);
const posts = [];
for (const filename of filenames) {
// get post JSON? content for each post
const postText = await fsPromises.readFile(path.join(postPath, filename), 'utf8');
// parse through post JSON content
const post = JSON.parse(postText);
posts.push(post);
}
return posts;
};
if (ownerId) {
// if ownerId is provided get all posts related to user
const posts = await getAllUserBlogPosts();
const ownerPosts = posts.filter((post) => post.ownerId === ownerId);
ownerPosts.sort((a, b) => b.createdAt - a.createdAt);
return ownerPosts;
}
// when no ownerId is passed in read posts from the local data directory
const markdownFiles = await fsPromises.readdir('data');
const dataDirPostList = markdownFiles.map((filename) => {
const slug = filename.replace(/.md$/, '');
const [year, month, date, ...rest] = slug.split('-');
const createdAt = new Date(`${year} ${month} ${date}`).getTime();
const title = rest.join(' ');
return {
slug,
createdAt,
title
};
});
const allPosts = [...dataDirPostList, ...(await getAllUserBlogPosts())];
allPosts.sort((a, b) => b.createdAt - a.createdAt);
return allPosts;
}
export async function getPost(slug) {
// Try to fetch from the user's post list
const filePath = await genPostsFilePath(slug);
if (await pathExists(filePath)) {
const postJson = await fsPromises.readFile(filePath, 'utf8');
return JSON.parse(postJson);
}
// Fetch from the data directory
const [year, month, day, ...rest] = slug.split('-');
const createdAt = new Date(`${year} ${month} ${day}`).getTime();
const title = rest.join(' ');
const content = await fsPromises.readFile(`data/${slug}.md`, 'utf8');
return {
slug: slug,
title,
content,
createdAt
};
}
export async function deletePost(slug) {
// Try to fetch from the user's post list
const filePath = await genPostsFilePath(slug);
if (await pathExists(filePath)) {
await fsPromises.unlink(filePath);
}
}
export async function createPost({ ownerId, slug, title, content }) {
const createdAt = Date.now();
const post = {
ownerId,
slug,
title,
content,
createdAt,
updatedAt: createdAt
};
const filePath = await genPostsFilePath(slug);
if (await pathExists(filePath)) {
throw new Error(`Blog post already exists`);
}
await fsPromises.writeFile(filePath, JSON.stringify(post, null, 2), 'utf8');
return post;
}
export async function updatePost({ ownerId, slug, title, content }) {
const createdAt = Date.now();
const post = await getPost(slug, { ownerId });
if (post.ownerId !== ownerId) {
throw new Error(`Invalid ownerId`);
}
post.title = title;
post.content = content;
post.updatedAt = Date.now();
const filePath = await genPostsFilePath(slug);
await fsPromises.writeFile(filePath, JSON.stringify(post, null, 2), 'utf8');
return post;
}
function genUserFilePath(userId) {
return path.join(os.tmpdir(), 'bulletproof-next-app', 'user', `${userId}.json`);
}
async function genCommentsFilePath(slug) {
const filePath = path.join(os.tmpdir(), 'bulletproof-next-app', 'comments', `${slug}.json`);
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
if (!(await pathExists(filePath))) {
await fsPromises.writeFile(filePath, '[]', 'utf8');
}
return filePath;
}
async function genPostsPath() {
const postsPath = path.join(os.tmpdir(), 'bulletproof-next-app', 'posts');
await fsPromises.mkdir(postsPath, { recursive: true });
return postsPath;
}
async function genPostsFilePath(slug) {
const filePath = path.join(await genPostsPath(), `${slug}.json`);
return filePath;
}
export async function saveUser(type, profile) {
const user = {
id: `${type}-${profile.id}`,
[type]: profile,
profile: {
name: profile.name,
avatar: profile.avatar
}
};
const payload = JSON.stringify(user);
const filePath = genUserFilePath(user.id);
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
await fsPromises.writeFile(filePath, payload, 'utf8');
return user.id;
}
export async function getUser(id) {
const filePath = genUserFilePath(id);
try {
const jsonString = await fsPromises.readFile(filePath, 'utf8');
return JSON.parse(jsonString);
} catch (err) {
if (err.code === 'ENOENT') {
return null;
}
throw err;
}
}
async function getAllComments(slug) {
const filePath = await genCommentsFilePath(slug);
const content = await fsPromises.readFile(filePath, 'utf8');
if (!content) {
return [];
}
return JSON.parse(content);
}
export async function getComments(slug, options = {}) {
const { sort = 1, limit = 5, offset = null } = options;
const comments = await getAllComments(slug);
// sort it
comments.sort((a, b) => {
return sort === 1 ? a.createdAt - b.createdAt : b.createdAt - a.createdAt;
});
// remove everything upto the offset
let foundOffset = false;
const commentsWithOffset = offset
? comments.filter((c) => {
if (foundOffset) {
return true;
}
foundOffset = c.createdAt == offset;
return false;
})
: comments;
// apply the limit
const commentsWithLimit = commentsWithOffset.slice(0, limit);
return commentsWithLimit;
}
export async function addComment(slug, comment) {
const filePath = await genCommentsFilePath(slug);
const comments = await getAllComments(slug);
if (!comment.id) {
comment.id = String(Math.random());
}
comments.push(comment);
await fsPromises.writeFile(filePath, JSON.stringify(comments), 'utf8');
return comments;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment