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 =
|