224 lines
5.7 KiB
JavaScript
224 lines
5.7 KiB
JavaScript
// import knex from './knex';
|
|
import { verifyPrivilege } from './privileges';
|
|
import knex from './knex';
|
|
import { HttpError } from './errors';
|
|
|
|
import { fetchShelf, fetchShelves } from './shelves';
|
|
import { fetchUsers } from './users';
|
|
import { generateThumbnail } from './thumbnails';
|
|
import slugify from './utils/slugify';
|
|
|
|
import initLogger from './logger';
|
|
|
|
const logger = initLogger();
|
|
|
|
const emptyVote = {
|
|
tally: 0,
|
|
total: 0,
|
|
bump: false,
|
|
sink: false,
|
|
};
|
|
|
|
function curateDatabasePost(post, {
|
|
shelf, shelves, users, vote, votes,
|
|
}) {
|
|
const curatedPost = {
|
|
id: post.id,
|
|
title: post.title,
|
|
slug: slugify(post.title, { limit: 50 }),
|
|
body: post.body,
|
|
link: post.link,
|
|
shelfId: post.shelf_id,
|
|
createdAt: post.created_at,
|
|
hasThumbnail: post.thumbnail,
|
|
shelf: shelf || shelves?.[post.shelf_id],
|
|
user: users.find((user) => user.id === post.user_id),
|
|
vote: vote || votes?.[post.id] || emptyVote,
|
|
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(shelfIds, { user, limit = 100 } = {}) {
|
|
const shelves = await fetchShelves([].concat(shelfIds));
|
|
|
|
const posts = await knex('posts')
|
|
.select('posts.*', knex.raw('count(comments.id) as comment_count'))
|
|
.leftJoin('comments', 'comments.post_id', 'posts.id')
|
|
.whereIn('shelf_id', Object.keys(shelves))
|
|
.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, { shelves, users, votes }));
|
|
}
|
|
|
|
async function fetchUserPosts(user, { limit = 20 } = {}) {
|
|
if (!user) {
|
|
throw new HttpError({
|
|
statusMessage: 'You are not logged in',
|
|
statusCode: 401,
|
|
});
|
|
}
|
|
|
|
const posts = await knex('shelves_subscriptions')
|
|
.select('posts.*', knex.raw('count(comments.id) as comment_count'))
|
|
.leftJoin('posts', 'posts.shelf_id', 'shelves_subscriptions.shelf_id')
|
|
.leftJoin('comments', 'comments.post_id', 'posts.id')
|
|
.where('shelves_subscriptions.user_id', user.id)
|
|
.groupBy('posts.id')
|
|
.orderBy('created_at', 'desc')
|
|
.limit(limit);
|
|
|
|
const [shelves, users, votes] = await Promise.all([
|
|
fetchShelves(posts.map((post) => post.shelf_id)),
|
|
fetchUsers(posts.map((post) => post.user_id)),
|
|
fetchPostVotes(posts.map((post) => post.id), user),
|
|
]);
|
|
|
|
return posts.map((post) => curateDatabasePost(post, { shelves, users, votes }));
|
|
}
|
|
|
|
async function fetchAllPosts({ user, limit = 20 } = {}) {
|
|
const posts = await knex('posts')
|
|
.select('posts.*', knex.raw('count(comments.id) as comment_count'))
|
|
.leftJoin('comments', 'comments.post_id', 'posts.id')
|
|
.orderBy('created_at', 'desc')
|
|
.groupBy('posts.id')
|
|
.limit(limit);
|
|
|
|
const [shelves, users, votes] = await Promise.all([
|
|
fetchShelves(posts.map((post) => post.shelf_id)),
|
|
fetchUsers(posts.map((post) => post.user_id)),
|
|
fetchPostVotes(posts.map((post) => post.id), user),
|
|
]);
|
|
|
|
return posts.map((post) => curateDatabasePost(post, { shelves, 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 votePost(postId, value, user) {
|
|
if (value === 0) {
|
|
await knex('posts_votes')
|
|
.where({
|
|
post_id: postId,
|
|
user_id: user.id,
|
|
})
|
|
.delete();
|
|
} else {
|
|
await knex('posts_votes')
|
|
.insert({
|
|
value,
|
|
post_id: postId,
|
|
user_id: user.id,
|
|
})
|
|
.onConflict(['post_id', 'user_id'])
|
|
.merge();
|
|
}
|
|
|
|
logger.silly(`User ${user.username} voted ${value} on post ${postId}`);
|
|
|
|
const votes = await fetchPostVotes([postId], user);
|
|
|
|
return votes[postId] || emptyVote;
|
|
}
|
|
|
|
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, vote] = await Promise.all([
|
|
fetchUsers([postEntry.user_id]),
|
|
votePost(postEntry.id, 1, user),
|
|
]);
|
|
|
|
logger.verbose(`User ${user.username} created post ${postEntry.id} on s/${shelf.slug}`);
|
|
|
|
generateThumbnail(postEntry.id);
|
|
|
|
return curateDatabasePost(postEntry, { shelf, users, vote });
|
|
}
|
|
|
|
export {
|
|
createPost,
|
|
fetchPost,
|
|
fetchShelfPosts,
|
|
fetchUserPosts,
|
|
fetchAllPosts,
|
|
votePost,
|
|
};
|