med-cms 1.0.0-beta.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +13 -0
- package/README.md +117 -0
- package/database/config/db-init.js +67 -53
- package/database/models/appointments.js +35 -20
- package/database/models/clinics.js +25 -13
- package/database/models/diseases.js +21 -13
- package/database/models/districts.js +55 -0
- package/database/models/news.js +25 -0
- package/database/models/user-addresses.js +21 -14
- package/database/models/users.js +69 -26
- package/database/services/admin/create-clinic.js +18 -6
- package/database/services/admin/db-analytics.js +42 -16
- package/database/services/admin/index.js +6 -6
- package/database/services/admin/manage-staff.js +47 -17
- package/database/services/doctor/conduct-exam.js +26 -20
- package/database/services/doctor/get-schedule.js +26 -18
- package/database/services/doctor/index.js +6 -6
- package/database/services/doctor/patient-access.js +34 -25
- package/database/services/patient/book-appointment.js +37 -17
- package/database/services/patient/calc-district.js +70 -21
- package/database/services/patient/get-profile.js +49 -21
- package/database/services/patient/index.js +5 -5
- package/env_init.js +55 -0
- package/index.d.ts +38 -24
- package/index.js +48 -25
- package/migrate-diseases.js +192 -120
- package/package.json +3 -3
- package/public/css/admin.css +80 -80
- package/public/css/auth.css +52 -44
- package/public/css/doctor.css +131 -125
- package/public/css/home.css +155 -0
- package/public/html/dashboard-admin.html +423 -101
- package/public/html/dashboard-doctor.html +155 -66
- package/public/html/dashboard-patient.html +199 -94
- package/public/html/home/about.html +0 -0
- package/public/html/home/contact.html +0 -0
- package/public/html/home/info.html +0 -0
- package/public/html/home.html +159 -0
- package/public/html/login.html +73 -43
- package/public/js/admin.js +329 -165
- package/public/js/auth.js +45 -41
- package/public/js/doctor.js +117 -103
- package/public/js/home.js +113 -0
- package/public/js/patient.js +130 -108
- package/server/midleware.js +33 -13
- package/server/permissions.js +105 -43
- package/server/server.js +517 -176
- package/README +0 -161
- package/test.js +0 -27
|
@@ -1,49 +1,98 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
|
|
2
|
+
// Импортируем строго синглтон базы данных для исключения круговой зависимости
|
|
3
|
+
const dbConfig = require(
|
|
4
|
+
path.join(__dirname, "..", "..", "config", "db-init.js"),
|
|
5
|
+
);
|
|
6
|
+
|
|
3
7
|
const calcDistrict = {
|
|
4
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Динамический поиск участка по названию улицы в таблице настроек админа
|
|
10
|
+
*/
|
|
11
|
+
async determineLocation(clinicId, street) {
|
|
5
12
|
const cleanedStreet = street.trim().toLowerCase();
|
|
6
|
-
|
|
13
|
+
|
|
14
|
+
// Достаем проинициализированную модель Участков онлайн
|
|
15
|
+
const District = dbConfig.models.District || dbConfig.District;
|
|
16
|
+
|
|
17
|
+
if (!District) {
|
|
18
|
+
console.log(
|
|
19
|
+
"⚠️ [Med Module] Модель District еще не инициализирована в БД",
|
|
20
|
+
);
|
|
7
21
|
return {
|
|
8
|
-
assignedDistrict:
|
|
9
|
-
assignedCabinet:
|
|
22
|
+
assignedDistrict: "Общий распределительный участок",
|
|
23
|
+
assignedCabinet: "Кабинет №100 (Регистратура)",
|
|
10
24
|
};
|
|
11
25
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
|
|
27
|
+
// Ищем все участки, закрепленные за данной клиникой в SQLite
|
|
28
|
+
const districts = await District.findAll({ where: { ClinicId: clinicId } });
|
|
29
|
+
|
|
30
|
+
// Проверяем, в какой из участков админ записал эту улицу
|
|
31
|
+
for (const district of districts) {
|
|
32
|
+
const streetsArray = district.coveredStreets
|
|
33
|
+
.split(",")
|
|
34
|
+
.map((s) => s.trim());
|
|
35
|
+
|
|
36
|
+
if (streetsArray.includes(cleanedStreet)) {
|
|
37
|
+
return {
|
|
38
|
+
assignedDistrict: district.name,
|
|
39
|
+
assignedCabinet: `${district.cabinet} (Врач: ${district.doctorName || "Не назначен"})`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
17
42
|
}
|
|
43
|
+
|
|
44
|
+
// Дефолтный вариант, если админ еще не добавил эту улицу в карту покрытия
|
|
18
45
|
return {
|
|
19
|
-
assignedDistrict:
|
|
20
|
-
assignedCabinet:
|
|
46
|
+
assignedDistrict: "Общий распределительный участок",
|
|
47
|
+
assignedCabinet: "Кабинет №100 (Регистратура)",
|
|
21
48
|
};
|
|
22
49
|
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Привязка адреса к пациенту с динамическим расчетом участка
|
|
53
|
+
*/
|
|
23
54
|
async saveAddress(userId, addressData) {
|
|
24
|
-
const { city, street, houseNumber } = addressData;
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
const { city, street, houseNumber, clinicId } = addressData;
|
|
56
|
+
|
|
57
|
+
// Достаем модель адресов из базы
|
|
58
|
+
const UserAddress = dbConfig.models.UserAddress || dbConfig.UserAddress;
|
|
59
|
+
|
|
60
|
+
if (!city || !street || !houseNumber || !clinicId) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
"Заполните обязательные поля: город, улица, дом, ID клиники прикрепления",
|
|
63
|
+
);
|
|
27
64
|
}
|
|
28
|
-
|
|
29
|
-
|
|
65
|
+
|
|
66
|
+
// Вычисляем лечебный участок по динамической карте улиц из базы данных
|
|
67
|
+
const { assignedDistrict, assignedCabinet } = await this.determineLocation(
|
|
68
|
+
clinicId,
|
|
69
|
+
street,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Проверяем, обновляет ли пациент старый адрес или создает новый
|
|
73
|
+
const existingAddress = await UserAddress.findOne({
|
|
74
|
+
where: { UserId: userId },
|
|
75
|
+
});
|
|
76
|
+
|
|
30
77
|
if (existingAddress) {
|
|
31
78
|
return await existingAddress.update({
|
|
32
79
|
city,
|
|
33
80
|
street,
|
|
34
81
|
houseNumber,
|
|
35
82
|
assignedDistrict,
|
|
36
|
-
assignedCabinet
|
|
83
|
+
assignedCabinet,
|
|
37
84
|
});
|
|
38
85
|
}
|
|
86
|
+
|
|
39
87
|
return await UserAddress.create({
|
|
40
88
|
UserId: userId,
|
|
41
89
|
city,
|
|
42
90
|
street,
|
|
43
91
|
houseNumber,
|
|
44
92
|
assignedDistrict,
|
|
45
|
-
assignedCabinet
|
|
93
|
+
assignedCabinet,
|
|
46
94
|
});
|
|
47
|
-
}
|
|
95
|
+
},
|
|
48
96
|
};
|
|
49
|
-
|
|
97
|
+
|
|
98
|
+
module.exports = calcDistrict;
|
|
@@ -1,47 +1,75 @@
|
|
|
1
|
-
const path = require("path")
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const path = require("path");
|
|
2
|
+
// Импортируем синглтон конфигурации базы данных
|
|
3
|
+
const dbConfig = require(
|
|
4
|
+
path.join(__dirname, "..", "..", "config", "db-init.js"),
|
|
5
|
+
);
|
|
6
|
+
|
|
6
7
|
const getProfile = {
|
|
8
|
+
/**
|
|
9
|
+
* Собрать полное медицинское досье пациента для личного кабинета
|
|
10
|
+
*/
|
|
7
11
|
async getFullData(patientId) {
|
|
8
12
|
if (!patientId) {
|
|
9
|
-
throw new Error(
|
|
13
|
+
throw new Error("ID пациента не указан");
|
|
10
14
|
}
|
|
15
|
+
|
|
16
|
+
// Безопасно достаем проинициализированные модели
|
|
17
|
+
const User = dbConfig.models.User || dbConfig.User;
|
|
18
|
+
const UserAddress = dbConfig.models.UserAddress || dbConfig.UserAddress;
|
|
19
|
+
const Appointment = dbConfig.models.Appointment || dbConfig.Appointment;
|
|
20
|
+
const Disease = dbConfig.models.Disease || dbConfig.Disease;
|
|
21
|
+
|
|
22
|
+
// 1. Ищем профиль пользователя с адресом
|
|
11
23
|
const profile = await User.findByPk(patientId, {
|
|
12
|
-
attributes: { exclude: [
|
|
24
|
+
attributes: { exclude: ["password"] },
|
|
13
25
|
include: [
|
|
14
26
|
{
|
|
15
27
|
model: UserAddress,
|
|
16
|
-
attributes: [
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
attributes: [
|
|
29
|
+
"city",
|
|
30
|
+
"street",
|
|
31
|
+
"houseNumber",
|
|
32
|
+
"assignedDistrict",
|
|
33
|
+
"assignedCabinet",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
19
37
|
});
|
|
38
|
+
|
|
20
39
|
if (!profile) {
|
|
21
|
-
throw new Error(
|
|
40
|
+
throw new Error("Профиль пациента не найден в базе данных");
|
|
22
41
|
}
|
|
42
|
+
|
|
43
|
+
// 2. Находим всю историю приёмов с врачами и диагнозами (используем правильный alias PatientAppointments)
|
|
23
44
|
const appointments = await Appointment.findAll({
|
|
24
45
|
where: { PatientId: patientId },
|
|
25
46
|
include: [
|
|
26
47
|
{
|
|
27
48
|
model: User,
|
|
28
|
-
as:
|
|
29
|
-
attributes: [
|
|
49
|
+
as: "Doctor",
|
|
50
|
+
attributes: ["fullName", "specialty", "position"],
|
|
30
51
|
},
|
|
31
52
|
{
|
|
32
53
|
model: Disease,
|
|
33
|
-
attributes: [
|
|
34
|
-
|
|
54
|
+
attributes: [
|
|
55
|
+
"name",
|
|
56
|
+
"code",
|
|
57
|
+
"treatmentRecommendations",
|
|
58
|
+
"recommendedMedications",
|
|
59
|
+
],
|
|
60
|
+
},
|
|
35
61
|
],
|
|
36
62
|
order: [
|
|
37
|
-
[
|
|
38
|
-
[
|
|
39
|
-
]
|
|
63
|
+
["date", "DESC"],
|
|
64
|
+
["time", "DESC"],
|
|
65
|
+
],
|
|
40
66
|
});
|
|
67
|
+
|
|
41
68
|
return {
|
|
42
69
|
personalInfo: profile,
|
|
43
|
-
medicalHistory: appointments
|
|
70
|
+
medicalHistory: appointments,
|
|
44
71
|
};
|
|
45
|
-
}
|
|
72
|
+
},
|
|
46
73
|
};
|
|
47
|
-
|
|
74
|
+
|
|
75
|
+
module.exports = getProfile;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const calcDistrict = require(path.join(__dirname,
|
|
3
|
-
const getProfile = require(path.join(__dirname,
|
|
4
|
-
const bookAppointment = require(path.join(__dirname,
|
|
2
|
+
const calcDistrict = require(path.join(__dirname, "calc-district"));
|
|
3
|
+
const getProfile = require(path.join(__dirname, "get-profile"));
|
|
4
|
+
const bookAppointment = require(path.join(__dirname, "book-appointment"));
|
|
5
5
|
const PatientService = {
|
|
6
6
|
savePatientAddress: calcDistrict.saveAddress.bind(calcDistrict),
|
|
7
7
|
getFullMyData: getProfile.getFullData,
|
|
8
|
-
bookAppointment: bookAppointment.create.bind(bookAppointment)
|
|
8
|
+
bookAppointment: bookAppointment.create.bind(bookAppointment),
|
|
9
9
|
};
|
|
10
|
-
module.exports = PatientService;
|
|
10
|
+
module.exports = PatientService;
|
package/env_init.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const process = require("process");
|
|
4
|
+
const envPath = path.resolve(process.cwd(), ".env");
|
|
5
|
+
const logger = require("custom-color-logs").print;
|
|
6
|
+
const defaultEnvContent = `
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ==============================================================================
|
|
10
|
+
# MED-CMS (v1.0.0)
|
|
11
|
+
# ==============================================================================
|
|
12
|
+
|
|
13
|
+
# Специяльный тег инициализации (значение не меняйте)
|
|
14
|
+
MED_CMS_INITIALIZED = true
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Код который будет хешироваться в куки
|
|
18
|
+
JWT_SECRET = 0000
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Данные админа
|
|
22
|
+
# Почта админестратора
|
|
23
|
+
ADMIN_GMAIL = x@gmail.com
|
|
24
|
+
|
|
25
|
+
# Телефон админестратора
|
|
26
|
+
ADMIN_PHONE = +375 00 00000000
|
|
27
|
+
|
|
28
|
+
# Пароль аккаунта админестратора
|
|
29
|
+
ADMIN_PASSWORD = 1234
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
function initializeEnvironment() {
|
|
33
|
+
try {
|
|
34
|
+
if (!fs.existsSync(envPath)) {
|
|
35
|
+
fs.writeFileSync(envPath, defaultEnvContent, "utf8");
|
|
36
|
+
} else {
|
|
37
|
+
const currentEnv = fs.readFileSync(envPath, "utf8");
|
|
38
|
+
if (currentEnv.includes("MED_CMS_INITIALIZED")) {
|
|
39
|
+
require("dotenv").config({ path: envPath });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
fs.appendFileSync(envPath, `\n\n${defaultEnvContent}`, "utf8");
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.log(
|
|
46
|
+
logger.ServerMedError(
|
|
47
|
+
"Не удалось проверить или обновить .env:",
|
|
48
|
+
err.message,
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
require("dotenv").config({ path: envPath });
|
|
53
|
+
}
|
|
54
|
+
initializeEnvironment();
|
|
55
|
+
module.exports = { envPath };
|
package/index.d.ts
CHANGED
|
@@ -1,25 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export interface MedCmsConfig {
|
|
4
|
-
jwtSecret: string;
|
|
5
|
-
dbPath?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
1
|
+
import { Express } from "express";
|
|
8
2
|
export type DoctorSpecialty =
|
|
9
|
-
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
3
|
+
| "Терапевт"
|
|
4
|
+
| "Педиатр"
|
|
5
|
+
| "Врач общей практики"
|
|
6
|
+
| "Кардиолог"
|
|
7
|
+
| "Невролог"
|
|
8
|
+
| "Эндокринолог"
|
|
9
|
+
| "Гастроэнтеролог"
|
|
10
|
+
| "Пульмонолог"
|
|
11
|
+
| "Нефролог"
|
|
12
|
+
| "Ревматолог"
|
|
13
|
+
| "Гематолог"
|
|
14
|
+
| "Хирург"
|
|
15
|
+
| "Травматолог-ортопед"
|
|
16
|
+
| "Сосудистый хирург"
|
|
17
|
+
| "Нейрохирург"
|
|
18
|
+
| "Кардиохирург"
|
|
19
|
+
| "Пластический хирург"
|
|
20
|
+
| "Офтальмолог"
|
|
21
|
+
| "Отоларинголог"
|
|
22
|
+
| "Дерматолог"
|
|
23
|
+
| "Стоматолог"
|
|
24
|
+
| "Гинеколог"
|
|
25
|
+
| "Уролог"
|
|
26
|
+
| "Онколог"
|
|
27
|
+
| "Инфекционист"
|
|
28
|
+
| "Аллерголог-иммунолог"
|
|
29
|
+
| "Психиатр"
|
|
30
|
+
| "Психотерапевт"
|
|
31
|
+
| "Нарколог"
|
|
32
|
+
| "Анестезиолог-реаниматолог"
|
|
33
|
+
| "Рентгенолог"
|
|
34
|
+
| "Врач УЗД"
|
|
35
|
+
| "Эндоскопист"
|
|
36
|
+
| "Патологоанатом";
|
|
37
|
+
export type UserRole = "patient" | "doctor" | "admin";
|
|
38
|
+
declare function init(app: Express): void;
|
|
39
|
+
export default init;
|
package/index.js
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
|
-
require("dotenv").config({debug:false});
|
|
2
|
-
const express = require(
|
|
3
|
-
const cookieParser = require(
|
|
1
|
+
require("dotenv").config({ debug: false });
|
|
2
|
+
const express = require("express");
|
|
3
|
+
const cookieParser = require("cookie-parser");
|
|
4
4
|
const chalk = require("chalk-palette");
|
|
5
5
|
const logger = require("custom-color-logs").print;
|
|
6
|
-
const path = require(
|
|
7
|
-
const fs = require(
|
|
8
|
-
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
require(path.join(__dirname, "env_init.js"));
|
|
9
9
|
// Оставляем только импорт самого модуля базы данных
|
|
10
|
-
const dbConfig = require(
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const dbConfig = require(
|
|
11
|
+
path.join(__dirname, "database", "config", "db-init.js"),
|
|
12
|
+
);
|
|
13
|
+
const server = require("./server/server");
|
|
14
|
+
const bcrypt = require("bcrypt");
|
|
13
15
|
|
|
14
16
|
async function init(app) {
|
|
15
17
|
await chalk.animation.textShimmer("MED-CMS");
|
|
16
18
|
console.log(logger.ServerMedInfo("Запуск сервера..."));
|
|
17
|
-
await chalk.animation.retroLines({
|
|
19
|
+
await chalk.animation.retroLines({
|
|
20
|
+
duration: 4000,
|
|
21
|
+
linesCount: 4,
|
|
22
|
+
offset: 3,
|
|
23
|
+
});
|
|
18
24
|
|
|
19
|
-
if (app && typeof app ===
|
|
25
|
+
if (app && typeof app === "function" && typeof app.handle === "function") {
|
|
20
26
|
console.log(logger.ServerMedInfo("Проверка на app пройдена успешно!"));
|
|
21
27
|
} else {
|
|
22
28
|
console.log(logger.ServerMedError("app не передан или повреждён!"));
|
|
@@ -26,40 +32,57 @@ async function init(app) {
|
|
|
26
32
|
try {
|
|
27
33
|
const dbPath = path.join(__dirname, "db", "med-cms.sqlite");
|
|
28
34
|
const dbDir = path.dirname(dbPath);
|
|
29
|
-
if (!fs.existsSync(dbDir)){
|
|
30
|
-
|
|
35
|
+
if (!fs.existsSync(dbDir)) {
|
|
36
|
+
fs.mkdirSync(dbDir, { extended: true });
|
|
31
37
|
}
|
|
32
|
-
|
|
38
|
+
|
|
33
39
|
// Инициализируем БД
|
|
34
40
|
await dbConfig.initDatabase(dbPath);
|
|
35
41
|
console.log(logger.ServerMedInfo("База данных успешно инициализирована"));
|
|
36
42
|
} catch (err) {
|
|
37
|
-
console.log(
|
|
43
|
+
console.log(
|
|
44
|
+
logger.DatabaseError(
|
|
45
|
+
"Критическая ошибка запуска базы данных med-cms:",
|
|
46
|
+
err,
|
|
47
|
+
),
|
|
48
|
+
);
|
|
38
49
|
return;
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
try {
|
|
42
53
|
// ИСПРАВЛЕНИЕ: Достаем User из проинициализированного объекта базы данных ОНЛАЙН
|
|
43
|
-
const User = dbConfig.User;
|
|
54
|
+
const User = dbConfig.User;
|
|
44
55
|
|
|
45
|
-
const adminExists = await User.findOne({ where: { role:
|
|
56
|
+
const adminExists = await User.findOne({ where: { role: "admin" } });
|
|
46
57
|
if (!adminExists) {
|
|
47
|
-
console.log(
|
|
48
|
-
|
|
58
|
+
console.log(
|
|
59
|
+
logger.ServerMedInfo(
|
|
60
|
+
"Администратор не найден. Создание аккаунта по умолчанию...",
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
const adminPassword = process.env.ADMIN_PASSWORD;
|
|
49
64
|
const hashedPassword = await bcrypt.hash(adminPassword, 10);
|
|
50
65
|
await User.create({
|
|
51
|
-
email:
|
|
66
|
+
email: process.env.ADMIN_GMAIL,
|
|
52
67
|
password: hashedPassword,
|
|
53
68
|
fullName: "Главный Администратор",
|
|
54
|
-
role: "admin",
|
|
55
|
-
specialty: null
|
|
69
|
+
role: "admin",
|
|
70
|
+
specialty: null,
|
|
56
71
|
});
|
|
57
|
-
console.log(
|
|
72
|
+
console.log(
|
|
73
|
+
logger.ServerMedInfo(`Аккаунт суперадмина успешно создан in SQLite.`),
|
|
74
|
+
);
|
|
58
75
|
} else {
|
|
59
|
-
console.log(
|
|
76
|
+
console.log(
|
|
77
|
+
logger.ServerMedInfo("Администратор уже существует в базе данных."),
|
|
78
|
+
);
|
|
60
79
|
}
|
|
61
80
|
} catch (error) {
|
|
62
|
-
console.log(
|
|
81
|
+
console.log(
|
|
82
|
+
logger.ServerMedError(
|
|
83
|
+
"Ошибка при автоматическом создании админа: " + error.message,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
console.log(logger.ServerMedInfo("Подключение роутов..."));
|