Skip to content

Commit

Permalink
feat: handle comments in posts
Browse files Browse the repository at this point in the history
  • Loading branch information
jkklapp committed Jun 8, 2022
1 parent 0a84224 commit 287a62e
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 21 deletions.
45 changes: 44 additions & 1 deletion backend/src/posts/posts.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jest.mock('./utils', () => {
getDisplayNameByUserId: jest
.fn()
.mockResolvedValueOnce('John Doe')
.mockResolvedValueOnce('Jane Doe'),
.mockResolvedValue('Jane Doe'),
};
});

Expand All @@ -22,6 +22,7 @@ describe('PostsController', () => {
old_env = process.env;
process.env = { MAX_NUMBER_POSTS_PER_DAY: '5', MAX_MESSAGE_LENGTH: '100' };
jest.spyOn(s, 'countAllforUserByDate').mockResolvedValue(0);
jest.spyOn(s, 'countAllForParentId').mockResolvedValue(0);
});
afterEach(() => {
process.env = old_env;
Expand Down Expand Up @@ -58,10 +59,50 @@ describe('PostsController', () => {
userName: 'John Doe',
likes: 0,
likedByMe: false,
comments: 0,
},
],
});
});
describe('when passing a parentId paramenter', () => {
it('returns only messages with that parentId', async () => {
const result = {
results: [
{
message: 'test',
date: 100000,
userId: '1234',
likes: [],
parentId: '123',
},
],
remainingMessages: 10,
nextPageToken: null,
};
jest
.spyOn(s, 'getMultiple')
.mockImplementation(() => Promise.resolve(result));

expect(
await c.getMultiple({ user: { user_id: '1234' } }, 10, null, '123'),
).toEqual({
nextPageToken: null,
remainingMessages: 5,
results: [
{
date: 100000,
message: 'test',
userId: '1234',
userName: 'Jane Doe',
likes: 0,
likedByMe: false,
parentId: '123',
comments: 0,
},
],
});
});
});
});
describe('create', () => {
beforeEach(() => {
Expand All @@ -85,6 +126,7 @@ describe('PostsController', () => {
userName: 'Test',
likes: 0,
likedByMe: false,
comments: 0,
});
});
describe('when the user has reached the max number of posts per day', () => {
Expand Down Expand Up @@ -130,6 +172,7 @@ describe('PostsController', () => {
userName: 'Jane Doe',
likes: 1,
likedByMe: true,
comments: 0,
});
});
});
Expand Down
54 changes: 52 additions & 2 deletions backend/src/posts/posts.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ export class PostsController {
@Req() request: any,
@Query('limit', ParseIntPipe) limit: number,
@Query('startAfter') startAfter?: string,
@Query('parentId') parentId?: string,
): Promise<PostDocumentResult> {
const { user } = request;
const { user_id: userId } = user;
const { results, nextPageToken } = await this.service.getMultiple(
limit,
startAfter,
parentId,
);
const resolvedPosts: ResolvedPostDocument[] = [];
for (const p in results) {
Expand All @@ -46,6 +48,7 @@ export class PostsController {
likes: (results[p].likes || []).length,
likedByMe: (results[p].likes || []).includes(userId),
userName: await getDisplayNameByUserId(results[p].userId),
comments: await this.service.countAllForParentId(results[p].id),
});
}

Expand Down Expand Up @@ -102,13 +105,56 @@ export class PostsController {
);
}

const newPost = await this.service.create(post.message, userId);
const newPost = await this.service.create(post, userId);
const comments = post.parentId
? await this.service.countAllForParentId(post.parentId)
: 0;

return {
...newPost,
likes: 0,
likedByMe: false,
userName,
comments,
};
}

@Post(':/id')
@UseGuards(FirebaseAuthGuard)
public async comment(
@Req() request: any,
@Param('id') parentId: string,
@Body() post: NewPostDocument,
): Promise<ResolvedPostDocument> {
const { user } = request;
const {
user_id: userId,
name: userName,
email_verified: emailVerified,
} = user;

if (!emailVerified) {
throw new BadRequestException('You must verify your email to comment');
}

const maxMessageLength = parseInt(process.env.MAX_MESSAGE_LENGTH, 10);
if (post.message.length > maxMessageLength) {
throw new BadRequestException(
'Message comment is too long. Max length is ' + maxMessageLength,
);
}

const newPost = await this.service.create({ ...post, parentId }, userId);
const comments = post.parentId
? await this.service.countAllForParentId(post.parentId)
: 0;

return {
...newPost,
likes: 0,
likedByMe: false,
userName,
comments,
};
}

