Merge branch 'main' of https://git.justw.tf/Lightemerald/hsp-gdh
This commit is contained in:
20
.env.sample
20
.env.sample
@@ -1,10 +1,22 @@
|
|||||||
PORT=3000
|
PORT=80
|
||||||
DOMAIN="http://localhost:3000"
|
DOMAIN="http://localhost:3000"
|
||||||
DATABASE_HOST="127.0.0.1"
|
DATABASE_HOST="127.0.0.1"
|
||||||
DATABASE_NAME=hsp_gdh
|
DATABASE_NAME=hsp_gdh
|
||||||
DATABASE_USER=hsp_gdh
|
DATABASE_USER=hsp_gdh
|
||||||
DATABASE_PASSWORD=""
|
DATABASE_PASSWORD=""
|
||||||
JWT_SECRET=""
|
JWT_SECRET=""
|
||||||
SMTP=
|
MAIL_SERVER=
|
||||||
MAIL=
|
MAIL_ADDRESS=
|
||||||
MAIL_PASS=
|
MAIL_PASSWORD=
|
||||||
|
BEHIND_PROXY=false
|
||||||
|
DISABLE_EMAIL_VERIFICATION=true
|
||||||
|
DISABLE_2_FACTOR_AUTHENTICATION=true
|
||||||
|
DISABLE_ANTI_DOS=true
|
||||||
|
DISABLE_ANTI_SPAM=true
|
||||||
|
RATE_LIMIT_REQUESTS=100
|
||||||
|
RATE_LIMIT_LOGIN_ATTEMPTS=5
|
||||||
|
RATE_LIMIT_VERIFICATION_REQUESTS=5
|
||||||
|
ENABLE_HTTPS=false
|
||||||
|
HTTPS_PORT=443
|
||||||
|
HTTPS_KEY=domain.key
|
||||||
|
HTTPS_CERT=domain.crt
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -137,3 +137,6 @@ logs/
|
|||||||
|
|
||||||
# token
|
# token
|
||||||
tokens/
|
tokens/
|
||||||
|
|
||||||
|
# certs
|
||||||
|
certs/
|
||||||
38
README.md
38
README.md
@@ -0,0 +1,38 @@
|
|||||||
|
# HSP-GDH (API)
|
||||||
|
|
||||||
|
Short description of your project.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [HSP-GDH (API)](#hsp-gdh-api)
|
||||||
|
- [Table of Contents](#table-of-contents)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Usage](#usage)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Install [bun](https://bun.sh/).
|
||||||
|
2. Clone the repository.
|
||||||
|
3. Install the dependencies by running the following command:
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
4. Copy .env.sample as .env
|
||||||
|
```bash
|
||||||
|
cp .env.sample .env
|
||||||
|
```
|
||||||
|
5. (Optional) Setup SSL/TLS certs
|
||||||
|
1. Create Certificate
|
||||||
|
```bash
|
||||||
|
openssl req -newkey rsa:2048 -nodes -keyout certs/domain.key -x509 -days 365 -out certs/domain.crt -subj "/C=FR/L=Paris/O=HSP-GDH/CN=localhost"
|
||||||
|
```
|
||||||
|
2. Enable HTTPS in .env
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Start the application by running the following command:
|
||||||
|
```bash
|
||||||
|
bun index.js
|
||||||
|
```
|
||||||
|
2. Open your browser and navigate to `http(s)://localhost:{port}`.
|
||||||
980
database.json
Normal file
980
database.json
Normal 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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ CREATE TABLE user_roles (
|
|||||||
CREATE TABLE verification_codes (
|
CREATE TABLE verification_codes (
|
||||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
user_id INT UNSIGNED NOT NULL,
|
user_id INT UNSIGNED NOT NULL,
|
||||||
verification_code VARCHAR(255),
|
verification_code VARCHAR(255) NOT NULL,
|
||||||
type VARCHAR(32) NOT NULL,
|
type VARCHAR(32) NOT NULL,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
|
|||||||
73
index.js
73
index.js
@@ -1,17 +1,22 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
|
import https from 'https';
|
||||||
|
import helmet from 'helmet';
|
||||||
import logger from 'morgan';
|
import logger from 'morgan';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
|
|
||||||
import { log } from './modules/logManager';
|
import { fileExist } from './modules/fileManager';
|
||||||
import { speedLimiter, checkSystemLoad } from './modules/requestHandler';
|
import { log, error } from './modules/logManager';
|
||||||
|
import { speedLimiter, requestLimiter, checkSystemLoad, respondWithStatus } from './modules/requestHandler';
|
||||||
|
|
||||||
import testRouter from './routes/test';
|
import testRouter from './routes/test';
|
||||||
import usersRouter from './routes/users';
|
import usersRouter from './routes/users';
|
||||||
|
import rolesRouter from './routes/roles';
|
||||||
import doctorsRouter from './routes/doctors';
|
import doctorsRouter from './routes/doctors';
|
||||||
import patientsRouter from './routes/patients';
|
import patientsRouter from './routes/patients';
|
||||||
|
import servicesRouter from './routes/services';
|
||||||
import companiesRouter from './routes/companies';
|
import companiesRouter from './routes/companies';
|
||||||
import hospitalsRouter from './routes/hospitals';
|
import hospitalsRouter from './routes/hospitals';
|
||||||
|
|
||||||
@@ -20,33 +25,73 @@ const logsDir = path.join(import.meta.dir, 'logs');
|
|||||||
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir);
|
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.set('trust proxy', 1);
|
if (process.env.BEHIND_PROXY == 'true') app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
|
||||||
|
app.use(cors({ origin: '*' }));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(speedLimiter);
|
|
||||||
|
// Security
|
||||||
|
app.use(helmet());
|
||||||
|
app.disable('x-powered-by');
|
||||||
|
|
||||||
|
// Rate limiting and anti DoS
|
||||||
|
if (!process.env.DISABLE_ANTI_SPAM == 'true') app.use(speedLimiter);
|
||||||
|
if (!process.env.DISABLE_ANTI_DOS == 'true') app.use(requestLimiter);
|
||||||
app.use(checkSystemLoad);
|
app.use(checkSystemLoad);
|
||||||
|
|
||||||
|
// Logging
|
||||||
app.use(logger('dev'));
|
app.use(logger('dev'));
|
||||||
app.use(logger('combined', { stream: fs.createWriteStream(path.join(import.meta.dir, 'logs/access.log'), { flags: 'a' }) }));
|
app.use(logger('combined', { stream: fs.createWriteStream(path.join(import.meta.dir, 'logs/access.log'), { flags: 'a' }) }));
|
||||||
app.use(cors({
|
|
||||||
origin: '*',
|
|
||||||
}));
|
|
||||||
|
|
||||||
app.use(express.static('public'));
|
app.use(express.static('public'));
|
||||||
|
|
||||||
// routes
|
// routes
|
||||||
app.use('/api/test', testRouter);
|
app.use('/api/test', testRouter);
|
||||||
app.use('/api/users', usersRouter);
|
app.use('/api/users', usersRouter);
|
||||||
|
app.use('/api/roles', rolesRouter);
|
||||||
app.use('/api/doctors', doctorsRouter);
|
app.use('/api/doctors', doctorsRouter);
|
||||||
app.use('/api/patients', patientsRouter);
|
app.use('/api/patients', patientsRouter);
|
||||||
|
app.use('/api/services', servicesRouter);
|
||||||
app.use('/api/companies', companiesRouter);
|
app.use('/api/companies', companiesRouter);
|
||||||
app.use('/api/hospitals', hospitalsRouter);
|
app.use('/api/hospitals', hospitalsRouter);
|
||||||
|
|
||||||
// run the API
|
|
||||||
app.listen(process.env.PORT, async () => {
|
app.use((req, res) => {
|
||||||
log(`running at port ${process.env.PORT}`);
|
return respondWithStatus(res, 404, 'Nothing\'s here!');
|
||||||
});
|
});
|
||||||
|
|
||||||
// test
|
app.use((err, req, res) => {
|
||||||
// import { post } from './modules/fetcher';
|
error(err.stack);
|
||||||
// post('http://127.0.0.1:1109/users/login', { 'usernameOrEmail':'foo', 'password':'bar' }).then(res => console.log(res));
|
return respondWithStatus(res, 500, 'Internal server error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// run the API server
|
||||||
|
if (process.env.ENABLE_HTTPS == 'true') {
|
||||||
|
const certsDir = path.join(import.meta.dir, 'certs');
|
||||||
|
if (!fs.existsSync(certsDir)) fs.mkdirSync(certsDir);
|
||||||
|
|
||||||
|
if (!fileExist(path.join(certsDir, process.env.HTTPS_KEY)) || !fileExist(path.join(certsDir, process.env.HTTPS_CERT))) {
|
||||||
|
error('Missing HTTPS key or certificate');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const options = {
|
||||||
|
key: fs.readFileSync(path.join(certsDir, process.env.HTTPS_KEY)),
|
||||||
|
cert: fs.readFileSync(path.join(certsDir, process.env.HTTPS_CERT)),
|
||||||
|
maxVersion: 'TLSv1.3',
|
||||||
|
minVersion: 'TLSv1.2',
|
||||||
|
ciphers: 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256',
|
||||||
|
ecdhCurve: 'P-521:P-384',
|
||||||
|
sigalgs: 'ecdsa_secp384r1_sha384',
|
||||||
|
honorCipherOrder: true,
|
||||||
|
};
|
||||||
|
https.createServer(options, app).listen(process.env.HTTPS_PORT, async () => {
|
||||||
|
log('running in HTTPS mode');
|
||||||
|
log(`running at port ${process.env.HTTPS_PORT}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app.listen(process.env.PORT, async () => {
|
||||||
|
log('running in HTTP mode');
|
||||||
|
log(`running at port ${process.env.PORT}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import mysql from 'mysql2';
|
import mysql from 'mysql2';
|
||||||
|
import dbconf from '../database.json';
|
||||||
|
import { log } from './logManager';
|
||||||
|
|
||||||
const connection = mysql.createConnection({
|
const connection = mysql.createConnection({
|
||||||
host: process.env.DATABASE_HOST,
|
host: process.env.DATABASE_HOST,
|
||||||
@@ -23,4 +25,58 @@ function createPool(host, user, password, db) {
|
|||||||
return newPool;
|
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 };
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { random } from './random';
|
|||||||
import { log } from './logManager';
|
import { log } from './logManager';
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
host: process.env.SMTP,
|
host: process.env.MAIL_SERVER,
|
||||||
port: 465,
|
port: 465,
|
||||||
secure: true,
|
secure: true,
|
||||||
auth: {
|
auth: {
|
||||||
user: `${process.env.MAIL}`,
|
user: `${process.env.MAIL_ADDRESS}`,
|
||||||
pass: `${process.env.PASS}`,
|
pass: `${process.env.PASSWORD}`,
|
||||||
},
|
},
|
||||||
tls: { rejectUnauthorized: false },
|
tls: { rejectUnauthorized: false },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export async function verifyPermissions(userId, permissionName, permissionType)
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function checkIfUserEmailIsVerified(userId) {
|
export async function checkIfUserEmailIsVerified(userId) {
|
||||||
|
if(process.env.DISABLE_EMAIL_VERIFICATION) return true;
|
||||||
try {
|
try {
|
||||||
const [user] = await pool.execute('SELECT email_verified FROM users WHERE id = ? LIMIT 1', [userId]);
|
const [user] = await pool.execute('SELECT email_verified FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||||
if (user.length === 0) return false;
|
if (user.length === 0) return false;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { log } from './logManager';
|
|||||||
|
|
||||||
const requestLimiter = rateLimit({
|
const requestLimiter = rateLimit({
|
||||||
windowMs: 60 * 1000,
|
windowMs: 60 * 1000,
|
||||||
max: 5,
|
max: process.env.RATE_LIMIT_REQUESTS || 100,
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
message: 'Too many requests from this IP, please try again later',
|
message: 'Too many requests from this IP, please try again later',
|
||||||
@@ -18,6 +18,22 @@ const speedLimiter = slowDown({
|
|||||||
delayMs: (hits) => hits * 100,
|
delayMs: (hits) => hits * 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const antiBruteForce = rateLimit({
|
||||||
|
windowMs: 60 * 60 * 1000,
|
||||||
|
max: process.env.RATE_LIMIT_LOGIN_ATTEMPTS || 5,
|
||||||
|
standardHeaders: true,
|
||||||
|
legacyHeaders: false,
|
||||||
|
message: 'Too many login attempts, please try again later',
|
||||||
|
});
|
||||||
|
|
||||||
|
const antiVerificationSpam = rateLimit({
|
||||||
|
windowMs: 60 * 1000,
|
||||||
|
max: process.env.RATE_LIMIT_VERIFICATION_REQUESTS || 5,
|
||||||
|
standardHeaders: true,
|
||||||
|
legacyHeaders: false,
|
||||||
|
message: 'Too many verification requests, please try again later',
|
||||||
|
});
|
||||||
|
|
||||||
function checkSystemLoad(req, res, next) {
|
function checkSystemLoad(req, res, next) {
|
||||||
const load = os.loadavg()[0];
|
const load = os.loadavg()[0];
|
||||||
const cores = os.cpus().length;
|
const cores = os.cpus().length;
|
||||||
@@ -49,6 +65,8 @@ function respondWithStatusJSON(res, statusCode, JSON) {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
requestLimiter,
|
requestLimiter,
|
||||||
|
antiBruteForce,
|
||||||
|
antiVerificationSpam,
|
||||||
speedLimiter,
|
speedLimiter,
|
||||||
checkSystemLoad,
|
checkSystemLoad,
|
||||||
respondWithStatus,
|
respondWithStatus,
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.19.2",
|
||||||
"express-rate-limit": "^7.1.4",
|
"express-rate-limit": "^7.1.4",
|
||||||
"express-slow-down": "^2.0.1",
|
"express-slow-down": "^2.0.1",
|
||||||
|
"helmet": "^7.1.0",
|
||||||
|
"https": "^1.0.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"level": "^8.0.1",
|
"level": "^8.0.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ router.post('/register', verifyToken, checkEmailVerified, checkBanned, async (re
|
|||||||
if ([ email, phone, speciality, status ].every(Boolean)) {
|
if ([ email, phone, speciality, status ].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
const [result] = await pool.execute(
|
const [result] = await pool.execute(
|
||||||
'INSERT INTO doctors (user_id, email, phone, speciality, status) VALUES (?, ?, ?, ?, ?, ?)',
|
'INSERT INTO doctors (user_id, email, phone, speciality, status) VALUES (?, ?, ?, ?, ?)',
|
||||||
[req.userId, email, phone, speciality, status],
|
[req.userId, email, phone, speciality, status],
|
||||||
);
|
);
|
||||||
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error storing doctor');
|
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error storing doctor');
|
||||||
@@ -83,10 +83,13 @@ router.post('/:doctorId/validate', verifyToken, checkBanned, checkPermissions('d
|
|||||||
const { doctor_id } = req.body;
|
const { doctor_id } = req.body;
|
||||||
if (doctor_id) {
|
if (doctor_id) {
|
||||||
try {
|
try {
|
||||||
const [result] = await pool.execute('UPDATE doctors SET is_verified = 1 WHERE id = ?', [doctor_id]);
|
const [result] = await pool.execute('SELECT * FROM doctors WHERE id = ?',[doctor_id]);
|
||||||
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error validating doctor');
|
if(result.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
const [result2] = await pool.execute('INSERT INTO user_roles (user_id, role_id) VALUES (?, (SELECT id FROM roles WHERE name = ? LIMIT 1))', [req.userId, 'Doctor']);
|
if(result[0].is_verified) return await respondWithStatus(res, 400, 'Doctor already verified');
|
||||||
if (result2.affectedRows === 0) return await respondWithStatus(res, 500, 'Error adding role to user');
|
const [result2] = await pool.execute('UPDATE doctors SET is_verified = 1 WHERE id = ?', [doctor_id]);
|
||||||
|
if (result2.affectedRows === 0) return await respondWithStatus(res, 500, 'Error validating doctor');
|
||||||
|
const [result3] = await pool.execute('INSERT INTO user_roles (user_id, role_id) VALUES (?, (SELECT id FROM roles WHERE name = ? LIMIT 1))', [result[0].user_id, 'Doctor']);
|
||||||
|
if (result3.affectedRows === 0) return await respondWithStatus(res, 500, 'Error adding role to user');
|
||||||
return await respondWithStatus(res, 200, 'Doctor validated successfully');
|
return await respondWithStatus(res, 200, 'Doctor validated successfully');
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -102,6 +105,10 @@ router.post('/:doctorId/validate', verifyToken, checkBanned, checkPermissions('d
|
|||||||
router.get('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
router.get('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'doctor', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'doctor', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM doctors WHERE id = ? LIMIT 1', [req.params.doctorId]);
|
const [rows] = await pool.execute('SELECT * FROM doctors WHERE id = ? LIMIT 1', [req.params.doctorId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
@@ -117,6 +124,10 @@ router.patch('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { type, value } = req.body;
|
const { type, value } = req.body;
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'doctor', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'doctor', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM doctors WHERE id = ? LIMIT 1', [req.params.doctorId]);
|
const [rows] = await pool.execute('SELECT * FROM doctors WHERE id = ? LIMIT 1', [req.params.doctorId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
@@ -143,6 +154,10 @@ router.put('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
if ([ user_id, email, phone, speciality, status, is_verified ].every(Boolean)) {
|
if ([ user_id, email, phone, speciality, status, is_verified ].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'doctor', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'doctor', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM doctors WHERE id = ? LIMIT 1', [req.params.doctorId]);
|
const [rows] = await pool.execute('SELECT * FROM doctors WHERE id = ? LIMIT 1', [req.params.doctorId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
@@ -168,6 +183,10 @@ router.put('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
router.delete('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
router.delete('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'doctor', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'doctor', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM doctors WHERE id = ? LIMIT 1', [req.params.doctorId]);
|
const [rows] = await pool.execute('SELECT * FROM doctors WHERE id = ? LIMIT 1', [req.params.doctorId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
@@ -186,6 +205,10 @@ router.delete('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
router.get('/:doctorId/appointments', verifyToken, checkBanned, async (req, res) => {
|
router.get('/:doctorId/appointments', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute(
|
const [rows] = await pool.execute(
|
||||||
'SELECT a.*, u.first_name, u.last_name, p.gender, p.date_of_birth, s.service_name, a.service_id FROM appointments AS a JOIN patients AS p ON a.patient_id = p.id JOIN users AS u ON p.user_id = u.id JOIN services AS s ON a.service_id = s.id WHERE a.doctor_id = ?',
|
'SELECT a.*, u.first_name, u.last_name, p.gender, p.date_of_birth, s.service_name, a.service_id FROM appointments AS a JOIN patients AS p ON a.patient_id = p.id JOIN users AS u ON p.user_id = u.id JOIN services AS s ON a.service_id = s.id WHERE a.doctor_id = ?',
|
||||||
@@ -204,6 +227,10 @@ router.post('/:doctorId/appointments', verifyToken, checkBanned, async (req, res
|
|||||||
const { patient_id, service_id, hospital_id, room_id = null, date, time, status } = req.body;
|
const { patient_id, service_id, hospital_id, room_id = null, date, time, status } = req.body;
|
||||||
if (!['Confirmed', 'Completed', 'Absent', 'Cancelled by Patient', 'Cancelled by Doctor'].includes(status)) return await respondWithStatus(res, 400, 'Invalid status');
|
if (!['Confirmed', 'Completed', 'Absent', 'Cancelled by Patient', 'Cancelled by Doctor'].includes(status)) return await respondWithStatus(res, 400, 'Invalid status');
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
if ([patient_id, service_id, hospital_id, date, time, status].every(Boolean)) {
|
if ([patient_id, service_id, hospital_id, date, time, status].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
@@ -227,6 +254,10 @@ router.post('/:doctorId/appointments', verifyToken, checkBanned, async (req, res
|
|||||||
router.get('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, async (req, res) => {
|
router.get('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute(
|
const [rows] = await pool.execute(
|
||||||
'SELECT a.*, u.first_name, u.last_name, p.gender, p.date_of_birth, s.service_name, a.service_id FROM appointments AS a JOIN patients AS p ON a.patient_id = p.id JOIN users AS u ON p.user_id = u.id WHERE a.id = ? AND a.doctor_id = ? LIMIT 1',
|
'SELECT a.*, u.first_name, u.last_name, p.gender, p.date_of_birth, s.service_name, a.service_id FROM appointments AS a JOIN patients AS p ON a.patient_id = p.id JOIN users AS u ON p.user_id = u.id WHERE a.id = ? AND a.doctor_id = ? LIMIT 1',
|
||||||
@@ -245,6 +276,10 @@ router.patch('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned,
|
|||||||
try {
|
try {
|
||||||
const { type, value } = req.body;
|
const { type, value } = req.body;
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM appointments WHERE id = ? LIMIT 1', [req.params.appointmentId]);
|
const [rows] = await pool.execute('SELECT * FROM appointments WHERE id = ? LIMIT 1', [req.params.appointmentId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Appointment not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Appointment not found');
|
||||||
@@ -271,6 +306,10 @@ router.put('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, a
|
|||||||
const { patient_id, service_id, hospital_id, room_id, date, time, status } = req.body;
|
const { patient_id, service_id, hospital_id, room_id, date, time, status } = req.body;
|
||||||
if (!['Confirmed', 'Completed', 'Absent', 'Cancelled by Patient', 'Cancelled by Doctor'].includes(status)) return await respondWithStatus(res, 400, 'Invalid status');
|
if (!['Confirmed', 'Completed', 'Absent', 'Cancelled by Patient', 'Cancelled by Doctor'].includes(status)) return await respondWithStatus(res, 400, 'Invalid status');
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
if ([patient_id, service_id, hospital_id, room_id, date, time, status].every(Boolean)) {
|
if ([patient_id, service_id, hospital_id, room_id, date, time, status].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
@@ -297,6 +336,10 @@ router.put('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, a
|
|||||||
router.delete('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, async (req, res) => {
|
router.delete('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'appointment', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM appointments WHERE id = ? LIMIT 1', [req.params.appointmentId]);
|
const [rows] = await pool.execute('SELECT * FROM appointments WHERE id = ? LIMIT 1', [req.params.appointmentId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Appointment not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Appointment not found');
|
||||||
@@ -315,6 +358,10 @@ router.delete('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned
|
|||||||
router.get('/:doctorId/services', verifyToken, checkBanned, async (req, res) => {
|
router.get('/:doctorId/services', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT s.* FROM services s INNER JOIN service_doctors sd ON s.id = sd.service_id WHERE sd.doctor_id = ?', [req.params.doctorId]);
|
const [rows] = await pool.execute('SELECT s.* FROM services s INNER JOIN service_doctors sd ON s.id = sd.service_id WHERE sd.doctor_id = ?', [req.params.doctorId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Services not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Services not found');
|
||||||
@@ -329,6 +376,10 @@ router.get('/:doctorId/services', verifyToken, checkBanned, async (req, res) =>
|
|||||||
router.post('/:doctorId/services', verifyToken, checkBanned, async (req, res) => {
|
router.post('/:doctorId/services', verifyToken, checkBanned, async (req, res) => {
|
||||||
const { service_id } = req.body;
|
const { service_id } = req.body;
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
if (service_id) {
|
if (service_id) {
|
||||||
try {
|
try {
|
||||||
@@ -349,6 +400,10 @@ router.post('/:doctorId/services', verifyToken, checkBanned, async (req, res) =>
|
|||||||
router.patch('/:doctorId/services/:serviceId', verifyToken, checkBanned, async (req, res) => {
|
router.patch('/:doctorId/services/:serviceId', verifyToken, checkBanned, async (req, res) => {
|
||||||
const { type, value } = req.body;
|
const { type, value } = req.body;
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
if (type === 'service_id') {
|
if (type === 'service_id') {
|
||||||
try {
|
try {
|
||||||
@@ -368,6 +423,10 @@ router.patch('/:doctorId/services/:serviceId', verifyToken, checkBanned, async (
|
|||||||
|
|
||||||
router.delete('/:doctorId/services/:serviceId', verifyToken, checkBanned, async (req, res) => {
|
router.delete('/:doctorId/services/:serviceId', verifyToken, checkBanned, async (req, res) => {
|
||||||
const doctorId = await getDoctorId (req.userId);
|
const doctorId = await getDoctorId (req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
try {
|
try {
|
||||||
const [result] = await pool.execute('DELETE FROM service_doctors WHERE doctor_id = ? AND service_id = ?', [req.params.doctorId, req.params.serviceId]);
|
const [result] = await pool.execute('DELETE FROM service_doctors WHERE doctor_id = ? AND service_id = ?', [req.params.doctorId, req.params.serviceId]);
|
||||||
@@ -380,6 +439,25 @@ router.delete('/:doctorId/services/:serviceId', verifyToken, checkBanned, async
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/:doctorId/hospitals', verifyToken, checkBanned, async (req,res) => {
|
||||||
|
const doctorId = await getDoctorId(req.userId);
|
||||||
|
if (req.params.doctorId == '@me') {
|
||||||
|
if (!doctorId) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||||
|
req.params.doctorId = doctorId;
|
||||||
|
}
|
||||||
|
if (doctorId != req.params.doctorId && !verifyPermissions(req.userId, 'service', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
|
try {
|
||||||
|
//'SELECT s.* FROM services s INNER JOIN service_doctors sd ON s.id = sd.service_id WHERE sd.doctor_id = ?', [req.params.doctorId]
|
||||||
|
const [rows] = await pool.execute('SELECT h.* FROM hospitals h INNER JOIN hospital_doctors hd ON h.id = hd.hospital_id WHERE hd.doctor_id = ?', [req.params.doctorId]);
|
||||||
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Hospitals not found');
|
||||||
|
return await respondWithStatusJSON(res, 200, rows);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ router.put('/:hospitalId', verifyToken, checkBanned, checkPermissions('hospital'
|
|||||||
return await respondWithStatus(res, 404, 'Hospital not found');
|
return await respondWithStatus(res, 404, 'Hospital not found');
|
||||||
}
|
}
|
||||||
const [result] = await pool.execute(
|
const [result] = await pool.execute(
|
||||||
'UPDATE hospitals SET company_id = ?, name = ?, country = ?, region = ?, city = ?, address = ? WHERE id = ?',
|
'UPDATE hospitals SET company_id = ?, name = ?, code = ?, country = ?, region = ?, city = ?, address = ? WHERE id = ?',
|
||||||
[company_id, name, code, country, region, city, address, id],
|
[company_id, name, code, country, region, city, address, id],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ router.post('/register', verifyToken, checkEmailVerified, checkBanned, async (re
|
|||||||
router.get('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
router.get('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const patientId = await getPatientId(req.userId);
|
const patientId = await getPatientId(req.userId);
|
||||||
|
if (req.params.patientId == '@me') {
|
||||||
|
if (!patientId) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
|
req.params.patientId = patientId;
|
||||||
|
}
|
||||||
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'patient', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'patient', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM patients WHERE id = ? LIMIT 1', [req.params.patientId]);
|
const [rows] = await pool.execute('SELECT * FROM patients WHERE id = ? LIMIT 1', [req.params.patientId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Patient not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
@@ -98,6 +102,10 @@ router.patch('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { type, value } = req.body;
|
const { type, value } = req.body;
|
||||||
const patientId = await getPatientId(req.userId);
|
const patientId = await getPatientId(req.userId);
|
||||||
|
if (req.params.patientId == '@me') {
|
||||||
|
if (!patientId) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
|
req.params.patientId = patientId;
|
||||||
|
}
|
||||||
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'patient', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'patient', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM patients WHERE id = ? LIMIT 1', [req.params.patientId]);
|
const [rows] = await pool.execute('SELECT * FROM patients WHERE id = ? LIMIT 1', [req.params.patientId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Patient not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
@@ -124,6 +132,10 @@ router.put('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
if ([ user_id, date_of_birth, gender, address, social_security_number, insurance_number ].every(Boolean)) {
|
if ([ user_id, date_of_birth, gender, address, social_security_number, insurance_number ].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
const patientId = await getPatientId(req.userId);
|
const patientId = await getPatientId(req.userId);
|
||||||
|
if (req.params.patientId == '@me') {
|
||||||
|
if (!patientId) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
|
req.params.patientId = patientId;
|
||||||
|
}
|
||||||
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'patient', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'patient', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM patients WHERE id = ? LIMIT 1', [req.params.patientId]);
|
const [rows] = await pool.execute('SELECT * FROM patients WHERE id = ? LIMIT 1', [req.params.patientId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Patient not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
@@ -149,6 +161,10 @@ router.put('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
router.delete('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
router.delete('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const patientId = await getPatientId(req.userId);
|
const patientId = await getPatientId(req.userId);
|
||||||
|
if (req.params.patientId == '@me') {
|
||||||
|
if (!patientId) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
|
req.params.patientId = patientId;
|
||||||
|
}
|
||||||
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'patient', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'patient', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute('SELECT * FROM patients WHERE id = ? LIMIT 1', [req.params.patientId]);
|
const [rows] = await pool.execute('SELECT * FROM patients WHERE id = ? LIMIT 1', [req.params.patientId]);
|
||||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'Patient not found');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
@@ -167,6 +183,10 @@ router.delete('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
router.get('/:patientId/appointments', verifyToken, checkBanned, async (req, res) => {
|
router.get('/:patientId/appointments', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const patientId = await getPatientId(req.userId);
|
const patientId = await getPatientId(req.userId);
|
||||||
|
if (req.params.patientId == '@me') {
|
||||||
|
if (!patientId) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
|
req.params.patientId = patientId;
|
||||||
|
}
|
||||||
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'appointment', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'appointment', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [rows] = await pool.execute(
|
const [rows] = await pool.execute(
|
||||||
'SELECT a.id, u.first_name, u.last_name, d.email, d.phone, h.name, h.address, a.date, a.time, a.status, s.name FROM appointments a JOIN doctors d ON a.doctor_id = d.id JOIN users u ON d.user_id = u.id JOIN hospitals h ON a.hospital_id = h.id JOIN services s ON a.service_id = s.id WHERE a.patient_id = ?',
|
'SELECT a.id, u.first_name, u.last_name, d.email, d.phone, h.name, h.address, a.date, a.time, a.status, s.name FROM appointments a JOIN doctors d ON a.doctor_id = d.id JOIN users u ON d.user_id = u.id JOIN hospitals h ON a.hospital_id = h.id JOIN services s ON a.service_id = s.id WHERE a.patient_id = ?',
|
||||||
@@ -186,6 +206,10 @@ router.post('/:patientId/appointments', verifyToken, checkBanned, async (req, re
|
|||||||
if ([ doctor_id, service_id, hospital_id, date, time ].every(Boolean)) {
|
if ([ doctor_id, service_id, hospital_id, date, time ].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
const patientId = await getPatientId(req.userId);
|
const patientId = await getPatientId(req.userId);
|
||||||
|
if (req.params.patientId == '@me') {
|
||||||
|
if (!patientId) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
|
req.params.patientId = patientId;
|
||||||
|
}
|
||||||
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'appointment', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'appointment', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const [result] = await pool.execute(
|
const [result] = await pool.execute(
|
||||||
'INSERT INTO appointments (doctor_id, service_id, hospital_id, patient_id, date, time) VALUES (?, ?, ?, ?, ?, ?)',
|
'INSERT INTO appointments (doctor_id, service_id, hospital_id, patient_id, date, time) VALUES (?, ?, ?, ?, ?, ?)',
|
||||||
@@ -203,10 +227,54 @@ router.post('/:patientId/appointments', verifyToken, checkBanned, async (req, re
|
|||||||
return await respondWithStatus(res, 400, 'Missing fields');
|
return await respondWithStatus(res, 400, 'Missing fields');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /:patientId/appointments/:appointmentId
|
// GET /:patientId/appointments/:appointmentId
|
||||||
|
router.get('/:patientId/appointments/:appointmentId', verifyToken, checkBanned, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const patientId = await getPatientId(req.userId);
|
||||||
|
if (req.params.patientId == '@me') {
|
||||||
|
if (!patientId) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
|
req.params.patientId = patientId;
|
||||||
|
}
|
||||||
|
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'appointment', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
|
const [rows] = await pool.execute(
|
||||||
|
'SELECT a.id, u.first_name, u.last_name, d.email, d.phone, h.name, h.address, a.date, a.time, a.status, s.name FROM appointments a JOIN doctors d ON a.doctor_id = d.id JOIN users u ON d.user_id = u.id JOIN hospitals h ON a.hospital_id = h.id JOIN services s ON a.service_id = s.id WHERE a.id = ? AND a.patient_id = ?',
|
||||||
|
[req.params.appointmentId, req.params.patientId],
|
||||||
|
);
|
||||||
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Appointment not found');
|
||||||
|
return await respondWithStatusJSON(res, 200, rows[0]);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// PATCH /:patientId/appointments/:appointmentId
|
// PATCH /:patientId/appointments/:appointmentId
|
||||||
|
|
||||||
// PUT /:patientId/appointments/:appointmentId
|
// PUT /:patientId/appointments/:appointmentId
|
||||||
|
|
||||||
// DELETE /:patientId/appointments/:appointmentId
|
// DELETE /:patientId/appointments/:appointmentId
|
||||||
|
router.delete('/:patientId/appointments/:appointmentId', verifyToken, checkBanned, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const patientId = await getPatientId(req.userId);
|
||||||
|
if (req.params.patientId == '@me') {
|
||||||
|
if (!patientId) return await respondWithStatus(res, 404, 'Patient not found');
|
||||||
|
req.params.patientId = patientId;
|
||||||
|
}
|
||||||
|
if (patientId != req.params.patientId && !verifyPermissions(req.userId, 'appointment', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
|
const [rows] = await pool.execute('SELECT * FROM appointments WHERE id = ? AND patient_id = ? LIMIT 1', [req.params.appointmentId, req.params.patientId]);
|
||||||
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Appointment not found');
|
||||||
|
|
||||||
|
const [result] = await pool.execute('DELETE FROM appointments WHERE id = ?', [req.params.appointmentId]);
|
||||||
|
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error removing appointment');
|
||||||
|
return await respondWithStatus(res, 200, 'Appointment deleted successfully');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
|||||||
92
routes/roles.js
Normal file
92
routes/roles.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import { error } from '../modules/logManager';
|
||||||
|
import { pool } from '../modules/databaseManager';
|
||||||
|
import { verifyToken } from '../modules/tokenManager';
|
||||||
|
import { respondWithStatus, respondWithStatusJSON } from '../modules/requestHandler';
|
||||||
|
import { checkBanned, checkPermissions } from '../modules/permissionManager';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
||||||
|
// GET role list
|
||||||
|
router.get('/', verifyToken, checkBanned, checkPermissions('role', 1), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const rows = await pool.execute('SELECT * FROM roles');
|
||||||
|
if (rows[0].length === 0) return await respondWithStatus(res, 404, 'No roles found');
|
||||||
|
return await respondWithStatusJSON(res, rows[0]);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST create role
|
||||||
|
router.post('/', verifyToken, checkBanned, checkPermissions('role', 2), async (req, res) => {
|
||||||
|
const { name, user_bitfield, role_bitfield, verification_code_bitfield, ban_bitfield, patient_bitfield, doctor_bitfield, service_bitfield, company_bitfield, hospital_bitfield, room_bitfield, appointment_bitfield } = req.body;
|
||||||
|
if ([ name, user_bitfield, role_bitfield, verification_code_bitfield, ban_bitfield, patient_bitfield, doctor_bitfield, service_bitfield, company_bitfield, hospital_bitfield, room_bitfield, appointment_bitfield ].every(Boolean)) {
|
||||||
|
try {
|
||||||
|
await pool.execute(
|
||||||
|
'INSERT INTO users (name, user_bitfield, role_bitfield, verification_code_bitfield, ban_bitfield, patient_bitfield, doctor_bitfield, service_bitfield, company_bitfield, hospital_bitfield, room_bitfield, appointment_bitfield) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||||
|
[ name, user_bitfield, role_bitfield, verification_code_bitfield, ban_bitfield, patient_bitfield, doctor_bitfield, service_bitfield, company_bitfield, hospital_bitfield, room_bitfield, appointment_bitfield ],
|
||||||
|
);
|
||||||
|
return await respondWithStatus(res, 200, 'Role created successfully');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await respondWithStatus(res, 400, 'Missing fields');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET role
|
||||||
|
router.get('/:id', verifyToken, checkBanned, checkPermissions('role', 1), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const [rows] = await pool.execute('SELECT * FROM roles WHERE id = ? LIMIT 1', [ req.params.id ]);
|
||||||
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Role not found');
|
||||||
|
return await respondWithStatusJSON(res, rows[0]);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PUT update role
|
||||||
|
router.put('/:id', verifyToken, checkBanned, checkPermissions('role', 2), async (req, res) => {
|
||||||
|
const { name, user_bitfield, role_bitfield, verification_code_bitfield, ban_bitfield, patient_bitfield, doctor_bitfield, service_bitfield, company_bitfield, hospital_bitfield, room_bitfield, appointment_bitfield } = req.body;
|
||||||
|
if ([ name, user_bitfield, role_bitfield, verification_code_bitfield, ban_bitfield, patient_bitfield, doctor_bitfield, service_bitfield, company_bitfield, hospital_bitfield, room_bitfield, appointment_bitfield ].every(Boolean)) {
|
||||||
|
try {
|
||||||
|
await pool.execute(
|
||||||
|
'UPDATE roles SET name = ?, user_bitfield = ?, role_bitfield = ?, verification_code_bitfield = ?, ban_bitfield = ?, patient_bitfield = ?, doctor_bitfield = ?, service_bitfield = ?, company_bitfield = ?, hospital_bitfield = ?, room_bitfield = ?, appointment_bitfield = ? WHERE id = ?',
|
||||||
|
[ name, user_bitfield, role_bitfield, verification_code_bitfield, ban_bitfield, patient_bitfield, doctor_bitfield, service_bitfield, company_bitfield, hospital_bitfield, room_bitfield, appointment_bitfield, req.params.id ],
|
||||||
|
);
|
||||||
|
return await respondWithStatus(res, 200, 'Role updated successfully');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await respondWithStatus(res, 400, 'Missing fields');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE role
|
||||||
|
router.delete('/:id', verifyToken, checkBanned, checkPermissions('role', 4), async (req, res) => {
|
||||||
|
try {
|
||||||
|
await pool.execute('DELETE FROM roles WHERE id = ?', [ req.params.id ]);
|
||||||
|
return await respondWithStatus(res, 200, 'Role deleted successfully');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
122
routes/services.js
Normal file
122
routes/services.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import { error } from '../modules/logManager';
|
||||||
|
import { pool } from '../modules/databaseManager';
|
||||||
|
import { verifyToken } from '../modules/tokenManager';
|
||||||
|
import { checkPermissions, checkBanned } from '../modules/permissionManager';
|
||||||
|
import { respondWithStatus, respondWithStatusJSON } from '../modules/requestHandler';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get('/', verifyToken, checkBanned, checkPermissions('service', 1), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const [rows] = await pool.execute('SELECT * FROM services WHERE 1');
|
||||||
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Services not found');
|
||||||
|
return await respondWithStatusJSON(res, 200, rows);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', verifyToken, checkBanned, checkPermissions('service', 2), async (req, res) => {
|
||||||
|
const { name, description, price } = req.body;
|
||||||
|
if ([ name, description, price ].every(Boolean)) {
|
||||||
|
try {
|
||||||
|
const [result] = await pool.execute(
|
||||||
|
'INSERT INTO services (name, description, price) VALUES (?, ?, ?)',
|
||||||
|
[ name, description, price ],
|
||||||
|
);
|
||||||
|
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error storing service');
|
||||||
|
return await respondWithStatus(res, 200, 'Service created successfully');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await respondWithStatus(res, 400, 'Missing fields');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/:serviceId', verifyToken, checkBanned, checkPermissions('service', 1), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const [rows] = await pool.execute('SELECT * FROM services WHERE id = ? LIMIT 1', [req.params.serviceId]);
|
||||||
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Services not found');
|
||||||
|
return await respondWithStatusJSON(res, 200, rows[0]);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.patch('/:serviceId', verifyToken, checkBanned, checkPermissions('service', 2), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { type, value } = req.body;
|
||||||
|
const [rows] = await pool.execute('SELECT * FROM services WHERE id = ? LIMIT 1', [req.params.serviceId]);
|
||||||
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'Service not found');
|
||||||
|
|
||||||
|
const fields = rows.map(row => Object.keys(row));
|
||||||
|
if (fields[0].includes(type)) {
|
||||||
|
const [result] = await pool.execute(`UPDATE services SET ${type} = ? WHERE id = ?`, [value, req.params.serviceId]);
|
||||||
|
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error updating service');
|
||||||
|
return await respondWithStatus(res, 200, 'Service updated successfully');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await respondWithStatus(res, 400, 'Invalid type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/:serviceId', verifyToken, checkBanned, checkPermissions('service', 2), async (req, res) => {
|
||||||
|
const id = req.params.serviceId;
|
||||||
|
const { name, description, price } = req.body;
|
||||||
|
if ([ name, description, price ].every(Boolean)) {
|
||||||
|
try {
|
||||||
|
const [rows] = await pool.execute('SELECT * FROM services WHERE id = ? LIMIT 1', [id]);
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return await respondWithStatus(res, 404, 'Service not found');
|
||||||
|
}
|
||||||
|
const [result] = await pool.execute(
|
||||||
|
'UPDATE services SET name = ?, description = ?, price = ? WHERE id = ?',
|
||||||
|
[name, description, price, id],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.affectedRows === 0) {
|
||||||
|
return await respondWithStatus(res, 500, 'Error updating Service');
|
||||||
|
}
|
||||||
|
return await respondWithStatus(res, 200, 'Service updated successfully');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await respondWithStatus(res, 400, 'Missing fields');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/:serviceId', verifyToken, checkBanned, checkPermissions('service', 4), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const [rows] = await pool.execute('SELECT * FROM services WHERE id = ? LIMIT 1', [req.params.serviceId]);
|
||||||
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'service not found');
|
||||||
|
|
||||||
|
const [result] = await pool.execute('DELETE FROM services WHERE id = ?', [req.params.serviceId]);
|
||||||
|
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error removing Service');
|
||||||
|
return await respondWithStatus(res, 200, 'Service deleted successfully');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -4,12 +4,12 @@ import { pool } from '../modules/databaseManager';
|
|||||||
import { sendVerification } from '../modules/mailHandler';
|
import { sendVerification } from '../modules/mailHandler';
|
||||||
import { verifyToken, generateToken } from '../modules/tokenManager';
|
import { verifyToken, generateToken } from '../modules/tokenManager';
|
||||||
import { isEmailDomainValid, isValidEmail, isPhoneNumber } from '../modules/formatManager';
|
import { isEmailDomainValid, isValidEmail, isPhoneNumber } from '../modules/formatManager';
|
||||||
import { requestLimiter, respondWithStatus, respondWithStatusJSON } from '../modules/requestHandler';
|
import { antiBruteForce, antiVerificationSpam, respondWithStatus, respondWithStatusJSON } from '../modules/requestHandler';
|
||||||
import { checkBanned, checkPermissions, userExists, isBanned, verifyPermissions } from '../modules/permissionManager';
|
import { checkBanned, checkPermissions, userExists, isBanned, verifyPermissions } from '../modules/permissionManager';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/register', requestLimiter, async (req, res) => {
|
router.post('/register', antiBruteForce, async (req, res) => {
|
||||||
const { username, email, password, first_name, last_name, phone = null } = req.body;
|
const { username, email, password, first_name, last_name, phone = null } = req.body;
|
||||||
if ([ username, email, password, first_name, last_name ].every(Boolean)) {
|
if ([ username, email, password, first_name, last_name ].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
@@ -30,6 +30,8 @@ router.post('/register', requestLimiter, async (req, res) => {
|
|||||||
);
|
);
|
||||||
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error storing user');
|
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error storing user');
|
||||||
|
|
||||||
|
if (process.env.DISABLE_EMAIL_VERIFICATION == 'true') return await respondWithStatus(res, 200, 'Successfully registered');
|
||||||
|
|
||||||
const [rows] = await pool.execute('SELECT id FROM users WHERE email = ? LIMIT 1', [email]);
|
const [rows] = await pool.execute('SELECT id FROM users WHERE email = ? LIMIT 1', [email]);
|
||||||
const code = sendVerification(email, rows[0].id, 'email');
|
const code = sendVerification(email, rows[0].id, 'email');
|
||||||
pool.execute('INSERT INTO verification_codes (user_id, verification_code, type) VALUES (?, ?, ?)', [ rows[0].id, code, 'email' ]);
|
pool.execute('INSERT INTO verification_codes (user_id, verification_code, type) VALUES (?, ?, ?)', [ rows[0].id, code, 'email' ]);
|
||||||
@@ -49,7 +51,7 @@ router.post('/register', requestLimiter, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/login', requestLimiter, async (req, res) => {
|
router.post('/login', antiBruteForce, async (req, res) => {
|
||||||
const { usernameOrEmail, password } = req.body;
|
const { usernameOrEmail, password } = req.body;
|
||||||
if ([usernameOrEmail, password].every(Boolean)) {
|
if ([usernameOrEmail, password].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
@@ -74,6 +76,7 @@ router.post('/login', requestLimiter, async (req, res) => {
|
|||||||
email: user.email,
|
email: user.email,
|
||||||
first_name: user.first_name,
|
first_name: user.first_name,
|
||||||
last_name: user.last_name,
|
last_name: user.last_name,
|
||||||
|
verified_status: process.env.DISABLE_EMAIL_VERIFICATION == 'true' ? true : user.email_verified,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -121,7 +124,7 @@ router.post('/', verifyToken, checkBanned, checkPermissions('user', 2), async (r
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Email verification endpoints
|
// Email verification endpoints
|
||||||
router.get('/email/request', verifyToken, checkBanned, async (req, res) => {
|
router.get('/email/request', antiVerificationSpam, verifyToken, checkBanned, async (req, res) => {
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
try {
|
try {
|
||||||
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||||
@@ -138,7 +141,7 @@ router.get('/email/request', verifyToken, checkBanned, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/email/verify', verifyToken, checkBanned, async (req, res) => {
|
router.get('/email/verify', antiVerificationSpam, verifyToken, checkBanned, async (req, res) => {
|
||||||
const { code } = req.query;
|
const { code } = req.query;
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
if (code) {
|
if (code) {
|
||||||
@@ -170,7 +173,7 @@ router.get('/email/verify', verifyToken, checkBanned, async (req, res) => {
|
|||||||
// PATCH /phone/verify
|
// PATCH /phone/verify
|
||||||
|
|
||||||
// Password reset endpoints
|
// Password reset endpoints
|
||||||
router.post('/password/request', async (req, res) => {
|
router.post('/password/request', antiVerificationSpam, async (req, res) => {
|
||||||
const { usernameOrEmail } = req.body;
|
const { usernameOrEmail } = req.body;
|
||||||
if (usernameOrEmail) {
|
if (usernameOrEmail) {
|
||||||
try {
|
try {
|
||||||
@@ -201,7 +204,7 @@ router.post('/password/request', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.patch('/password/verify', async (req, res) => {
|
router.patch('/password/verify', antiVerificationSpam, async (req, res) => {
|
||||||
const { usernameOrEmail, password, code } = req.body;
|
const { usernameOrEmail, password, code } = req.body;
|
||||||
if ([usernameOrEmail, password, code].every(Boolean)) {
|
if ([usernameOrEmail, password, code].every(Boolean)) {
|
||||||
try {
|
try {
|
||||||
@@ -241,6 +244,7 @@ router.patch('/password/verify', async (req, res) => {
|
|||||||
|
|
||||||
router.get('/:userId', verifyToken, checkBanned, async (req, res) => {
|
router.get('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
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');
|
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]);
|
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');
|
if (rows.length === 0) return await respondWithStatus(res, 404, 'User not found');
|
||||||
@@ -257,6 +261,7 @@ router.get('/:userId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
|
|
||||||
router.patch('/:userId', verifyToken, checkBanned, async (req, res) => {
|
router.patch('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
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');
|
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||||
const { type } = req.body;
|
const { type } = req.body;
|
||||||
let { value } = req.body;
|
let { value } = req.body;
|
||||||
@@ -282,6 +287,7 @@ router.patch('/:userId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
|
|
||||||
router.put('/:userId', verifyToken, checkBanned, async (req, res) => {
|
router.put('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
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');
|
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;
|
const { first_name, last_name, username, password = null, email, phone = null } = req.body;
|
||||||
if ([first_name, last_name, username, email].every(Boolean)) {
|
if ([first_name, last_name, username, email].every(Boolean)) {
|
||||||
@@ -310,6 +316,7 @@ router.put('/:userId', verifyToken, checkBanned, async (req, res) => {
|
|||||||
|
|
||||||
router.delete('/:userId', verifyToken, checkBanned, async (req, res) => {
|
router.delete('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||||
try {
|
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 (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');
|
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 ]);
|
const [result] = await pool.execute('DELETE FROM users WHERE id = ?', [ req.params.userId ]);
|
||||||
@@ -322,4 +329,52 @@ 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 != 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');
|
||||||
|
return await respondWithStatusJSON(res, 200, rows);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/:userId/roles', verifyToken, checkBanned, checkPermissions('user', 2), async (req, res) => {
|
||||||
|
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 ]);
|
||||||
|
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error assigning role');
|
||||||
|
return await respondWithStatus(res, 200, 'Role assigned successfully');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await respondWithStatus(res, 400, 'Missing fields');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
error(err);
|
||||||
|
return await respondWithStatus(res, 500, 'An error has occured');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
Reference in New Issue
Block a user