Added voting. Improved comments.
This commit is contained in:
@@ -1,41 +1,80 @@
|
||||
import knex from './knex';
|
||||
import { fetchUsers } from './users';
|
||||
import { verifyPrivilege } from './privileges';
|
||||
import { HttpError } from './errors';
|
||||
|
||||
function curateDatabaseComment(comment, { users }) {
|
||||
return {
|
||||
id: comment.id,
|
||||
body: comment.body,
|
||||
parentId: comment.parent_id,
|
||||
userId: comment.user_id,
|
||||
user: users.find((user) => user.id === comment.user_id),
|
||||
createdAt: comment.created_at,
|
||||
};
|
||||
}
|
||||
|
||||
function threadComments(thread, commentsByParentId) {
|
||||
if (!thread) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return thread.map((comment) => ({
|
||||
...comment,
|
||||
comments: threadComments(commentsByParentId[comment.id], commentsByParentId),
|
||||
}));
|
||||
}
|
||||
|
||||
function nestComments(comments) {
|
||||
const commentsByParentId = comments.reduce((acc, comment) => {
|
||||
if (!acc[comment.parentId]) {
|
||||
acc[comment.parentId] = [];
|
||||
}
|
||||
|
||||
acc[comment.parentId].push(comment);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return threadComments(commentsByParentId.null, commentsByParentId);
|
||||
}
|
||||
|
||||
async function fetchPostComments(postId, { limit = 100 } = {}) {
|
||||
const comments = await knex('comments')
|
||||
.where('post_id', postId)
|
||||
.orderBy('created_at', 'asc')
|
||||
.orderBy('created_at', 'desc')
|
||||
.limit(limit);
|
||||
|
||||
const users = await fetchUsers(comments.map((comment) => comment.user_id));
|
||||
const curatedComments = comments.map((comment) => curateDatabaseComment(comment, { users }));
|
||||
|
||||
return comments.map((comment) => curateDatabaseComment(comment, { users }));
|
||||
const nestedComments = nestComments(curatedComments);
|
||||
|
||||
return nestedComments;
|
||||
}
|
||||
|
||||
async function addComment(comment, postId, user) {
|
||||
await verifyPrivilege('addComment', user);
|
||||
|
||||
const commentEntry = await knex('comments')
|
||||
if (!comment.body) {
|
||||
throw new HttpError({
|
||||
statusMessage: 'Comment cannot be empty',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const [commentEntry] = await knex('comments')
|
||||
.insert({
|
||||
body: comment.body,
|
||||
post_id: postId,
|
||||
parent_id: comment.parentId,
|
||||
user_id: user.id,
|
||||
})
|
||||
.returning('*');
|
||||
|
||||
console.log(comment, user);
|
||||
return curateDatabaseComment(commentEntry);
|
||||
const users = await fetchUsers([commentEntry.user_id]);
|
||||
|
||||
return curateDatabaseComment(commentEntry, { users });
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
75
src/posts.js
75
src/posts.js
@@ -6,7 +6,9 @@ import { HttpError } from './errors';
|
||||
import { fetchShelf } from './shelves';
|
||||
import { fetchUsers } from './users';
|
||||
|
||||
function curatePost(post, { shelf, users }) {
|
||||
function curateDatabasePost(post, {
|
||||
shelf, users, votes,
|
||||
}) {
|
||||
const curatedPost = {
|
||||
id: post.id,
|
||||
title: post.title,
|
||||
@@ -16,13 +18,40 @@ function curatePost(post, { shelf, users }) {
|
||||
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;
|
||||
}
|
||||
|
||||
async function fetchShelfPosts(shelfId, { limit = 100 } = {}) {
|
||||
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')
|
||||
@@ -33,15 +62,19 @@ async function fetchShelfPosts(shelfId, { limit = 100 } = {}) {
|
||||
.groupBy('posts.id')
|
||||
.limit(limit);
|
||||
|
||||
const users = await fetchUsers(posts.map((post) => post.user_id));
|
||||
const [users, votes] = await Promise.all([
|
||||
fetchUsers(posts.map((post) => post.user_id)),
|
||||
fetchPostVotes(posts.map((post) => post.id), user),
|
||||
]);
|
||||
|
||||
return posts.map((post) => curatePost(post, { shelf, users }));
|
||||
return posts.map((post) => curateDatabasePost(post, { shelf, users, votes }));
|
||||
}
|
||||
|
||||
async function fetchPost(postId) {
|
||||
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();
|
||||
@@ -53,12 +86,13 @@ async function fetchPost(postId) {
|
||||
});
|
||||
}
|
||||
|
||||
const [shelf, users] = await Promise.all([
|
||||
const [shelf, users, votes] = await Promise.all([
|
||||
fetchShelf(post.shelf_id),
|
||||
fetchUsers([post.user_id]),
|
||||
fetchPostVotes([post.id], user),
|
||||
]);
|
||||
|
||||
return curatePost(post, { shelf, users });
|
||||
return curateDatabasePost(post, { shelf, users, votes });
|
||||
}
|
||||
|
||||
async function createPost(post, shelfId, user) {
|
||||
@@ -73,9 +107,7 @@ async function createPost(post, shelfId, user) {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(post);
|
||||
|
||||
const postId = await knex('posts')
|
||||
const [postEntry] = await knex('posts')
|
||||
.insert({
|
||||
title: post.title,
|
||||
body: post.body,
|
||||
@@ -83,13 +115,32 @@ async function createPost(post, shelfId, user) {
|
||||
shelf_id: shelf.id,
|
||||
user_id: user.id,
|
||||
})
|
||||
.returning('id');
|
||||
.returning('*');
|
||||
|
||||
return postId;
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -36,15 +36,13 @@ async function fetchShelves({ limit = 10 } = {}) {
|
||||
}
|
||||
|
||||
async function createShelf(shelf, user) {
|
||||
const shelfEntry = await knex('shelves')
|
||||
const [shelfEntry] = await knex('shelves')
|
||||
.insert({
|
||||
slug: shelf.slug,
|
||||
founder_id: user.id,
|
||||
})
|
||||
.returning('*');
|
||||
|
||||
console.log('entry', shelfEntry);
|
||||
|
||||
return curateDatabaseShelf(shelfEntry);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createPost } from '../posts';
|
||||
import { createPost, votePost } from '../posts';
|
||||
|
||||
async function createPostApi(req, res) {
|
||||
const post = await createPost(req.body, req.params.shelfId, req.user);
|
||||
@@ -6,6 +6,13 @@ async function createPostApi(req, res) {
|
||||
res.send(post);
|
||||
}
|
||||
|
||||
async function votePostApi(req, res) {
|
||||
const votes = await votePost(req.params.postId, req.body.value, req.user);
|
||||
|
||||
res.send(votes);
|
||||
}
|
||||
|
||||
export {
|
||||
createPostApi as createPost,
|
||||
votePostApi as votePost,
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
|
||||
import { createShelf } from './shelves';
|
||||
|
||||
import { createPost } from './posts';
|
||||
import { createPost, votePost } from './posts';
|
||||
import { addComment } from './comments';
|
||||
|
||||
const logger = initLogger();
|
||||
@@ -70,6 +70,7 @@ async function startServer() {
|
||||
|
||||
// POSTS
|
||||
router.post('/api/shelves/:shelfId/posts', createPost);
|
||||
router.post('/api/posts/:postId/votes', votePost);
|
||||
|
||||
// COMMENTS
|
||||
router.post('/api/posts/:postId/comments', addComment);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createShelf } from '../shelves';
|
||||
|
||||
async function createShelfApi(req) {
|
||||
async function createShelfApi(req, res) {
|
||||
const shelf = await createShelf(req.body, req.user);
|
||||
|
||||
return shelf;
|
||||
res.send(shelf);
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
Reference in New Issue
Block a user