shack/src/posts.js

147 lines
3.4 KiB
JavaScript

// import knex from './knex';
import { verifyPrivilege } from './privileges';
import knex from './knex';
import { HttpError } from './errors';
import { fetchShelf } from './shelves';
import { fetchUsers } from './users';
function curateDatabasePost(post, {
shelf, users, votes,
}) {
const curatedPost = {
id: post.id,
title: post.title,
body: post.body,
link: post.link,
shelfId: post.shelf_id,
createdAt: post.created_at,
shelf,
user: users.find((user) => user.id === post.user_id),
votes: votes[post.id],
commentCount: Number(post.comment_count),
};
return curatedPost;
}
function curatePostVote(vote) {
return {
tally: Number(vote.tally),
total: Number(vote.total),
bump: !!vote.bump,
sink: !!vote.sink,
};
}
async function fetchPostVotes(postIds, user) {
const votes = await knex('posts_votes')
.select(
'post_id',
knex.raw('sum(value) as tally'),
knex.raw('count(id) as total'),
...(user ? [
knex.raw('bool_or(user_id = :userId and value = 1) as bump', { userId: knex.raw(user.id) }),
knex.raw('bool_or(user_id = :userId and value = -1) as sink', { userId: knex.raw(user.id) })]
: []),
)
.whereIn('post_id', postIds)
.groupBy('post_id');
return Object.fromEntries(votes.map((vote) => [vote.post_id, curatePostVote(vote)]));
}
async function fetchShelfPosts(shelfId, { user, limit = 100 } = {}) {
const shelf = await fetchShelf(shelfId);
const posts = await knex('posts')
.select('posts.*', knex.raw('count(comments.id) as comment_count'))
.leftJoin('comments', 'comments.post_id', 'posts.id')
.where('shelf_id', shelf.id)
.orderBy('created_at', 'desc')
.groupBy('posts.id')
.limit(limit);
const [users, votes] = await Promise.all([
fetchUsers(posts.map((post) => post.user_id)),
fetchPostVotes(posts.map((post) => post.id), user),
]);
return posts.map((post) => curateDatabasePost(post, { shelf, users, votes }));
}
async function fetchPost(postId, user) {
const post = await knex('posts')
.select('posts.*', knex.raw('count(comments.id) as comment_count'))
.leftJoin('comments', 'comments.post_id', 'posts.id')
.leftJoin('posts_votes', 'posts_votes.post_id', 'posts.id')
.where('posts.id', postId)
.groupBy('posts.id')
.first();
if (!post) {
throw new HttpError({
statusMessage: 'This post does not exist',
statusCode: 404,
});
}
const [shelf, users, votes] = await Promise.all([
fetchShelf(post.shelf_id),
fetchUsers([post.user_id]),
fetchPostVotes([post.id], user),
]);
return curateDatabasePost(post, { shelf, users, votes });
}
async function createPost(post, shelfId, user) {
await verifyPrivilege('createPost', user);
const shelf = await fetchShelf(shelfId);
if (!shelf) {
throw new HttpError({
statusMessage: 'The target shelf does not exist',
statusCode: 404,
});
}
const [postEntry] = await knex('posts')
.insert({
title: post.title,
body: post.body,
link: post.link,
shelf_id: shelf.id,
user_id: user.id,
})
.returning('*');
const users = await fetchUsers([postEntry.user_id]);
return curateDatabasePost(postEntry, { shelf, users });
}
async function votePost(postId, value, user) {
await knex('posts_votes')
.insert({
value,
post_id: postId,
user_id: user.id,
})
.onConflict(['post_id', 'user_id'])
.merge()
.returning('value');
const votes = await fetchPostVotes([postId], user);
return votes[postId];
}
export {
createPost,
fetchPost,
fetchShelfPosts,
votePost,
};