Updating backend

- Added database self check
- Added more @me handlers
- Fixed issues
This commit is contained in:
2024-03-31 22:25:01 +02:00
parent 82486c68e7
commit 3a7e065ba0
5 changed files with 1045 additions and 7 deletions

980
database.json Normal file
View File

@@ -0,0 +1,980 @@
{
"tables": [
{
"name": "roles",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "name",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL",
"UNIQUE"
]
},
{
"name": "user_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "role_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "verification_code_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "ban_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "patient_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "doctor_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "service_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "company_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "hospital_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "room_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "appointment_bitfield",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
}
],
"data": [
{
"name": "Admin",
"user_bitfield": 7,
"role_bitfield": 7,
"verification_code_bitfield": 7,
"ban_bitfield": 7,
"patient_bitfield": 7,
"doctor_bitfield": 7,
"service_bitfield": 7,
"company_bitfield": 7,
"hospital_bitfield": 7,
"room_bitfield": 7,
"appointment_bitfield": 7
},
{
"name": "Doctor",
"user_bitfield": 0,
"role_bitfield": 0,
"verification_code_bitfield": 0,
"ban_bitfield": 0,
"patient_bitfield": 1,
"doctor_bitfield": 1,
"service_bitfield": 1,
"company_bitfield": 1,
"hospital_bitfield": 1,
"room_bitfield": 1,
"appointment_bitfield": 0
},
{
"name": "Patient",
"user_bitfield": 0,
"role_bitfield": 0,
"verification_code_bitfield": 0,
"ban_bitfield": 0,
"patient_bitfield": 0,
"doctor_bitfield": 1,
"service_bitfield": 1,
"company_bitfield": 1,
"hospital_bitfield": 1,
"room_bitfield": 1,
"appointment_bitfield": 0
}
]
},
{
"name": "users",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "first_name",
"type": "VARCHAR(64)",
"constraints": [
"NOT NULL"
]
},
{
"name": "last_name",
"type": "VARCHAR(64)",
"constraints": [
"NOT NULL"
]
},
{
"name": "username",
"type": "VARCHAR(64)",
"constraints": [
"NOT NULL"
]
},
{
"name": "password",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "email",
"type": "VARCHAR(128)",
"constraints": [
"NOT NULL"
]
},
{
"name": "email_verified",
"type": "BOOLEAN",
"constraints": [
"NOT NULL",
"DEFAULT FALSE"
]
},
{
"name": "phone",
"type": "VARCHAR(32)",
"constraints": [
"DEFAULT 'None'"
]
},
{
"name": "phone_verified",
"type": "BOOLEAN",
"constraints": [
"NOT NULL",
"DEFAULT FALSE"
]
}
]
},
{
"name": "user_roles",
"columns": [
{
"name": "user_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "role_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
}
],
"constraints": [
{
"primary_key": true,
"columns": ["user_id", "role_id"]
},
{
"foreign_key": true,
"name": "user_roles_user_id",
"column": "user_id",
"reference": "users(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"foreign_key": true,
"name": "user_roles_role_id",
"column": "role_id",
"reference": "roles(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "user_roles_user_idx",
"columns": ["user_id"]
},
{
"index": true,
"name": "user_roles_role_idx",
"columns": ["role_id"]
}
]
},
{
"name": "verification_codes",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "user_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "verification_code",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "type",
"type": "VARCHAR(32)",
"constraints": [
"NOT NULL"
]
},
{
"name": "created_at",
"type": "TIMESTAMP",
"constraints": [
"NOT NULL",
"DEFAULT CURRENT_TIMESTAMP"
]
}
],
"constraints": [
{
"foreign_key": true,
"name": "verification_codes_user_id",
"column": "user_id",
"reference": "users(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "verification_codes_user_idx",
"columns": ["user_id"]
}
]
},
{
"name": "bans",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "user_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "reason",
"type": "TEXT",
"constraints": [
"NOT NULL"
]
},
{
"name": "created_at",
"type": "TIMESTAMP",
"constraints": [
"NOT NULL",
"DEFAULT CURRENT_TIMESTAMP"
]
}
],
"constraints": [
{
"foreign_key": true,
"name": "bans_user_id",
"column": "user_id",
"reference": "users(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "bans_user_idx",
"columns": ["user_id"]
}
]
},
{
"name": "doctors",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "user_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"UNIQUE"
]
},
{
"name": "email",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "phone",
"type": "VARCHAR(20)",
"constraints": [
"NOT NULL"
]
},
{
"name": "specialty",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "status",
"type": "ENUM('Available', 'Absent', 'Unavailable')",
"constraints": [
"NOT NULL",
"DEFAULT 'Available'"
]
},
{
"name": "is_verified",
"type": "BOOLEAN",
"constraints": [
"NOT NULL",
"DEFAULT FALSE"
]
}
],
"constraints": [
{
"foreign_key": true,
"name": "doctors_user_id",
"column": "user_id",
"reference": "users(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "doctors_user_idx",
"columns": ["user_id"]
}
]
},
{
"name": "patients",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "user_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"UNIQUE"
]
},
{
"name": "date_of_birth",
"type": "DATE",
"constraints": [
"NOT NULL"
]
},
{
"name": "gender",
"type": "ENUM('M', 'F', 'O')",
"constraints": [
"NOT NULL"
]
},
{
"name": "address",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "social_security_number",
"type": "VARCHAR(128)",
"constraints": [
"NOT NULL"
]
},
{
"name": "insurance_number",
"type": "VARCHAR(128)",
"constraints": [
"NOT NULL"
]
}
],
"constraints": [
{
"foreign_key": true,
"name": "patients_user_id",
"column": "user_id",
"reference": "users(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "patients_user_idx",
"columns": ["user_id"]
}
]
},
{
"name": "services",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "name",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "description",
"type": "TEXT",
"constraints": [
"NOT NULL"
]
},
{
"name": "price",
"type": "DECIMAL(10, 2)",
"constraints": [
"NOT NULL"
]
}
]
},
{
"name": "service_doctors",
"columns": [
{
"name": "service_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "doctor_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
}
],
"constraints": [
{
"primary_key": true,
"columns": ["service_id", "doctor_id"]
},
{
"foreign_key": true,
"name": "service_doctors_service_id",
"column": "service_id",
"reference": "services(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"foreign_key": true,
"name": "service_doctors_doctor_id",
"column": "doctor_id",
"reference": "doctors(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "service_doctors_service_idx",
"columns": ["service_id"]
},
{
"index": true,
"name": "service_doctors_doctor_idx",
"columns": ["doctor_id"]
}
]
},
{
"name": "companies",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "name",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "code",
"type": "VARCHAR(2)",
"constraints": [
"NOT NULL"
]
},
{
"name": "logo",
"type": "VARCHAR(255)"
}
]
},
{
"name": "hospitals",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "company_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "name",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "code",
"type": "VARCHAR(3)",
"constraints": [
"NOT NULL"
]
},
{
"name": "country",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "region",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "city",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "address",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
}
],
"constraints": [
{
"foreign_key": true,
"name": "hospitals_company_id",
"column": "company_id",
"reference": "companies(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "hospitals_company_idx",
"columns": ["company_id"]
}
]
},
{
"name": "rooms",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "hospital_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "name",
"type": "VARCHAR(255)",
"constraints": [
"NOT NULL"
]
},
{
"name": "code",
"type": "VARCHAR(3)",
"constraints": [
"NOT NULL"
]
},
{
"name": "floor",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "room_number",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "room_type",
"type": "ENUM('General Ward', 'Private', 'Intensive Care Unit', 'Labor and Delivery', 'Operating', 'Recovery', 'Isolation', 'Emergency', 'Imaging', 'Procedure', 'Physical Therapy', 'Consultation')",
"constraints": [
"NOT NULL"
]
}
],
"constraints": [
{
"foreign_key": true,
"name": "rooms_hospital_id",
"column": "hospital_id",
"reference": "hospitals(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "rooms_hospital_idx",
"columns": ["hospital_id"]
}
]
},
{
"name": "hospital_doctors",
"columns": [
{
"name": "hospital_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "doctor_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
}
],
"constraints": [
{
"primary_key": true,
"columns": ["hospital_id", "doctor_id"]
},
{
"foreign_key": true,
"name": "hospital_doctors_hospital_id",
"column": "hospital_id",
"reference": "hospitals(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"foreign_key": true,
"name": "hospital_doctors_doctor_id",
"column": "doctor_id",
"reference": "doctors(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "hospital_doctors_hospital_idx",
"columns": ["hospital_id"]
},
{
"index": true,
"name": "hospital_doctors_doctor_idx",
"columns": ["doctor_id"]
}
]
},
{
"name": "appointments",
"columns": [
{
"name": "id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL",
"AUTO_INCREMENT"
],
"primary_key": true
},
{
"name": "patient_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "doctor_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "service_id",
"type": "INT UNSIGNED",
"constraints": [
"NOT NULL"
]
},
{
"name": "hospital_id",
"type": "INT UNSIGNED",
"constraints": [
"DEFAULT NULL"
]
},
{
"name": "room_id",
"type": "INT UNSIGNED",
"constraints": [
"DEFAULT NULL"
]
},
{
"name": "date",
"type": "DATE",
"constraints": [
"NOT NULL"
]
},
{
"name": "time",
"type": "TIME",
"constraints": [
"NOT NULL"
]
},
{
"name": "status",
"type": "ENUM('Confirmed', 'Completed', 'Absent', 'Cancelled by Patient', 'Cancelled by Doctor')",
"constraints": [
"NOT NULL"
]
}
],
"constraints": [
{
"foreign_key": true,
"name": "appointments_patient_id",
"column": "patient_id",
"reference": "patients(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"foreign_key": true,
"name": "appointments_doctor_id",
"column": "doctor_id",
"reference": "doctors(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"foreign_key": true,
"name": "appointments_service_id",
"column": "service_id",
"reference": "services(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"foreign_key": true,
"name": "appointments_hospital_id",
"column": "hospital_id",
"reference": "hospitals(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"foreign_key": true,
"name": "appointments_room_id",
"column": "room_id",
"reference": "rooms(id)",
"on_delete": "RESTRICT",
"on_update": "CASCADE"
},
{
"index": true,
"name": "appointments_patient_idx",
"columns": ["patient_id"]
},
{
"index": true,
"name": "appointments_doctor_idx",
"columns": ["doctor_id"]
},
{
"index": true,
"name": "appointments_service_idx",
"columns": ["service_id"]
},
{
"index": true,
"name": "appointments_hospital_idx",
"columns": ["hospital_id"]
},
{
"index": true,
"name": "appointments_room_idx",
"columns": ["room_id"]
}
]
}
]
}

