Skip to content

Commit

Permalink
refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
rycwilson committed Sep 5, 2023
1 parent 8068d9f commit cb9d353
Show file tree
Hide file tree
Showing 9 changed files with 39 additions and 45 deletions.
10 changes: 3 additions & 7 deletions controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import User from '../models/user.js';
import { StatusCodes } from 'http-status-codes';
import { BadRequestError, UnauthenticatedError } from '../errors/index.js';

const register = async (req: Request, res: Response) => {
console.log(req.body)
export async function register(req: Request, res: Response) {
// mongoose will validate
// const { name, email, password } = req.body
// if (!name || !email || !password) {
Expand All @@ -15,8 +14,7 @@ const register = async (req: Request, res: Response) => {
res.status(StatusCodes.CREATED).json({ user: { name: user.fullName }, token: user.createJWT() });
};

const login = async (req: Request, res: Response) => {
console.log(req.headers);
export async function login(req: Request, res: Response) {
const { email, password } = req.body;
if (!email || !password) throw new BadRequestError('Email and password are required');
const user = await User.findOne({ email });
Expand All @@ -27,6 +25,4 @@ const login = async (req: Request, res: Response) => {
} else {
throw new UnauthenticatedError('Invalid credentials');
}
};

export { register, login };
};
25 changes: 12 additions & 13 deletions controllers/widgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@ import { StatusCodes } from 'http-status-codes';
import Widget from '../models/widget.js';
import { BadRequestError, NotFoundError } from '../errors/index.js';

const getAllWidgets = async (req: AuthenticatedRequest, res: Response) => {
export async function index(req: AuthenticatedRequest, res: Response) {
const widgets = await Widget.find({ createdBy: req.user.id }).sort('createdAt');
res.status(StatusCodes.OK).json({ widgets, count: widgets.length });
};

const createWidget = async (req: AuthenticatedRequest, res: Response) => {
const widget = await Widget.create({ ...req.body, createdBy: req.user.id });
res.status(StatusCodes.CREATED).json({ widget });
};
}

const getWidget = async (req: AuthenticatedRequest, res: Response) => {
export async function show(req: AuthenticatedRequest, res: Response) {
const { user: { id: userId }, params: { id: widgetId }} = req;
const widget = await Widget.findOne({ _id: widgetId, createdBy: userId }).select('-createdAt -updatedAt');
if (!widget) throw new NotFoundError(`No widget with id: ${widgetId}`);
res.status(StatusCodes.OK).json({ widget });
};

const updateWidget = async (req: AuthenticatedRequest, res: Response) => {
export async function create(req: AuthenticatedRequest, res: Response) {
const widget = await Widget.create({ ...req.body, createdBy: req.user.id });
res.status(StatusCodes.CREATED).json({ widget });
};


export async function update(req: AuthenticatedRequest, res: Response) {
const { user: { id: userId }, body: { company, position }, params: { id: widgetId } } = req;
if (!company || !position) throw new BadRequestError('Missing required fields');

Expand All @@ -34,11 +35,9 @@ const updateWidget = async (req: AuthenticatedRequest, res: Response) => {
res.status(200).json({ widget });
};

const deleteWidget = async (req: AuthenticatedRequest, res: Response) => {
export async function destroy(req: AuthenticatedRequest, res: Response) {
const { user: { id: userId }, params: { id: widgetId }} = req;
const widget = await Widget.findOneAndDelete({ _id: widgetId, createdBy: userId });
if (!widget) throw new NotFoundError(`No widget with id: ${widgetId}`);
res.status(200).send();
};

export { getAllWidgets, createWidget, getWidget, updateWidget, deleteWidget };
};
2 changes: 1 addition & 1 deletion errors/custom-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ class CustomApiError extends Error {
}
}

export default CustomApiError
export default CustomApiError;
2 changes: 2 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// https://dev.to/asjadanis/parsing-env-with-typescript-3jjm
// https://stackoverflow.com/questions/45194598/using-process-env-in-typescript

import { Request } from 'express';

Expand All @@ -16,6 +17,7 @@ declare global {
}
}

// https://blog.logrocket.com/extend-express-request-object-typescript/
namespace Express {
interface Request {
user?: RegisteredUser
Expand Down
9 changes: 3 additions & 6 deletions middleware/error-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import { CustomApiError } from '../errors/index.js';

type ErrorType = Error | MongooseError | MongoError | CustomApiError;

const errorHandler = (err: ErrorType, req: Request, res: Response, next: NextFunction) => {
export default function handleError(err: ErrorType, req: Request, res: Response, next: NextFunction) {
const customError = {
statusCode: (err instanceof CustomApiError && err.statusCode) || StatusCodes.INTERNAL_SERVER_ERROR,
msg: (err instanceof CustomApiError && err.message) || 'Sorry, there was an error'
}
if (err instanceof MongooseError.ValidationError) {
console.log(Object.values(err.errors).map(item => item.message).join(','))
customError.msg = Object.values(err.errors).map(item => item.message).join(', ');
customError.statusCode = 400;
}
Expand All @@ -21,12 +20,10 @@ const errorHandler = (err: ErrorType, req: Request, res: Response, next: NextFun
customError.statusCode = 404;
}
if (err instanceof MongoError && err.code === 11000) {
// TODO: keyValue property is not defined on the underlying type?
// customError.msg = `That ${Object.keys(err.keyValue)} already exists`;
customError.msg = 'Duplicate field'
customError.statusCode = 400;
}
// return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ err })
res.status(customError.statusCode).json({ msg: customError.msg });
}

export default errorHandler;
}
8 changes: 4 additions & 4 deletions models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Schema, type SchemaDefinition, model } from 'mongoose';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';

interface IUser {
interface User {
firstName: string,
lastName?: string,
email: string,
Expand Down Expand Up @@ -44,9 +44,9 @@ const userAttributes: SchemaDefinition = {
// }
};
const options = { timestamps: true };
const userSchema = new Schema<IUser>(userAttributes, options);
const userSchema = new Schema<User>(userAttributes, options);

userSchema.virtual('fullName').get(function (this: IUser) { return `${this.firstName} ${this.lastName}`.trim(); });
userSchema.virtual('fullName').get(function (this: User) { return `${this.firstName} ${this.lastName}`.trim(); });

userSchema.pre('save', async function () {
const salt = await bcrypt.genSalt(10);
Expand All @@ -66,4 +66,4 @@ userSchema.methods.comparePassword = async function (candidatePass: string) {
return isMatch;
}

export default model<IUser>('User', userSchema);
export default model<User>('User', userSchema);
4 changes: 2 additions & 2 deletions models/widget.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Schema, type SchemaDefinition, Types, model } from 'mongoose';

interface IWidget {
interface Widget {
name: string,
category: string,
createdBy: Types.ObjectId
Expand All @@ -24,6 +24,6 @@ const widgetAttributes: SchemaDefinition = {
}
};
const options = { timestamps: true };
const widgetSchema = new Schema<IWidget>(widgetAttributes, options);
const widgetSchema = new Schema<Widget>(widgetAttributes, options);

export default model('Widget', widgetSchema);
16 changes: 8 additions & 8 deletions routes/widgets.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { RequestHandler } from 'express';
import express from 'express';
import { getAllWidgets, createWidget, getWidget, updateWidget, deleteWidget } from '../controllers/widgets.js';
import { Router } from 'express';
import { index, show, create, update, destroy } from '../controllers/widgets.js';

const router = express.Router()
const router = Router();

// RequestHandler assertion is necessary due to argument being AuthenticatedRequest instead of Request
// TODO: probably a better way to do this
router.route('/')
.get(getAllWidgets as RequestHandler)
.post(createWidget as RequestHandler);
.get(index as RequestHandler)
.post(create as RequestHandler);

router.route('/:id')
.get(getWidget as RequestHandler)
.put(updateWidget as RequestHandler)
.delete(deleteWidget as RequestHandler);
.get(show as RequestHandler)
.put(update as RequestHandler)
.delete(destroy as RequestHandler);

export default router;
8 changes: 4 additions & 4 deletions server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Express, Request, Response } from 'express';
import type { Request, Response } from 'express';
import express from 'express';
import 'express-async-errors';

Expand All @@ -18,10 +18,10 @@ import widgetsRouter from './routes/widgets.js';
// middleware
import authenticateUser from './middleware/auth.js';
import handleNotFound from './middleware/not-found.js';
import handleErrors from './middleware/error-handler.js';
import handleError from './middleware/error-handler.js';

const appName = 'node-api';
const app: Express = express();
const app = express();

app
.get('/', (req: Request, res: Response) => res.send('node api')) // just a sanity check
Expand All @@ -38,7 +38,7 @@ app
.use('/api/v1/auth', authRouter)
.use('/api/v1/widgets', authenticateUser, widgetsRouter)
.use(handleNotFound)
.use(handleErrors);
.use(handleError);

connectDb(`${config.MONGO_URI}/${appName}`)
.then(start)
Expand Down

0 comments on commit cb9d353

Please sign in to comment.