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"
|
||||
DATABASE_HOST="127.0.0.1"
|
||||
DATABASE_NAME=hsp_gdh
|
||||
DATABASE_USER=hsp_gdh
|
||||
DATABASE_PASSWORD=""
|
||||
JWT_SECRET=""
|
||||
SMTP=
|
||||
MAIL=
|
||||
MAIL_PASS=
|
||||
MAIL_SERVER=
|
||||
MAIL_ADDRESS=
|
||||
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
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -136,4 +136,7 @@ logs/
|
||||
*.log
|
||||
|
||||
# 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 (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id INT UNSIGNED NOT NULL,
|
||||
verification_code VARCHAR(255),
|
||||
verification_code VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(32) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
|
||||
73
index.js
73
index.js
@@ -1,17 +1,22 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import cors from 'cors';
|
||||
import https from 'https';
|
||||
import helmet from 'helmet';
|
||||
import logger from 'morgan';
|
||||
import express from 'express';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
import { log } from './modules/logManager';
|
||||
import { speedLimiter, checkSystemLoad } from './modules/requestHandler';
|
||||
import { fileExist } from './modules/fileManager';
|
||||
import { log, error } from './modules/logManager';
|
||||
import { speedLimiter, requestLimiter, checkSystemLoad, respondWithStatus } from './modules/requestHandler';
|
||||
|
||||
import testRouter from './routes/test';
|
||||
import usersRouter from './routes/users';
|
||||
import rolesRouter from './routes/roles';
|
||||
import doctorsRouter from './routes/doctors';
|
||||
import patientsRouter from './routes/patients';
|
||||
import servicesRouter from './routes/services';
|
||||
import companiesRouter from './routes/companies';
|
||||
import hospitalsRouter from './routes/hospitals';
|
||||
|
||||
@@ -20,33 +25,73 @@ const logsDir = path.join(import.meta.dir, 'logs');
|
||||
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir);
|
||||
|
||||
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(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);
|
||||
|
||||
// Logging
|
||||
app.use(logger('dev'));
|
||||
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'));
|
||||
|
||||
// routes
|
||||
app.use('/api/test', testRouter);
|
||||
app.use('/api/users', usersRouter);
|
||||
app.use('/api/roles', rolesRouter);
|
||||
app.use('/api/doctors', doctorsRouter);
|
||||
app.use('/api/patients', patientsRouter);
|
||||
app.use('/api/services', servicesRouter);
|
||||
app.use('/api/companies', companiesRouter);
|
||||
app.use('/api/hospitals', hospitalsRouter);
|
||||
|
||||
// run the API
|
||||
app.listen(process.env.PORT, async () => {
|
||||
log(`running at port ${process.env.PORT}`);
|
||||
|
||||
app.use((req, res) => {
|
||||
return respondWithStatus(res, 404, 'Nothing\'s here!');
|
||||
});
|
||||
|
||||
// test
|
||||
// import { post } from './modules/fetcher';
|
||||
// post('http://127.0.0.1:1109/users/login', { 'usernameOrEmail':'foo', 'password':'bar' }).then(res => console.log(res));
|
||||
app.use((err, req, res) => {
|
||||
error(err.stack);
|
||||
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 dbconf from '../database.json';
|
||||
import { log } from './logManager';
|
||||
|
||||
const connection = mysql.createConnection({
|
||||
host: process.env.DATABASE_HOST,
|
||||
@@ -23,4 +25,58 @@ function createPool(host, user, password, db) {
|
||||
return newPool;
|
||||
}
|
||||
|
||||
export { connection, pool, createPool };
|
||||
function databaseSelfTest() {
|
||||
log('Database self-test');
|
||||
dbconf.tables.forEach(table => {
|
||||
let query = `CREATE TABLE IF NOT EXISTS ${table.name} (`;
|
||||
table.columns.forEach((column, index) => {
|
||||
query += `${column.name} ${column.type}`;
|
||||
if (column.primary_key) query += ' PRIMARY KEY';
|
||||
if (column.constraints && column.constraints.length > 0) query += ` ${column.constraints.join(' ')}`;
|
||||
if (column.index) query += `, INDEX ${column.name}_idx (${column.name})`;
|
||||
if (index < table.columns.length - 1) query += ', ';
|
||||
});
|
||||
if (table.constraints && table.constraints.length > 0) {
|
||||
table.constraints.forEach(constraint => {
|
||||
setTimeout(() => { // do not remove or it breaks /sarcasm
|
||||
if (constraint.primary_key) query += `, PRIMARY KEY (${constraint.columns.join(', ')})`;
|
||||
if (constraint.foreign_key) query += `, CONSTRAINT ${constraint.name} FOREIGN KEY (${constraint.column}) REFERENCES ${constraint.reference} ON DELETE ${constraint.on_delete} ON UPDATE ${constraint.on_update}`;
|
||||
if (constraint.index) query += `, INDEX ${constraint.name} (${constraint.columns.join(', ')})`;
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
query += ') ENGINE=InnoDB;';
|
||||
pool.query(query)
|
||||
.then(() => log(`Table ${table.name} validated`))
|
||||
.catch(err => console.log(`Error creating table ${table.name}: ${err}`));
|
||||
|
||||
if (table.data) {
|
||||
pool.query(`SELECT * FROM ${table.name}`)
|
||||
.then(([rows]) => {
|
||||
if (rows.length === 0) {
|
||||
table.data.forEach(row => {
|
||||
let insertQuery = `INSERT INTO ${table.name} (`;
|
||||
let values = 'VALUES (';
|
||||
Object.keys(row).forEach((key, index) => {
|
||||
insertQuery += key;
|
||||
values += `'${row[key]}'`;
|
||||
if (index < Object.keys(row).length - 1) {
|
||||
insertQuery += ', ';
|
||||
values += ', ';
|
||||
}
|
||||
});
|
||||
insertQuery += ') ' + values + ');';
|
||||
pool.query(insertQuery)
|
||||
.then(() => log(`Row inserted in table ${table.name}`))
|
||||
.catch(err => log(`Error inserting row in table ${table.name}: ${err}`));
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => log(`Error checking if table ${table.name} is empty: ${err}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
databaseSelfTest();
|
||||
|
||||
export { connection, pool, createPool, databaseSelfTest };
|
||||
|
||||
@@ -3,12 +3,12 @@ import { random } from './random';
|
||||
import { log } from './logManager';
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP,
|
||||
host: process.env.MAIL_SERVER,
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: `${process.env.MAIL}`,
|
||||
pass: `${process.env.PASS}`,
|
||||
user: `${process.env.MAIL_ADDRESS}`,
|
||||
pass: `${process.env.PASSWORD}`,
|
||||
},
|
||||
tls: { rejectUnauthorized: false },
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@ export async function verifyPermissions(userId, permissionName, permissionType)
|
||||
}
|
||||
|
||||
export async function checkIfUserEmailIsVerified(userId) {
|
||||
if(process.env.DISABLE_EMAIL_VERIFICATION) return true;
|
||||
try {
|
||||
const [user] = await pool.execute('SELECT email_verified FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
if (user.length === 0) return false;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { log } from './logManager';
|
||||
|
||||
const requestLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 5,
|
||||
max: process.env.RATE_LIMIT_REQUESTS || 100,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: 'Too many requests from this IP, please try again later',
|
||||
@@ -18,6 +18,22 @@ const speedLimiter = slowDown({
|
||||
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) {
|
||||
const load = os.loadavg()[0];
|
||||
const cores = os.cpus().length;
|
||||
@@ -49,6 +65,8 @@ function respondWithStatusJSON(res, statusCode, JSON) {
|
||||
|
||||
export {
|
||||
requestLimiter,
|
||||
antiBruteForce,
|
||||
antiVerificationSpam,
|
||||
speedLimiter,
|
||||
checkSystemLoad,
|
||||
respondWithStatus,
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"express": "^4.19.2",
|
||||
"express-rate-limit": "^7.1.4",
|
||||
"express-slow-down": "^2.0.1",
|
||||
"helmet": "^7.1.0",
|
||||
"https": "^1.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"level": "^8.0.1",
|
||||
"morgan": "^1.10.0",
|
||||
|
||||
@@ -63,7 +63,7 @@ router.post('/register', verifyToken, checkEmailVerified, checkBanned, async (re
|
||||
if ([ email, phone, speciality, status ].every(Boolean)) {
|
||||
try {
|
||||
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],
|
||||
);
|
||||
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;
|
||||
if (doctor_id) {
|
||||
try {
|
||||
const [result] = await pool.execute('UPDATE doctors SET is_verified = 1 WHERE id = ?', [doctor_id]);
|
||||
if (result.affectedRows === 0) return await respondWithStatus(res, 500, 'Error validating doctor');
|
||||
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 (result2.affectedRows === 0) return await respondWithStatus(res, 500, 'Error adding role to user');
|
||||
const [result] = await pool.execute('SELECT * FROM doctors WHERE id = ?',[doctor_id]);
|
||||
if(result.length === 0) return await respondWithStatus(res, 404, 'Doctor not found');
|
||||
if(result[0].is_verified) return await respondWithStatus(res, 400, 'Doctor already verified');
|
||||
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');
|
||||
}
|
||||
catch (err) {
|
||||
@@ -102,6 +105,10 @@ router.post('/:doctorId/validate', verifyToken, checkBanned, checkPermissions('d
|
||||
router.get('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
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');
|
||||
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');
|
||||
@@ -117,6 +124,10 @@ router.patch('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
const { type, value } = req.body;
|
||||
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');
|
||||
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');
|
||||
@@ -143,6 +154,10 @@ router.put('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
||||
if ([ user_id, email, phone, speciality, status, is_verified ].every(Boolean)) {
|
||||
try {
|
||||
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');
|
||||
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');
|
||||
@@ -168,6 +183,10 @@ router.put('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
||||
router.delete('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
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');
|
||||
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');
|
||||
@@ -186,6 +205,10 @@ router.delete('/:doctorId', verifyToken, checkBanned, async (req, res) => {
|
||||
router.get('/:doctorId/appointments', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
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');
|
||||
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 = ?',
|
||||
@@ -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;
|
||||
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);
|
||||
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 ([patient_id, service_id, hospital_id, date, time, status].every(Boolean)) {
|
||||
try {
|
||||
@@ -227,6 +254,10 @@ router.post('/:doctorId/appointments', verifyToken, checkBanned, async (req, res
|
||||
router.get('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
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');
|
||||
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',
|
||||
@@ -245,6 +276,10 @@ router.patch('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned,
|
||||
try {
|
||||
const { type, value } = req.body;
|
||||
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');
|
||||
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');
|
||||
@@ -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;
|
||||
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);
|
||||
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 ([patient_id, service_id, hospital_id, room_id, date, time, status].every(Boolean)) {
|
||||
try {
|
||||
@@ -297,6 +336,10 @@ router.put('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, a
|
||||
router.delete('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
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');
|
||||
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');
|
||||
@@ -315,6 +358,10 @@ router.delete('/:doctorId/appointments/:appointmentId', verifyToken, checkBanned
|
||||
router.get('/:doctorId/services', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
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');
|
||||
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');
|
||||
@@ -329,6 +376,10 @@ router.get('/:doctorId/services', verifyToken, checkBanned, async (req, res) =>
|
||||
router.post('/:doctorId/services', verifyToken, checkBanned, async (req, res) => {
|
||||
const { service_id } = req.body;
|
||||
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 (service_id) {
|
||||
try {
|
||||
@@ -349,6 +400,10 @@ router.post('/:doctorId/services', verifyToken, checkBanned, async (req, res) =>
|
||||
router.patch('/:doctorId/services/:serviceId', verifyToken, checkBanned, async (req, res) => {
|
||||
const { type, value } = req.body;
|
||||
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 (type === 'service_id') {
|
||||
try {
|
||||
@@ -368,6 +423,10 @@ router.patch('/:doctorId/services/:serviceId', verifyToken, checkBanned, async (
|
||||
|
||||
router.delete('/:doctorId/services/:serviceId', 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 {
|
||||
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;
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ router.put('/:hospitalId', verifyToken, checkBanned, checkPermissions('hospital'
|
||||
return await respondWithStatus(res, 404, 'Hospital not found');
|
||||
}
|
||||
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],
|
||||
);
|
||||
|
||||
|
||||
@@ -83,6 +83,10 @@ router.post('/register', verifyToken, checkEmailVerified, checkBanned, async (re
|
||||
router.get('/:patientId', 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, 'patient', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||
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');
|
||||
@@ -98,6 +102,10 @@ router.patch('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
const { type, value } = req.body;
|
||||
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');
|
||||
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');
|
||||
@@ -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)) {
|
||||
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, 'patient', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||
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');
|
||||
@@ -149,6 +161,10 @@ router.put('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
||||
router.delete('/:patientId', 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, 'patient', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||
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');
|
||||
@@ -167,6 +183,10 @@ router.delete('/:patientId', verifyToken, checkBanned, async (req, res) => {
|
||||
router.get('/:patientId/appointments', 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.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)) {
|
||||
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', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||
const [result] = await pool.execute(
|
||||
'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');
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
|
||||
// PUT /: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;
|
||||
|
||||
|
||||
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 { verifyToken, generateToken } from '../modules/tokenManager';
|
||||
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';
|
||||
|
||||
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;
|
||||
if ([ username, email, password, first_name, last_name ].every(Boolean)) {
|
||||
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 (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 code = sendVerification(email, rows[0].id, '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;
|
||||
if ([usernameOrEmail, password].every(Boolean)) {
|
||||
try {
|
||||
@@ -74,6 +76,7 @@ router.post('/login', requestLimiter, async (req, res) => {
|
||||
email: user.email,
|
||||
first_name: user.first_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
|
||||
router.get('/email/request', verifyToken, checkBanned, async (req, res) => {
|
||||
router.get('/email/request', antiVerificationSpam, verifyToken, checkBanned, async (req, res) => {
|
||||
const userId = req.userId;
|
||||
try {
|
||||
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 userId = req.userId;
|
||||
if (code) {
|
||||
@@ -170,7 +173,7 @@ router.get('/email/verify', verifyToken, checkBanned, async (req, res) => {
|
||||
// PATCH /phone/verify
|
||||
|
||||
// Password reset endpoints
|
||||
router.post('/password/request', async (req, res) => {
|
||||
router.post('/password/request', antiVerificationSpam, async (req, res) => {
|
||||
const { usernameOrEmail } = req.body;
|
||||
if (usernameOrEmail) {
|
||||
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;
|
||||
if ([usernameOrEmail, password, code].every(Boolean)) {
|
||||
try {
|
||||
@@ -241,6 +244,7 @@ router.patch('/password/verify', async (req, res) => {
|
||||
|
||||
router.get('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
if (req.params.userId == '@me') req.params.userId = req.userId;
|
||||
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 1)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||
const [rows] = await pool.execute('SELECT id, first_name, last_name, username, email, phone FROM users WHERE id = ? LIMIT 1', [req.params.userId]);
|
||||
if (rows.length === 0) return await respondWithStatus(res, 404, 'User not found');
|
||||
@@ -257,6 +261,7 @@ router.get('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||
|
||||
router.patch('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
if (req.params.userId == '@me') req.params.userId = req.userId;
|
||||
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||
const { type } = req.body;
|
||||
let { value } = req.body;
|
||||
@@ -282,6 +287,7 @@ router.patch('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||
|
||||
router.put('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
if (req.params.userId == '@me') req.params.userId = req.userId;
|
||||
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 2)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||
const { first_name, last_name, username, password = null, email, phone = null } = req.body;
|
||||
if ([first_name, last_name, username, email].every(Boolean)) {
|
||||
@@ -310,6 +316,7 @@ router.put('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||
|
||||
router.delete('/:userId', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
if (req.params.userId == '@me') req.params.userId = req.userId;
|
||||
if (req.params.userId != req.userId && !verifyPermissions(req.userId, 'user', 4)) return await respondWithStatus(res, 403, 'Missing permission');
|
||||
if (!userExists(req.params.userId)) return await respondWithStatus(res, 404, 'User not found');
|
||||
const [result] = await pool.execute('DELETE FROM users WHERE id = ?', [ req.params.userId ]);
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user