Expand All @@ -134,12 +180,16 @@ export class PostsController {
const post = await this.service.get(id);

const userName = await getDisplayNameByUserId(post.userId);
const comments = post.parentId
? await this.service.countAllForParentId(post.parentId)
: 0;

return {
...post,
userName,
likes: post.likes.length,
likes: (post.likes || []).length,
likedByMe,
comments,
};
}
}
4 changes: 4 additions & 0 deletions backend/src/posts/posts.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class CreatePostDocumentDto {
message: string;
parentId?: string;
}
14 changes: 10 additions & 4 deletions backend/src/posts/posts.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ describe('PostsService', () => {
dataMock = jest.fn().mockReturnValue({
message: 'test',
date: 100000,
userId: '1234',
userId: 'abc',
parentId: '123',
});
postsCollectionMock = {
get: jest.fn().mockResolvedValue({
Expand All @@ -124,19 +125,24 @@ describe('PostsService', () => {
jest.restoreAllMocks();
});
it('should return a new post', async () => {
const result = await service.create('test', '1234', 'Test');
const result = await service.create(
{ message: 'test', parentId: '123' },
'abc',
);

expect(result).toEqual({
date: 100000,
id: '1',
message: 'test',
userId: '1234',
userId: 'abc',
parentId: '123',
});
expect(postsCollectionMock.doc).toHaveBeenCalled();
expect(postsCollectionMock.set).toHaveBeenCalledWith({
date: expect.anything(),
message: 'test',
userId: '1234',
userId: 'abc',
parentId: '123',
});
expect(dataMock).toHaveBeenCalled();
});
Expand Down
33 changes: 28 additions & 5 deletions backend/src/posts/posts.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Injectable, Inject, Logger } from '@nestjs/common';
import * as dayjs from 'dayjs';
import { CollectionReference } from '@google-cloud/firestore';
import { PostDocument, PaginatedResults } from './posts.types';
import { CreatePostDocumentDto } from './posts.dto';

@Injectable()
export class PostsService {
Expand Down Expand Up @@ -51,6 +52,7 @@ export class PostsService {
async getMultiple(
limit: number,
startAfter?: string | undefined,
parentId?: string | undefined,
): Promise<PaginatedResults> {
const _startAfter = startAfter ? parseInt(startAfter, 10) : '';
const noMoreResults = startAfter ? -1 : null;
Expand All @@ -60,10 +62,14 @@ export class PostsService {
.limit(limit)
.get()
.then(async (snapshot) => {
const results: PostDocument[] = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
const results: PostDocument[] = snapshot.docs
.map((doc) => ({
id: doc.id,
...doc.data(),
}))
.filter((post) => {
return post.parentId === parentId;
});
const q = await snapshot.query.offset(limit).get();

return {
Expand All @@ -75,6 +81,19 @@ export class PostsService {
});
}

async countAllForParentId(parentId: string): Promise<number> {
return this.postsCollection
.where('parentId', '==', parentId)
.get()
.then((snapshot) => {
return snapshot.size;
})
.catch((err) => {
this.logger.error(err);
return 0;
});
}

async countAllforUserByDate(userId: string, date: number): Promise<number> {
return this.postsCollection
.where('userId', '==', userId)
Expand All @@ -83,13 +102,17 @@ export class PostsService {
.then((snapshot) => snapshot.size);
}

async create(message: string, userId: string): Promise<PostDocument> {
async create(
{ message, parentId }: CreatePostDocumentDto,
userId: string,
): Promise<PostDocument> {
const t = dayjs(new Date()).valueOf();
const docRef = this.postsCollection.doc(t.toString());
await docRef.set({
message,
date: new Date().getTime(),
userId,
parentId,
});

return docRef.get().then((postDoc) => {
Expand Down
5 changes: 4 additions & 1 deletion backend/src/posts/posts.types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export class NewPostDocument {
message: string;
parentId?: string;
}

export class PostDocument extends NewPostDocument {
export class PostDocument {
static collectionName = 'posts';

message: string;
date: number;
userId: string;
id?: string;
likes?: string[];
parentId?: string;
}

export class PaginatedResults {
Expand All @@ -31,6 +33,7 @@ export class ResolvedPostDocument {
id?: string;
likes: number;
likedByMe: boolean;
comments: number;
}

export class UpdatePostDocument {
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/components/Dashboard/Posts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@
</div>
<div class="flex flex-wrap place-items-end">
<div class="w-100 mr-auto"></div>
<a
v-show="p.comments > 0"
href="#"
class="text-gray-500 dark:text-gray-400"
@click.prevent="() => fetchPosts(p.id)"
>
<small
>{{ p.comments }} comment<small v-show="p.comments > 1"
>s</small
></small
>
</a>
<a
v-show="likingPost != p.id"
href="#"
Expand Down Expand Up @@ -105,8 +117,8 @@ export default {
date(date) {
return moment(date).fromNow();
},
fetchPosts() {
this.$store.dispatch('fetchPosts', this.$store.state);
fetchPosts(parentId) {
this.$store.dispatch('fetchPosts', { ...this.$store.state, parentId });
},
reset() {
this.$store.dispatch('resetPostsPagination');
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/store/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ export default {
commit('SET_USER', user);
commit('SET_LOGGED_IN', user.email && user.displayName);
},
async fetchPosts({ commit }, { startAfter, limit }) {
// make get request with bearer token authentication
async fetchPosts({ commit }, { startAfter, limit, parentId }) {
commit('IS_LOADING_POSTS', true);
const { data } = await apiRequest('GET', '/posts', {
startAfter,
limit,
parentId,
});
const { results, nextPageToken, remainingMessages } = data;
commit('SET_POSTS', results);
commit('SET_POSTS', { results, parentId });
commit('SET_START_AFTER', nextPageToken);
commit('SET_REMAINING_MESSAGES', remainingMessages || 0);
commit('IS_LOADING_POSTS', false);
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/store/mutations.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ export default {
SET_LOGGED_IN(state, data) {
state.loggedIn = data;
},
SET_POSTS(state, data) {
state.posts = data;
SET_POSTS(state, { results, parentId }) {
if (parentId) {
const parent = state.posts.find((post) => post.id === parentId);
state.posts = [parent, ...results];
} else {
state.posts = results;
}
},
SET_POST_BY_ID(state, data) {
const index = state.posts.findIndex((post) => post.id === data.id);
Expand Down
Loading

0 comments on commit 287a62e

Please sign in to comment.