Skip to content

Commit

Permalink
Use ObjectId for _id, envelop error API responses, remove "all" middl…
Browse files Browse the repository at this point in the history
…eware (#133)

* Update to use ObjectId _id

* Reenable eslint and fix issues

* Envelop error messages in error object

* Remove "all" middleware
  • Loading branch information
hoangvvo authored Sep 23, 2021
1 parent 6035665 commit 48dc4b3
Show file tree
Hide file tree
Showing 24 changed files with 127 additions and 125 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": ["next/core-web-vitals", "prettier"],
"extends": ["eslint:recommended", "next/core-web-vitals", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
Expand Down
39 changes: 28 additions & 11 deletions api-lib/db/comment.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
export async function findComments(db, postId, from, limit) {
import { ObjectId } from 'mongodb';
import { dbProjectionUsers } from '.';

export async function findComments(db, postId, from, limit = 10) {
return db
.collection('comments')
.find({
postId,
...(from && {
createdAt: {
$lte: from,
.aggregate([
{
$match: {
postId: new ObjectId(postId),
...(from && {
createdAt: {
$lte: from,
},
}),
},
},
{ $limit: limit },
{ $sort: { _id: -1 } },
{
$lookup: {
from: 'users',
localField: 'creatorId',
foreignField: '_id',
as: 'creator',
},
}),
})
.sort({ $natural: -1 })
.limit(limit)
},
{ $unwind: '$creator' },
{ $project: dbProjectionUsers('creator.') },
])
.toArray();
}

export async function insertComment(db, postId, { content, creatorId }) {
const comment = {
content,
postId,
postId: new ObjectId(postId),
creatorId,
createdAt: new Date(),
};
Expand Down
9 changes: 5 additions & 4 deletions api-lib/db/post.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { nanoid } from 'nanoid';
import { ObjectId } from 'mongodb';
import { dbProjectionUsers } from './user';

export async function findPostById(db, id) {
const posts = await db.collection('posts').aggregate([
{ $match: { _id: id } },
{ $match: { _id: new ObjectId(id) } },
{ $limit: 1 },
{
$lookup: {
Expand Down Expand Up @@ -35,6 +35,7 @@ export async function findPosts(db, from, by, limit = 10) {
},
},
{ $limit: limit },
{ $sort: { _id: -1 } },
{
$lookup: {
from: 'users',
Expand All @@ -51,11 +52,11 @@ export async function findPosts(db, from, by, limit = 10) {

export async function insertPost(db, { content, creatorId }) {
const post = {
_id: nanoid(12),
content,
creatorId,
createdAt: new Date(),
};
await db.collection('posts').insertOne(post);
const { insertedId } = await db.collection('posts').insertOne(post);
post._id = insertedId;
return post;
}
14 changes: 8 additions & 6 deletions api-lib/db/user.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { nanoid } from 'nanoid';
import { ObjectId } from 'mongodb';
import normalizeEmail from 'validator/lib/normalizeEmail';

export async function UNSAFE_findUserForAuth(db, userId, all) {
return db
.collection('users')
.findOne(
{ _id: userId },
{ _id: new ObjectId(userId) },
{
projection: !all ? { password: 0 } : undefined,
}
Expand All @@ -16,7 +16,7 @@ export async function UNSAFE_findUserForAuth(db, userId, all) {
export async function findUserById(db, userId) {
return db
.collection('users')
.findOne({ _id: userId }, { projection: dbProjectionUsers() })
.findOne({ _id: new ObjectId(userId) }, { projection: dbProjectionUsers() })
.then((user) => user || null);
}

Expand All @@ -39,7 +39,7 @@ export async function updateUserById(db, id, data) {
return db
.collection('users')
.findOneAndUpdate(
{ _id: id },
{ _id: new ObjectId(id) },
{ $set: data },
{ returnDocument: 'after', projection: dbProjectionUsers() }
)
Expand All @@ -51,15 +51,17 @@ export async function insertUser(
{ email, password, bio = '', name, profilePicture, username }
) {
const user = {
_id: nanoid(12),
emailVerified: false,
profilePicture,
email,
name,
username,
bio,
};
await db.collection('users').insertOne({ ...user, password });
const { insertedId } = await db
.collection('users')
.insertOne({ ...user, password });
user._id = insertedId;
return user;
}

Expand Down
8 changes: 5 additions & 3 deletions api-lib/middlewares/ajv.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export function validateBody(schema) {
return next();
} else {
const error = validate.errors[0];
return res
.status(400)
.end(`"${error.instancePath.substring(1)}" ${error.message}`);
return res.status(400).json({
error: {
message: `"${error.instancePath.substring(1)}" ${error.message}`,
},
});
}
};
}
10 changes: 0 additions & 10 deletions api-lib/middlewares/all.js

This file was deleted.

15 changes: 10 additions & 5 deletions api-lib/middlewares/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ async function createIndexes(db) {
db
.collection('tokens')
.createIndex({ expireAt: -1 }, { expireAfterSeconds: 0 }),
db.collection('posts').createIndex({ createdAt: -1 }),
db.collection('comments').createIndex({ createdAt: -1 }),
db.collection('comments').createIndex({ postId: -1 }),
db.collection('users').createIndex({ email: 1 }, { unique: true }),
db.collection('users').createIndex({ username: 1 }, { unique: true }),
db
.collection('posts')
.createIndexes([{ key: { createdAt: -1 } }, { key: { creatorId: -1 } }]),
db
.collection('comments')
.createIndexes([{ key: { createdAt: -1 } }, { key: { postId: -1 } }]),
db.collection('users').createIndexes([
{ key: { email: 1 }, unique: true },
{ key: { username: 1 }, unique: true },
]),
]);
indexesCreated = true;
}
Expand Down
1 change: 0 additions & 1 deletion api-lib/middlewares/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { validateBody } from './ajv';
export { default as all } from './all';
export { default as auth } from './auth';
export { default as database } from './database';
4 changes: 2 additions & 2 deletions api-lib/nc.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const ncOpts = {
onError(err, req, res, next) {
onError(err, req, res) {
console.error(err);
res.statusCode =
err.status && err.status >= 100 && err.status < 600 ? err.status : 500;
res.end(err.message);
res.json({ message: err.message });
},
};
1 change: 1 addition & 0 deletions components/Skeleton/Skeleton.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import styles from './Skeleton.module.css';

const Skeleton = ({ width, height, className, children, hide }) => {
Expand Down
4 changes: 2 additions & 2 deletions pages/api/auth.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { passport } from '@/api-lib/auth';
import { all } from '@/api-lib/middlewares';
import { auth, database } from '@/api-lib/middlewares';
import { ncOpts } from '@/api-lib/nc';
import nc from 'next-connect';

const handler = nc(ncOpts);

handler.use(all);
handler.use(database, auth);

handler.post(passport.authenticate('local'), (req, res) => {
res.json({ user: req.user });
Expand Down
13 changes: 7 additions & 6 deletions pages/api/posts/[postId]/comments/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { ValidateProps } from '@/api-lib/constants';
import { findPostById } from '@/api-lib/db';
import { findComments, insertComment } from '@/api-lib/db/comment';
import { all, validateBody } from '@/api-lib/middlewares';
import { auth, database, validateBody } from '@/api-lib/middlewares';
import { ncOpts } from '@/api-lib/nc';
import nc from 'next-connect';

const handler = nc(ncOpts);

handler.use(all);
handler.use(database);

handler.get(async (req, res) => {
const post = await findPostById(req.db, req.query.postId);

if (!post) {
return res.status(404).send('post not found');
return res.status(404).json({ error: { message: 'Post is not found.' } });
}

const comments = await findComments(
Expand All @@ -23,10 +23,11 @@ handler.get(async (req, res) => {
req.query.limit ? parseInt(req.query.limit, 10) : undefined
);

return res.send({ comments });
return res.json({ comments });
});

handler.post(
auth,
validateBody({
type: 'object',
properties: {
Expand All @@ -37,15 +38,15 @@ handler.post(
}),
async (req, res) => {
if (!req.user) {
return res.status(401).send('unauthenticated');
return res.status(401).end();
}

const content = req.body.content;

const post = await findPostById(req.db, req.query.postId);

if (!post) {
return res.status(404).send('post not found');
return res.status(404).json({ error: { message: 'Post is not found.' } });
}

const comment = await insertComment(req.db, post._id, {
Expand Down
9 changes: 5 additions & 4 deletions pages/api/posts/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ValidateProps } from '@/api-lib/constants';
import { findPosts, insertPost } from '@/api-lib/db';
import { all, validateBody } from '@/api-lib/middlewares';
import { auth, database, validateBody } from '@/api-lib/middlewares';
import { ncOpts } from '@/api-lib/nc';
import nc from 'next-connect';

const handler = nc(ncOpts);

handler.use(all);
handler.use(database);

handler.get(async (req, res) => {
const posts = await findPosts(
Expand All @@ -16,10 +16,11 @@ handler.get(async (req, res) => {
req.query.limit ? parseInt(req.query.limit, 10) : undefined
);

res.send({ posts });
res.json({ posts });
});

handler.post(
auth,
validateBody({
type: 'object',
properties: {
Expand All @@ -30,7 +31,7 @@ handler.post(
}),
async (req, res) => {
if (!req.user) {
return res.status(401).send('unauthenticated');
return res.status(401).end();
}

const post = await insertPost(req.db, {
Expand Down
8 changes: 4 additions & 4 deletions pages/api/user/email/verify.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { createToken } from '@/api-lib/db';
import { CONFIG as MAIL_CONFIG, sendMail } from '@/api-lib/mail';
import { all } from '@/api-lib/middlewares';
import { auth, database } from '@/api-lib/middlewares';
import { ncOpts } from '@/api-lib/nc';
import nc from 'next-connect';

const handler = nc(ncOpts);

handler.use(all);
handler.use(database, auth);

handler.post(async (req, res) => {
if (!req.user) {
res.json(401).send('you need to be authenticated');
res.json(401).end();
return;
}

Expand All @@ -32,7 +32,7 @@ handler.post(async (req, res) => {
`,
});

res.end('ok');
res.status(204).end();
});

export default handler;
8 changes: 3 additions & 5 deletions pages/api/user/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ValidateProps } from '@/api-lib/constants';
import { updateUserById } from '@/api-lib/db';
import { all, validateBody } from '@/api-lib/middlewares';
import { auth, database, validateBody } from '@/api-lib/middlewares';
import { ncOpts } from '@/api-lib/nc';
import { v2 as cloudinary } from 'cloudinary';
import multer from 'multer';
Expand All @@ -23,13 +23,11 @@ if (process.env.CLOUDINARY_URL) {
});
}

handler.use(all);
handler.use(database, auth);

handler.get(async (req, res) => {
// Filter out password
if (!req.user) return res.json({ user: null });
const { password, ...u } = req.user;
return res.json({ user: u });
return res.json({ user: req.user });
});

handler.patch(
Expand Down
Loading

0 comments on commit 48dc4b3

Please sign in to comment.