Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CHE-120] Add Total Posts Display On Threads #149

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const ThreadDetail = ({ forumId, threadId }: ThreadDetailProps) => {
}
};

if (pending) return null;
brok3turtl3 marked this conversation as resolved.
Show resolved Hide resolved
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!thread) return <div>Thread not found.</div>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ const ThreadsDisplay = ({ forumId, onThreadSelect }: ThreadsDisplayProps) => {
setCreatingThread(!creatingThread);
};

function formatReplies(count: number) {
if (count === 0) return 'No replies';
if (count === 1) return '1 reply';
return `${count} replies`;
}

if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;

Expand Down Expand Up @@ -77,6 +83,8 @@ const ThreadsDisplay = ({ forumId, onThreadSelect }: ThreadsDisplayProps) => {
<small>
Started by {thread.user.firstName} on{' '}
{new Date(thread.createdAt).toLocaleDateString()}
<span className="mx-2 text-gray-400">|</span>
<span className="italic">{formatReplies(thread.postCount)}</span>
brok3turtl3 marked this conversation as resolved.
Show resolved Hide resolved
</small>
</li>
))}
Expand Down
1 change: 1 addition & 0 deletions client/types/forums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface Thread {
content: string;
user: IUser;
createdAt: string;
postCount: number;
}

export interface IForum {
Expand Down
7 changes: 3 additions & 4 deletions server/controllers/forumController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Request, Response, NextFunction } from 'express';
import Forum from '../models/forumModel';
import Thread from '../models/threadModel';
import { sortAndPopulate } from './helpers/queryHelpers';
import { aggregateThreadsWithPostCount } from './helpers/queryHelpers';

// ENDPOINT POST api/forums
// PURPOSE Create a new forum
Expand Down Expand Up @@ -51,12 +50,12 @@ const getForumById = async (req: Request, res: Response, next: NextFunction) =>

try {
const forum = await Forum.findById(forumId);

if (!forum) {
return res.status(404).json({ message: 'Forum not found' });
}

const threadsQuery = Thread.find({ forum: forumId });
const threads = await sortAndPopulate(threadsQuery);
const threads = await aggregateThreadsWithPostCount(forumId, 'createdAt', -1);

res.status(200).json({ forum, threads });
} catch (error) {
Expand Down
79 changes: 73 additions & 6 deletions server/controllers/helpers/queryHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,81 @@
import mongoose, { Query } from "mongoose";
import Thread from '../../models/threadModel';
import mongoose from 'mongoose';

type SortOrder = 1 | -1;

export const sortAndPopulate = (
query: Query<any, any, any>,
sortField: string = "createdAt",
interface SortAndPopulateQuery<T> {
sort: (arg: { [key: string]: SortOrder }) => SortAndPopulateQuery<T>;
populate: (field: string, select?: string) => SortAndPopulateQuery<T>;
exec: () => Promise<T>;
}
interface AggregateQuery<T> {
sort: (arg: { [key: string]: SortOrder }) => AggregateQuery<T>;
project: (field: { [key: string]: 0 | 1 }) => AggregateQuery<T>;
lookup: (lookupOptions: {
from: string;
localField: string;
foreignField: string;
as: string;
}) => AggregateQuery<T>;
exec: () => Promise<T[]>;
}

export const sortAndPopulate = <T>(
query: SortAndPopulateQuery<T>,
sortField: string = 'createdAt',
sortOrder: number = -1,
populateField: string = "user",
selectFields: string = "firstName lastName"
populateField: string = 'user',
selectFields: string = 'firstName lastName',
) => {
const sortObj = { [sortField]: sortOrder } as { [key: string]: SortOrder };
return query.sort(sortObj).populate(populateField, selectFields).exec();
};

export const aggregateSort = <T>(
pipeline: AggregateQuery<T>,
sortField: string = 'createdAt',
sortOrder: SortOrder = -1,
) => {
const sortObj: Record<string, SortOrder> = { [sortField]: sortOrder };
return pipeline.sort(sortObj).exec();
};

export const aggregateThreadsWithPostCount = (
forumId?: string,
brok3turtl3 marked this conversation as resolved.
Show resolved Hide resolved
sortField: string = 'createdAt',
sortOrder: SortOrder = -1,
) => {
const baseStages = [
{
$lookup: {
from: 'posts',
localField: '_id',
foreignField: 'thread',
as: 'posts',
},
},
{
$addFields: {
postCount: { $size: '$posts' },
},
},
{
$project: {
posts: 0,
},
},
];

const threadsAggregate = forumId
? [
{
$match: {
forum: new mongoose.Types.ObjectId(forumId),
},
},
...baseStages,
]
: baseStages;

return aggregateSort(Thread.aggregate(threadsAggregate), sortField, sortOrder);
};
7 changes: 4 additions & 3 deletions server/controllers/threadController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from 'express';
import Post from '../models/postModel';
import Thread from '../models/threadModel';
import { CustomRequest } from '../types/customRequest';
import { sortAndPopulate } from './helpers/queryHelpers';
import { sortAndPopulate, aggregateThreadsWithPostCount } from './helpers/queryHelpers';

// ENDPOINT POST api/:forumId/threads
// PURPOSE Create a new thread
Expand Down Expand Up @@ -39,8 +39,8 @@ const createThread = async (req: CustomRequest, res: Response, next: NextFunctio
// ACCESS Private
const getAllThreads = async (req: CustomRequest, res: Response, next: NextFunction) => {
try {
const threadsQuery = Thread.find({});
const threads = await sortAndPopulate(threadsQuery);
const threads = await aggregateThreadsWithPostCount(undefined, 'createdAt', -1);

res.status(200).json(threads);
} catch (error) {
next({
Expand All @@ -60,6 +60,7 @@ const listThreadsByForumId = async (req: Request, res: Response, next: NextFunct
try {
const threadsQuery = Thread.find({ forum: forumId });
const threads = await sortAndPopulate(threadsQuery);

res.status(200).json(threads);
} catch (error) {
next({
Expand Down
Loading