summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-06-01 04:40:09 +0200
committerRory& <root@rory.gay>2025-06-01 04:40:09 +0200
commita22c00fcefa10a99505c05393106fb3a655de243 (patch)
tree417757b1e5aa9f44702ed963569e0edf6209043d /src
parentAdd some tests (diff)
downloadnodejs-final-assignment-a22c00fcefa10a99505c05393106fb3a655de243.tar.xz
Add register with validation
Diffstat (limited to 'src')
-rw-r--r--src/api/middlewares/errorMiddleware.js39
-rw-r--r--src/api/routes/auth/registerRoute.js22
-rw-r--r--src/api/start.js6
-rw-r--r--src/db/dbAccess/user.js22
-rw-r--r--src/db/dbAccess/user.test.js2
-rw-r--r--src/dto/auth/LoginDto.js31
-rw-r--r--src/dto/auth/RegisterDto.js36
-rw-r--r--src/dto/auth/index.js2
-rw-r--r--src/dto/index.js1
-rw-r--r--src/util/error.js5
10 files changed, 144 insertions, 22 deletions
diff --git a/src/api/middlewares/errorMiddleware.js b/src/api/middlewares/errorMiddleware.js
new file mode 100644

index 0000000..b8de68e --- /dev/null +++ b/src/api/middlewares/errorMiddleware.js
@@ -0,0 +1,39 @@ +import { SafeNSoundError } from '#util/error.js'; +import { MongoServerError } from 'mongodb'; + +export function handleErrors(err, req, res, _next) { + if (err instanceof MongoServerError) { + if (err.code === 11000) { + // Duplicate key error + const newErr = new SafeNSoundError({ + errCode: 'DUPLICATE_KEY_ERROR', + message: 'A duplicate key error occurred.', + key: Object.keys(err.keyPattern)[0], + value: err.keyValue[Object.keys(err.keyValue)[0]] + }); + + err = newErr; + } + } + + if (err instanceof SafeNSoundError) { + console.log('meow'); + console.error(err.stack); + res.status(500).json(err); + } else { + // log and handle uncaught exceptions + console.error( + 'Unhandled error:', + Object.getPrototypeOf(err), + JSON.stringify(err, null, 2) + ); + console.error(err.stack); + res.status(500).json( + new SafeNSoundError({ + errCode: 'INTERNAL_SERVER_ERROR', + message: null, + err + }) + ); + } +} diff --git a/src/api/routes/auth/registerRoute.js b/src/api/routes/auth/registerRoute.js
index f2befd8..87762d3 100644 --- a/src/api/routes/auth/registerRoute.js +++ b/src/api/routes/auth/registerRoute.js
@@ -1,14 +1,20 @@ -import { DbUser } from '#db/index.js'; +import { registerUser } from '#db/index.js'; +import { LoginDto, RegisterDto } from '#dto/index.js'; export const registerRoute = { route: '/auth/register', - async onGet(req, res) { - const result = await User.create({ - username: req.query.username, - password: req.query.password, - email: req.query.email - }); + async onPost(req, res) { + const data = await RegisterDto.create(req.body); + await registerUser(data); + res.send(data); + } +}; - res.send(result); +export const loginRoute = { + route: '/auth/login', + async onPost(req, res) { + const data = await LoginDto.create(req.body); + await registerUser(data); + res.send(data); } }; diff --git a/src/api/start.js b/src/api/start.js
index 6f57afd..dcd5cf3 100644 --- a/src/api/start.js +++ b/src/api/start.js
@@ -2,7 +2,8 @@ import express from 'express'; import { registerRoutes } from './routes.js'; import { useCors, useLogging } from './middlewares/index.js'; import { initDb } from '#db/index.js'; -import {initJwt} from "#util/index.js"; +import { initJwt } from '#util/index.js'; +import { handleErrors } from '#api/middlewares/errorMiddleware.js'; const app = express(); const PORT = process.env.PORT ?? 3000; @@ -15,6 +16,7 @@ await initJwt(); app.use(express.json()); app.use(useCors); app.disable('x-powered-by'); +app.set('json spaces', 2); if (logRequests) { app.use(useLogging(logRequests)); @@ -22,6 +24,8 @@ if (logRequests) { registerRoutes(app); +app.use(handleErrors); + app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); diff --git a/src/db/dbAccess/user.js b/src/db/dbAccess/user.js
index 413b1cf..6301cb5 100644 --- a/src/db/dbAccess/user.js +++ b/src/db/dbAccess/user.js
@@ -1,23 +1,25 @@ import { hash, compare } from 'bcrypt'; import { DbUser } from '#db/schemas/index.js'; +import { RegisterDto } from '#dto/auth/index.js'; -export async function registerUser(username, password, email, type = 'user') { - if (!username || !password || !email) { - throw new Error( - 'Username, password, and email are required to register a user.' - ); - } +/** + * @param data {RegisterDto} + * @returns {Promise<(Error | HydratedDocument<InferSchemaType<module:mongoose.Schema>, ObtainSchemaGeneric<module:mongoose.Schema, "TVirtuals"> & ObtainSchemaGeneric<module:mongoose.Schema, "TInstanceMethods">, ObtainSchemaGeneric<module:mongoose.Schema, "TQueryHelpers">, ObtainSchemaGeneric<module:mongoose.Schema, "TVirtuals">>)[]>} + */ +export async function registerUser(data) { + if (!(data instanceof RegisterDto)) + throw new Error('Invalid data type. Expected RegisterDto.'); - const passwordHash = await hash(password, 10); + const passwordHash = await hash(data.password, 10); if (!passwordHash) { throw new Error('Failed to hash password.'); } return DbUser.create({ - username, + username: data.username, passwordHash, - email, - type + email: data.email, + type: data.type }); } diff --git a/src/db/dbAccess/user.test.js b/src/db/dbAccess/user.test.js
index e8e1af5..7b72d29 100644 --- a/src/db/dbAccess/user.test.js +++ b/src/db/dbAccess/user.test.js
@@ -26,7 +26,7 @@ await it('Can create user', async () => { }); await it('Can delete user', async () => { - const { username, password, email, user } = await createTestUser(); + const { password, user } = await createTestUser(); const deletePromise = deleteUser(user._id, password); await assert.doesNotReject(deletePromise); diff --git a/src/dto/auth/LoginDto.js b/src/dto/auth/LoginDto.js new file mode 100644
index 0000000..b675b4d --- /dev/null +++ b/src/dto/auth/LoginDto.js
@@ -0,0 +1,31 @@ +import { SafeNSoundError } from '#util/error.js'; +import Joi from 'joi'; + +export class LoginDto { + static schema = new Joi.object({ + username: Joi.string().required(), + email: Joi.string().email().required(), + password: Joi.string().required() + }).or('username', 'email'); + + username; + password; + + async Create({ username, email, password }) { + this.username = username ?? email; + this.password = password; + + try { + return await LoginDto.schema.validateAsync(this, { + abortEarly: true + }); + } catch (e) { + console.log(e); + throw new SafeNSoundError({ + errCode: 'JOI_VALIDATION_ERROR', + message: e.message, + validation_details: e.details + }); + } + } +} diff --git a/src/dto/auth/RegisterDto.js b/src/dto/auth/RegisterDto.js new file mode 100644
index 0000000..40f1959 --- /dev/null +++ b/src/dto/auth/RegisterDto.js
@@ -0,0 +1,36 @@ +import { SafeNSoundError } from '#util/error.js'; +import Joi from 'joi'; + +export class RegisterDto { + static schema = new Joi.object({ + username: Joi.string().required(), + email: Joi.string().email().required(), + password: Joi.string().required(), + type: Joi.string().valid('user', 'monitor', 'admin').required() + }); + + username; + email; + password; + type = 'user'; + + static async create(data) { + const obj = new RegisterDto(); + for (const key of Object.keys(data)) { + if (key in obj) { + obj[key] = data[key]; + } + } + + try { + return await RegisterDto.schema.validateAsync(obj); + } catch (e) { + console.log(e); + throw new SafeNSoundError({ + errCode: 'JOI_VALIDATION_ERROR', + message: e.message, + validation_details: e.details + }); + } + } +} diff --git a/src/dto/auth/index.js b/src/dto/auth/index.js new file mode 100644
index 0000000..6d57d5e --- /dev/null +++ b/src/dto/auth/index.js
@@ -0,0 +1,2 @@ +export * from './LoginDto.js'; +export * from './RegisterDto.js'; diff --git a/src/dto/index.js b/src/dto/index.js new file mode 100644
index 0000000..fd4a05a --- /dev/null +++ b/src/dto/index.js
@@ -0,0 +1 @@ +export * from './auth/index.js'; diff --git a/src/util/error.js b/src/util/error.js
index 96f1deb..5fc454a 100644 --- a/src/util/error.js +++ b/src/util/error.js
@@ -5,8 +5,9 @@ export class SafeNSoundError extends Error { this.errCode = options; super.message = this.getDefaultMessage(); } else if (typeof options === 'object') { - this.errCode = options.errCode || 'UNKNOWN_ERROR'; - super.message = options.message || this.getDefaultMessage(); + for (const key in options) { + this[key] = options[key]; + } } else { this.errCode = 'UNKNOWN_ERROR'; this.message =