View File

@@ -1,4 +1,6 @@
import mysql from 'mysql2';
import dbconf from '../database.json';
import { log } from './logManager';
const connection = mysql.createConnection({
host: process.env.DATABASE_HOST,
@@ -23,4 +25,58 @@ function createPool(host, user, password, db) {
return newPool;
}
export { connection, pool, createPool };
function databaseSelfTest() {
log('Database self-test');
dbconf.tables.forEach(table => {
let query = `CREATE TABLE IF NOT EXISTS ${table.name} (`;
table.columns.forEach((column, index) => {
query += `${column.name} ${column.type}`;
if (column.primary_key) query += ' PRIMARY KEY';
if (column.constraints && column.constraints.length > 0) query += ` ${column.constraints.join(' ')}`;
if (column.index) query += `, INDEX ${column.name}_idx (${column.name})`;
if (index < table.columns.length - 1) query += ', ';
});
if (table.constraints && table.constraints.length > 0) {
table.constraints.forEach(constraint => {
setTimeout(() => { // do not remove or it breaks /sarcasm
if (constraint.primary_key) query += `, PRIMARY KEY (${constraint.columns.join(', ')})`;
if (constraint.foreign_key) query += `, CONSTRAINT ${constraint.name} FOREIGN KEY (${constraint.column}) REFERENCES ${constraint.reference} ON DELETE ${constraint.on_delete} ON UPDATE ${constraint.on_update}`;
if (constraint.index) query += `, INDEX ${constraint.name} (${constraint.columns.join(', ')})`;
}, 500);
});
}
query += ') ENGINE=InnoDB;';
pool.query(query)
.then(() => log(`Table ${table.name} validated`))
.catch(err => console.log(`Error creating table ${table.name}: ${err}`));
if (table.data) {
pool.query(`SELECT * FROM ${table.name}`)
.then(([rows]) => {
if (rows.length === 0) {
table.data.forEach(row => {
let insertQuery = `INSERT INTO ${table.name} (`;
let values = 'VALUES (';
Object.keys(row).forEach((key, index) => {
insertQuery += key;
values += `'${row[key]}'`;
if (index < Object.keys(row).length - 1) {
insertQuery += ', ';
values += ', ';
}
});
insertQuery += ') ' + values + ');';
pool.query(insertQuery)
.then(() => log(`Row inserted in table ${table.name}`))
.catch(err => log(`Error inserting row in table ${table.name}: ${err}`));
});
}
})
.catch(err => log(`Error checking if table ${table.name} is empty: ${err}`));
}
});
}
databaseSelfTest();
export { connection, pool, createPool, databaseSelfTest };

View File

@@ -47,7 +47,6 @@ export async function verifyPermissions(userId, permissionName, permissionType)
}
export async function checkIfUserEmailIsVerified(userId) {
return true;
try {
const [user] = await pool.execute('SELECT email_verified FROM users WHERE id = ? LIMIT 1', [userId]);
if (user.length === 0) return false;

View File

@@ -29,7 +29,6 @@
"node-cron": "^3.0.3",
"nodemailer": "^6.9.10",
"path": "^0.12.7",
"pino": "^8.16.2",
"pino-pretty": "^11.0.0"
"pino": "^8.16.2"
}
}

View File

@@ -244,6 +244,7 @@ router.patch('/password/verify', antiVerificationSpam, async (req, res) => {
router.get('/:userId', verifyToken, checkBanned, async (req, res) => {
try {
if (req.params.userId == '@me') req.params.userId = req.userId;
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 1)) return await respondWithStatus(res, 403, 'Missing permission');
const [rows] = await pool.execute('SELECT id, first_name, last_name, username, email, phone FROM users WHERE id = ? LIMIT 1', [req.params.userId]);
if (rows.length === 0) return await respondWithStatus(res, 404, 'User not found');
@@ -260,6 +261,7 @@ router.get('/:userId', verifyToken, checkBanned, async (req, res) => {
router.patch('/:userId', verifyToken, checkBanned, async (req, res) => {
try {
if (req.params.userId == '@me') req.params.userId = req.userId;
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 2)) return await respondWithStatus(res, 403, 'Missing permission');
const { type } = req.body;
let { value } = req.body;
@@ -285,6 +287,7 @@ router.patch('/:userId', verifyToken, checkBanned, async (req, res) => {
router.put('/:userId', verifyToken, checkBanned, async (req, res) => {
try {
if (req.params.userId == '@me') req.params.userId = req.userId;
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 2)) return await respondWithStatus(res, 403, 'Missing permission');
const { first_name, last_name, username, password = null, email, phone = null } = req.body;
if ([first_name, last_name, username, email].every(Boolean)) {
@@ -313,6 +316,7 @@ router.put('/:userId', verifyToken, checkBanned, async (req, res) => {
router.delete('/:userId', verifyToken, checkBanned, async (req, res) => {
try {
if (req.params.userId == '@me') req.params.userId = req.userId;
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 4)) return await respondWithStatus(res, 403, 'Missing permission');
if (!userExists(req.params.userId)) return await respondWithStatus(res, 404, 'User not found');
const [result] = await pool.execute('DELETE FROM users WHERE id = ?', [ req.params.userId ]);
@@ -327,9 +331,7 @@ router.delete('/:userId', verifyToken, checkBanned, async (req, res) => {
router.get('/:userId/roles', verifyToken, checkBanned, async (req, res) => {
try {
if (req.params.userId == '@me') {
req.params.userId = req.userId;
}
if (req.params.userId == '@me') req.params.userId = req.userId;
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 1)) return await respondWithStatus(res, 403, 'Missing permission');
const [rows] = await pool.execute('SELECT r.* FROM users u INNER JOIN user_roles ur ON u.id = ur.user_id INNER JOIN roles r ON ur.role_id = r.id WHERE u.id = ?', [ req.params.userId ]);
if (rows.length === 0) return await respondWithStatus(res, 404, 'No roles found');
@@ -345,6 +347,7 @@ router.post('/:userId/roles', verifyToken, checkBanned, checkPermissions('user',
const { roleId } = req.body;
if (roleId) {
try {
if (req.params.userId == '@me') req.params.userId = req.userId;
const [rows] = await pool.execute('SELECT * FROM roles WHERE id = ? LIMIT 1', [ roleId ]);
if (rows.length === 0) return await respondWithStatus(res, 404, 'Role not found');
const [result] = await pool.execute('INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)', [ req.params.userId, roleId ]);
@@ -363,6 +366,7 @@ router.post('/:userId/roles', verifyToken, checkBanned, checkPermissions('user',
router.delete('/:userId/roles/:roleId', verifyToken, checkBanned, checkPermissions('user', 4), async (req, res) => {
try {
if (req.params.userId == '@me') req.params.userId = req.userId;
const [result] = await pool.execute('DELETE FROM user_roles WHERE user_id = ? AND role_id = ?', [ req.params.userId, req.params.roleId ]);
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error removing role');
return await respondWithStatus(res, 200, 'Role removed successfully');