med-cms 1.0.0-beta.1 → 1.0.0-beta.2
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/database/config/db-init.js +66 -20
- package/database/models/appointments.js +29 -39
- package/database/models/clinics.js +22 -18
- package/database/models/diseases.js +19 -37
- package/database/models/user-addresses.js +22 -34
- package/database/models/users.js +36 -31
- package/index.js +19 -7
- package/package.json +1 -1
- package/server/server.js +59 -50
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
const { Sequelize } = require('sequelize');
|
|
1
|
+
const { Sequelize, DataTypes } = require('sequelize');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const Clinic = require(path.join(__dirname, '..', 'models', 'clinics'));
|
|
4
|
-
const User = require(path.join(__dirname, '..', 'models', 'users'));
|
|
5
|
-
const UserAddress = require(path.join(__dirname, '..', 'models', 'user-addresses'));
|
|
6
|
-
const Disease = require(path.join(__dirname, '..', 'models', 'diseases'));
|
|
7
|
-
const Appointment = require(path.join(__dirname, '..', 'models', 'appointments'));
|
|
8
3
|
const logger = require("custom-color-logs").print;
|
|
4
|
+
|
|
9
5
|
let sequelize;
|
|
6
|
+
|
|
10
7
|
function getSequelizeInstance(dbPath) {
|
|
11
8
|
if (!sequelize) {
|
|
12
9
|
const storagePath = dbPath || path.join(process.cwd(), 'med-cms.sqlite');
|
|
@@ -18,27 +15,76 @@ function getSequelizeInstance(dbPath) {
|
|
|
18
15
|
}
|
|
19
16
|
return sequelize;
|
|
20
17
|
}
|
|
18
|
+
|
|
21
19
|
async function initDatabase(dbPath) {
|
|
22
20
|
const instance = getSequelizeInstance(dbPath);
|
|
21
|
+
|
|
23
22
|
try {
|
|
24
23
|
await instance.authenticate();
|
|
25
|
-
console.log(logger.DatabaseMedInfo("Успешное подключение к SQLite через Sequelize."))
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
console.log(logger.DatabaseMedInfo("Успешное подключение к SQLite через Sequelize."));
|
|
25
|
+
|
|
26
|
+
// 1. Импортируем функции моделей и сразу инициализируем их инстансом sequelize
|
|
27
|
+
const models = {
|
|
28
|
+
Clinic: require(path.join(__dirname, '..', 'models', 'clinics'))(instance, DataTypes),
|
|
29
|
+
User: require(path.join(__dirname, '..', 'models', 'users'))(instance, DataTypes),
|
|
30
|
+
UserAddress: require(path.join(__dirname, '..', 'models', 'user-addresses'))(instance, DataTypes),
|
|
31
|
+
Disease: require(path.join(__dirname, '..', 'models', 'diseases'))(instance, DataTypes),
|
|
32
|
+
Appointment: require(path.join(__dirname, '..', 'models', 'appointments'))(instance, DataTypes)
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// 2. Автоматически запускаем связи, которые мы прописали внутри файлов таблиц
|
|
36
|
+
Object.keys(models).forEach((modelName) => {
|
|
37
|
+
if (models[modelName].associate) {
|
|
38
|
+
models[modelName].associate(models);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
33
42
|
await instance.sync({ alter: true });
|
|
34
|
-
console.log(logger.DatabaseMedInfo("Все таблицы базы данных успешно синхронизированы."))
|
|
43
|
+
console.log(logger.DatabaseMedInfo("Все таблицы базы данных успешно синхронизированы."));
|
|
35
44
|
} catch (error) {
|
|
36
|
-
console.log(logger.DatabaseMedError('Ошибка инициализации базы данных:', error))
|
|
45
|
+
console.log(logger.DatabaseMedError('Ошибка инициализации базы данных:', error));
|
|
37
46
|
throw error;
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
|
|
50
|
+
// В самом конце вашего database/config/db-init.js вместо старого exports:
|
|
51
|
+
|
|
52
|
+
const db = {
|
|
42
53
|
getSequelizeInstance,
|
|
43
|
-
initDatabase
|
|
44
|
-
}
|
|
54
|
+
initDatabase,
|
|
55
|
+
get sequelize() { return sequelize; },
|
|
56
|
+
// Динамически прокидываем модели наружу, чтобы они были доступны как db.User, db.Clinic и т.д.
|
|
57
|
+
models: {}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Перепишем функцию initDatabase, чтобы она сохраняла модели в db.models и самом объекте db
|
|
61
|
+
async function initDatabase(dbPath) {
|
|
62
|
+
const instance = getSequelizeInstance(dbPath);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await instance.authenticate();
|
|
66
|
+
console.log(logger.DatabaseMedInfo("Успешное подключение к SQLite через Sequelize."));
|
|
67
|
+
|
|
68
|
+
// Инициализируем и сохраняем в объект
|
|
69
|
+
db.models.Clinic = db.Clinic = require(path.join(__dirname, '..', 'models', 'clinics'))(instance, DataTypes);
|
|
70
|
+
db.models.User = db.User = require(path.join(__dirname, '..', 'models', 'users'))(instance, DataTypes);
|
|
71
|
+
db.models.UserAddress = db.UserAddress = require(path.join(__dirname, '..', 'models', 'user-addresses'))(instance, DataTypes);
|
|
72
|
+
db.models.Disease = db.Disease = require(path.join(__dirname, '..', 'models', 'diseases'))(instance, DataTypes);
|
|
73
|
+
db.models.Appointment = db.Appointment = require(path.join(__dirname, '..', 'models', 'appointments'))(instance, DataTypes);
|
|
74
|
+
|
|
75
|
+
// Запускаем связи
|
|
76
|
+
Object.keys(db.models).forEach((modelName) => {
|
|
77
|
+
if (db.models[modelName].associate) {
|
|
78
|
+
db.models[modelName].associate(db.models);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await instance.sync({ alter: true });
|
|
83
|
+
console.log(logger.DatabaseMedInfo("Все таблицы базы данных успешно синхронизированы."));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.log(logger.DatabaseMedError('Ошибка инициализации базы данных:', error));
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = db;
|
|
@@ -1,39 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type: DataTypes.
|
|
17
|
-
allowNull:
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
allowNull: true
|
|
31
|
-
},
|
|
32
|
-
doctorNotes: {
|
|
33
|
-
type: DataTypes.TEXT,
|
|
34
|
-
allowNull: true
|
|
35
|
-
}
|
|
36
|
-
}, {
|
|
37
|
-
timestamps: true
|
|
38
|
-
});
|
|
39
|
-
module.exports = Appointment;
|
|
1
|
+
module.exports = (sequelize, DataTypes) => {
|
|
2
|
+
const Appointment = sequelize.define('Appointment', {
|
|
3
|
+
id: {
|
|
4
|
+
type: DataTypes.UUID,
|
|
5
|
+
defaultValue: DataTypes.UUIDV4,
|
|
6
|
+
primaryKey: true
|
|
7
|
+
},
|
|
8
|
+
date: { type: DataTypes.DATEONLY, allowNull: false },
|
|
9
|
+
time: { type: DataTypes.STRING, allowNull: false },
|
|
10
|
+
status: {
|
|
11
|
+
type: DataTypes.ENUM('ожидается', 'завершен', 'отменен'),
|
|
12
|
+
defaultValue: 'ожидается',
|
|
13
|
+
allowNull: false
|
|
14
|
+
},
|
|
15
|
+
complaints: { type: DataTypes.TEXT, allowNull: true },
|
|
16
|
+
objectively: { type: DataTypes.TEXT, allowNull: true },
|
|
17
|
+
doctorNotes: { type: DataTypes.TEXT, allowNull: true }
|
|
18
|
+
}, {
|
|
19
|
+
timestamps: true
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
Appointment.associate = (models) => {
|
|
23
|
+
Appointment.belongsTo(models.User, { as: 'Patient', foreignKey: 'PatientId', onDelete: 'CASCADE' });
|
|
24
|
+
Appointment.belongsTo(models.User, { as: 'Doctor', foreignKey: 'DoctorId', onDelete: 'CASCADE' });
|
|
25
|
+
Appointment.belongsTo(models.Disease, { foreignKey: 'DiseaseId', onDelete: 'SET NULL' });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return Appointment;
|
|
29
|
+
};
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
module.exports = (sequelize, DataTypes) => {
|
|
2
|
+
const Clinic = sequelize.define('Clinic', {
|
|
3
|
+
id: {
|
|
4
|
+
type: DataTypes.INTEGER,
|
|
5
|
+
autoIncrement: true,
|
|
6
|
+
primaryKey: true
|
|
7
|
+
},
|
|
8
|
+
name: { type: DataTypes.STRING, allowNull: false, unique: true },
|
|
9
|
+
type: { type: DataTypes.ENUM('больница', 'поликлиника', 'станция скорой помощи', 'частный медицинский центр'), allowNull: false },
|
|
10
|
+
address: { type: DataTypes.STRING, allowNull: false },
|
|
11
|
+
phone: { type: DataTypes.STRING, allowNull: true }
|
|
12
|
+
}, {
|
|
13
|
+
timestamps: true
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Настройка связи прямо в таблице клиник
|
|
17
|
+
Clinic.associate = (models) => {
|
|
18
|
+
Clinic.hasMany(models.User, { foreignKey: 'ClinicId' });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return Clinic;
|
|
22
|
+
};
|
|
@@ -1,37 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type: DataTypes.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
21
|
-
treatmentRecommendations: {
|
|
22
|
-
type: DataTypes.TEXT,
|
|
23
|
-
allowNull: true
|
|
24
|
-
},
|
|
25
|
-
recommendedMedications: {
|
|
26
|
-
type: DataTypes.TEXT,
|
|
27
|
-
allowNull: true
|
|
28
|
-
},
|
|
29
|
-
casesCount: {
|
|
30
|
-
type: DataTypes.INTEGER,
|
|
31
|
-
defaultValue: 0,
|
|
32
|
-
allowNull: false
|
|
33
|
-
}
|
|
34
|
-
}, {
|
|
35
|
-
timestamps: true
|
|
36
|
-
});
|
|
37
|
-
module.exports = Disease;
|
|
1
|
+
module.exports = (sequelize, DataTypes) => {
|
|
2
|
+
const Disease = sequelize.define('Disease', {
|
|
3
|
+
id: {
|
|
4
|
+
type: DataTypes.INTEGER,
|
|
5
|
+
autoIncrement: true,
|
|
6
|
+
primaryKey: true
|
|
7
|
+
},
|
|
8
|
+
name: { type: DataTypes.STRING, allowNull: false, unique: true },
|
|
9
|
+
code: { type: DataTypes.STRING, allowNull: false, unique: true },
|
|
10
|
+
treatmentRecommendations: { type: DataTypes.TEXT, allowNull: true },
|
|
11
|
+
recommendedMedications: { type: DataTypes.TEXT, allowNull: true },
|
|
12
|
+
casesCount: { type: DataTypes.INTEGER, defaultValue: 0, allowNull: false }
|
|
13
|
+
}, {
|
|
14
|
+
timestamps: true
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// У этой модели нет прямых исходящих связей (зависит от Appointment, но связь прописана там)
|
|
18
|
+
return Disease;
|
|
19
|
+
};
|
|
@@ -1,34 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type: DataTypes.STRING,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
assignedDistrict: {
|
|
24
|
-
type: DataTypes.STRING,
|
|
25
|
-
allowNull: true
|
|
26
|
-
},
|
|
27
|
-
assignedCabinet: {
|
|
28
|
-
type: DataTypes.STRING,
|
|
29
|
-
allowNull: true
|
|
30
|
-
}
|
|
31
|
-
}, {
|
|
32
|
-
timestamps: true
|
|
33
|
-
});
|
|
34
|
-
module.exports = UserAddress;
|
|
1
|
+
module.exports = (sequelize, DataTypes) => {
|
|
2
|
+
const UserAddress = sequelize.define('UserAddress', {
|
|
3
|
+
id: {
|
|
4
|
+
type: DataTypes.UUID,
|
|
5
|
+
defaultValue: DataTypes.UUIDV4,
|
|
6
|
+
primaryKey: true
|
|
7
|
+
},
|
|
8
|
+
city: { type: DataTypes.STRING, allowNull: false },
|
|
9
|
+
street: { type: DataTypes.STRING, allowNull: false },
|
|
10
|
+
houseNumber: { type: DataTypes.STRING, allowNull: false },
|
|
11
|
+
assignedDistrict: { type: DataTypes.STRING, allowNull: true },
|
|
12
|
+
assignedCabinet: { type: DataTypes.STRING, allowNull: true }
|
|
13
|
+
}, {
|
|
14
|
+
timestamps: true
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
UserAddress.associate = (models) => {
|
|
18
|
+
UserAddress.belongsTo(models.User, { foreignKey: 'UserId', onDelete: 'CASCADE' });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return UserAddress;
|
|
22
|
+
};
|
package/database/models/users.js
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
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
|
-
|
|
1
|
+
module.exports = (sequelize, DataTypes) => {
|
|
2
|
+
const User = sequelize.define('User', {
|
|
3
|
+
id: {
|
|
4
|
+
type: DataTypes.INTEGER,
|
|
5
|
+
autoIncrement: true,
|
|
6
|
+
primaryKey: true
|
|
7
|
+
},
|
|
8
|
+
email: { type: DataTypes.STRING, allowNull: false, unique: true, validate: { isEmail: true } },
|
|
9
|
+
password: { type: DataTypes.STRING, allowNull: false },
|
|
10
|
+
fullName: { type: DataTypes.STRING, allowNull: false },
|
|
11
|
+
role: { type: DataTypes.ENUM('patient', 'doctor', 'admin'), defaultValue: 'patient', allowNull: false },
|
|
12
|
+
specialty: {
|
|
13
|
+
type: DataTypes.ENUM(
|
|
14
|
+
'Терапевт', 'Педиатр', 'Врач общей практики', 'Кардиолог', 'Невролог',
|
|
15
|
+
'Эндокринолог', 'Гастроэнтеролог', 'Пульмонолог', 'Нефролог', 'Ревматолог',
|
|
16
|
+
'Гематолог', 'Хирург', 'Травматолог-ортопед', 'Сосудистый хирург', 'Нейрохирург',
|
|
17
|
+
'Кардиохирург', 'Пластический хирург', 'Офтальмолог', 'Отоларинголог', 'Дерматолог',
|
|
18
|
+
'Стоматолог', 'Гинеколог', 'Уролог', 'Онколог', 'Инфекционист', 'Аллерголог-иммунолог',
|
|
19
|
+
'Психиатр', 'Психотерапевт', 'Нарколог', 'Анестезиолог-реаниматолог', 'Рентгенолог',
|
|
20
|
+
'Врач УЗД', 'Эндоскопист', 'Патологоанатом'
|
|
21
|
+
),
|
|
22
|
+
allowNull: true
|
|
23
|
+
},
|
|
24
|
+
isTemporaryPassword: { type: DataTypes.BOOLEAN, defaultValue: false }
|
|
25
|
+
}, {
|
|
26
|
+
timestamps: true
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Настройка связей пользователя
|
|
30
|
+
User.associate = (models) => {
|
|
31
|
+
User.belongsTo(models.Clinic, { foreignKey: 'ClinicId', onDelete: 'SET NULL' });
|
|
32
|
+
User.hasOne(models.UserAddress, { foreignKey: 'UserId' });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return User;
|
|
36
|
+
};
|
package/index.js
CHANGED
|
@@ -5,33 +5,43 @@ const chalk = require("chalk-palette");
|
|
|
5
5
|
const logger = require("custom-color-logs").print;
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const fs = require('fs');
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
// Оставляем только импорт самого модуля базы данных
|
|
10
|
+
const dbConfig = require(path.join(__dirname, "database", "config", "db-init.js"));
|
|
9
11
|
const server = require('./server/server');
|
|
10
|
-
const User = require(path.join(__dirname, "database", "models", "users.js"));
|
|
11
12
|
const bcrypt = require('bcrypt');
|
|
13
|
+
|
|
12
14
|
async function init(app) {
|
|
13
15
|
await chalk.animation.textShimmer("MED-CMS");
|
|
14
16
|
console.log(logger.ServerMedInfo("Запуск сервера..."));
|
|
15
17
|
await chalk.animation.retroLines({duration:4000, linesCount:4, offset:3});
|
|
18
|
+
|
|
16
19
|
if (app && typeof app === 'function' && typeof app.handle === 'function') {
|
|
17
20
|
console.log(logger.ServerMedInfo("Проверка на app пройдена успешно!"));
|
|
18
21
|
} else {
|
|
19
22
|
console.log(logger.ServerMedError("app не передан или повреждён!"));
|
|
20
23
|
return;
|
|
21
24
|
}
|
|
25
|
+
|
|
22
26
|
try {
|
|
23
27
|
const dbPath = path.join(__dirname, "db", "med-cms.sqlite");
|
|
24
28
|
const dbDir = path.dirname(dbPath);
|
|
25
29
|
if (!fs.existsSync(dbDir)){
|
|
26
30
|
fs.mkdirSync(dbDir, { extended: true });
|
|
27
31
|
}
|
|
28
|
-
|
|
32
|
+
|
|
33
|
+
// Инициализируем БД
|
|
34
|
+
await dbConfig.initDatabase(dbPath);
|
|
29
35
|
console.log(logger.ServerMedInfo("База данных успешно инициализирована"));
|
|
30
36
|
} catch (err) {
|
|
31
37
|
console.log(logger.DatabaseError("Критическая ошибка запуска базы данных med-cms:", err));
|
|
32
38
|
return;
|
|
33
39
|
}
|
|
40
|
+
|
|
34
41
|
try {
|
|
42
|
+
// ИСПРАВЛЕНИЕ: Достаем User из проинициализированного объекта базы данных ОНЛАЙН
|
|
43
|
+
const User = dbConfig.User;
|
|
44
|
+
|
|
35
45
|
const adminExists = await User.findOne({ where: { role: 'admin' } });
|
|
36
46
|
if (!adminExists) {
|
|
37
47
|
console.log(logger.ServerMedInfo("Администратор не найден. Создаю аккаунт по умолчанию..."));
|
|
@@ -44,19 +54,20 @@ async function init(app) {
|
|
|
44
54
|
role: "admin",
|
|
45
55
|
specialty: null
|
|
46
56
|
});
|
|
47
|
-
console.log(logger.ServerMedInfo(`Аккаунт суперадмина успешно создан
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
57
|
+
console.log(logger.ServerMedInfo(`Аккаунт суперадмина успешно создан in SQLite!`));
|
|
58
|
+
} else {
|
|
50
59
|
console.log(logger.ServerMedInfo("Администратор уже существует в базе данных."));
|
|
51
60
|
}
|
|
52
61
|
} catch (error) {
|
|
53
62
|
console.log(logger.ServerMedError("Ошибка при автоматическом создании админа: " + error.message));
|
|
54
63
|
}
|
|
64
|
+
|
|
55
65
|
console.log(logger.ServerMedInfo("Подключение роутов..."));
|
|
56
66
|
const rout = express.Router();
|
|
57
67
|
const med = server(process.env.JWT_SECRET);
|
|
58
68
|
const r_api = med.api;
|
|
59
69
|
const r_web = med.web;
|
|
70
|
+
|
|
60
71
|
rout.use(cookieParser());
|
|
61
72
|
rout.use(express.json());
|
|
62
73
|
rout.use(express.urlencoded({ extended: true }));
|
|
@@ -64,4 +75,5 @@ async function init(app) {
|
|
|
64
75
|
rout.use("/med/api", r_api);
|
|
65
76
|
app.use("/", rout);
|
|
66
77
|
}
|
|
67
|
-
|
|
78
|
+
|
|
79
|
+
module.exports = init;
|
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -2,18 +2,26 @@ const express = require('express');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const bcrypt = require('bcrypt');
|
|
4
4
|
const jwt = require('jsonwebtoken');
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
// Импортируем проинициализированный объект базы со всеми готовыми моделями
|
|
7
|
+
const db = require(path.join(__dirname, '..', 'database', 'config', 'db-init.js'));
|
|
8
|
+
|
|
7
9
|
const createMiddleware = require(path.join(__dirname, 'midleware'));
|
|
8
10
|
const { MED_PERMISSIONS } = require(path.join(__dirname, 'permissions'));
|
|
9
11
|
const AdminService = require(path.join(__dirname, '..', 'database', 'services', 'admin'));
|
|
10
12
|
const DoctorService = require(path.join(__dirname, '..', 'database', 'services', 'doctor'));
|
|
11
13
|
const PatientService = require(path.join(__dirname, '..', 'database', 'services', 'patient'));
|
|
14
|
+
|
|
12
15
|
function createMedRouter(jwtSecret) {
|
|
13
16
|
const api = express.Router();
|
|
14
17
|
const web = express.Router();
|
|
18
|
+
|
|
19
|
+
// Достаем готовые к работе модели из объекта db
|
|
20
|
+
const { User, Disease, Clinic } = db;
|
|
21
|
+
|
|
15
22
|
web.use("/", express.static(path.join(__dirname, "..", "public")));
|
|
16
23
|
const { verifyCookie, requireRole, checkPermission } = createMiddleware(jwtSecret);
|
|
24
|
+
|
|
17
25
|
api.post('/auth/register', async (req, res) => {
|
|
18
26
|
try {
|
|
19
27
|
const { email, password, fullName } = req.body;
|
|
@@ -27,6 +35,7 @@ function createMedRouter(jwtSecret) {
|
|
|
27
35
|
res.status(400).json({ success: false, error: err.message });
|
|
28
36
|
}
|
|
29
37
|
});
|
|
38
|
+
|
|
30
39
|
api.post('/auth/login', async (req, res) => {
|
|
31
40
|
try {
|
|
32
41
|
const { email, password } = req.body;
|
|
@@ -50,13 +59,16 @@ function createMedRouter(jwtSecret) {
|
|
|
50
59
|
res.status(500).json({ success: false, error: err.message });
|
|
51
60
|
}
|
|
52
61
|
});
|
|
62
|
+
|
|
53
63
|
api.post('/auth/logout', (req, res) => {
|
|
54
64
|
res.clearCookie('med_auth_token');
|
|
55
65
|
res.json({ success: true, message: 'Успешный выход' });
|
|
56
66
|
});
|
|
67
|
+
|
|
57
68
|
web.get('/med-patient', verifyCookie, requireRole(['patient']), (req, res) => {
|
|
58
69
|
res.sendFile(path.join(__dirname, '..', 'public', 'html', 'dashboard-patient.html'));
|
|
59
70
|
});
|
|
71
|
+
|
|
60
72
|
api.get('/patient/me', verifyCookie, async (req, res) => {
|
|
61
73
|
try {
|
|
62
74
|
const fullData = await PatientService.getFullMyData(req.user.id);
|
|
@@ -65,6 +77,7 @@ function createMedRouter(jwtSecret) {
|
|
|
65
77
|
res.status(500).json({ success: false, error: err.message });
|
|
66
78
|
}
|
|
67
79
|
});
|
|
80
|
+
|
|
68
81
|
api.post('/patient/address', verifyCookie, async (req, res) => {
|
|
69
82
|
try {
|
|
70
83
|
const updatedAddress = await PatientService.savePatientAddress(req.user.id, req.body);
|
|
@@ -73,9 +86,11 @@ function createMedRouter(jwtSecret) {
|
|
|
73
86
|
res.status(400).json({ success: false, error: err.message });
|
|
74
87
|
}
|
|
75
88
|
});
|
|
89
|
+
|
|
76
90
|
web.get('/med-doctor', verifyCookie, requireRole(['doctor']), (req, res) => {
|
|
77
91
|
res.sendFile(path.join(__dirname, '..', 'public', 'html', 'dashboard-doctor.html'));
|
|
78
92
|
});
|
|
93
|
+
|
|
79
94
|
api.get('/doctor/schedule', verifyCookie, requireRole(['doctor']), async (req, res) => {
|
|
80
95
|
try {
|
|
81
96
|
const schedule = await DoctorService.getMySchedule(req.user.id);
|
|
@@ -84,6 +99,7 @@ function createMedRouter(jwtSecret) {
|
|
|
84
99
|
res.status(500).json({ success: false, error: err.message });
|
|
85
100
|
}
|
|
86
101
|
});
|
|
102
|
+
|
|
87
103
|
api.get('/doctor/patient-history/:id', verifyCookie, requireRole(['doctor']), async (req, res) => {
|
|
88
104
|
try {
|
|
89
105
|
const history = await DoctorService.getPatientHistory(req.params.id);
|
|
@@ -92,6 +108,7 @@ function createMedRouter(jwtSecret) {
|
|
|
92
108
|
res.status(404).json({ success: false, error: err.message });
|
|
93
109
|
}
|
|
94
110
|
});
|
|
111
|
+
|
|
95
112
|
api.post('/doctor/complete-examination', verifyCookie, requireRole(['doctor']), async (req, res) => {
|
|
96
113
|
try {
|
|
97
114
|
const result = await DoctorService.saveExamination(req.body);
|
|
@@ -100,12 +117,15 @@ function createMedRouter(jwtSecret) {
|
|
|
100
117
|
res.status(400).json({ success: false, error: err.message });
|
|
101
118
|
}
|
|
102
119
|
});
|
|
120
|
+
|
|
103
121
|
api.post('/doctor/schedule-surgery', verifyCookie, requireRole(['doctor']), checkPermission(MED_PERMISSIONS.SCHEDULE_SURGERY), (req, res) => {
|
|
104
122
|
res.json({ success: true, message: 'Операция успешно добавлена в график хирургического отделения.' });
|
|
105
123
|
});
|
|
124
|
+
|
|
106
125
|
web.get('/med-admin', verifyCookie, requireRole(['admin']), (req, res) => {
|
|
107
126
|
res.sendFile(path.join(__dirname, '..', 'public', 'html', 'dashboard-admin.html'));
|
|
108
127
|
});
|
|
128
|
+
|
|
109
129
|
api.post('/admin/clinics', verifyCookie, requireRole(['admin']), async (req, res) => {
|
|
110
130
|
try {
|
|
111
131
|
const newClinic = await AdminService.createClinic(req.body);
|
|
@@ -114,18 +134,19 @@ function createMedRouter(jwtSecret) {
|
|
|
114
134
|
res.status(400).json({ success: false, error: err.message });
|
|
115
135
|
}
|
|
116
136
|
});
|
|
137
|
+
|
|
117
138
|
api.post('/admin/staff', verifyCookie, requireRole(['admin']), async (req, res) => {
|
|
118
139
|
try {
|
|
119
140
|
if (req.body.clinicId && !req.body.ClinicId) {
|
|
120
141
|
req.body.ClinicId = req.body.clinicId;
|
|
121
142
|
}
|
|
122
|
-
|
|
123
143
|
const staffUser = await AdminService.createStaffAccount(req.body);
|
|
124
144
|
res.status(201).json({ success: true, data: staffUser });
|
|
125
145
|
} catch (err) {
|
|
126
146
|
res.status(400).json({ success: false, error: err.message });
|
|
127
147
|
}
|
|
128
148
|
});
|
|
149
|
+
|
|
129
150
|
api.delete('/admin/users/:id', verifyCookie, requireRole(['admin']), async (req, res) => {
|
|
130
151
|
try {
|
|
131
152
|
const result = await AdminService.deleteAccount(req.params.id);
|
|
@@ -134,6 +155,7 @@ function createMedRouter(jwtSecret) {
|
|
|
134
155
|
res.status(400).json({ success: false, error: err.message });
|
|
135
156
|
}
|
|
136
157
|
});
|
|
158
|
+
|
|
137
159
|
api.get('/admin/stats', verifyCookie, requireRole(['admin']), async (req, res) => {
|
|
138
160
|
try {
|
|
139
161
|
const stats = await AdminService.getSystemStats();
|
|
@@ -142,35 +164,33 @@ function createMedRouter(jwtSecret) {
|
|
|
142
164
|
res.status(500).json({ success: false, error: err.message });
|
|
143
165
|
}
|
|
144
166
|
});
|
|
167
|
+
|
|
145
168
|
api.get('/admin/list-clinics', verifyCookie, requireRole(['admin']), async (req, res) => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
+
try {
|
|
170
|
+
// ИСПРАВЛЕНИЕ: берем Clinic из инстанса базы данных, а не сырой файл функции
|
|
171
|
+
const clinics = await Clinic.findAll({ order: [['name', 'ASC']] });
|
|
172
|
+
res.json({ success: true, data: clinics });
|
|
173
|
+
} catch (err) {
|
|
174
|
+
res.status(500).json({ success: false, error: err.message });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
api.get('/admin/list-staff', verifyCookie, requireRole(['admin']), async (req, res) => {
|
|
179
|
+
try {
|
|
180
|
+
// ИСПРАВЛЕНИЕ: Используем проинициализированные модули для include отношений
|
|
181
|
+
const staff = await User.findAll({
|
|
182
|
+
where: { role: 'doctor' },
|
|
183
|
+
include: [{ model: Clinic, attributes: ['name'] }],
|
|
184
|
+
order: [['fullName', 'ASC']]
|
|
185
|
+
});
|
|
186
|
+
res.json({ success: true, data: staff });
|
|
187
|
+
} catch (err) {
|
|
188
|
+
res.status(500).json({ success: false, error: err.message });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
169
192
|
api.get('/patient/doctors-list', verifyCookie, requireRole(['patient']), async (req, res) => {
|
|
170
193
|
try {
|
|
171
|
-
const User = require('../database/models/users');
|
|
172
|
-
const Clinic = require('../database/models/clinics');
|
|
173
|
-
|
|
174
194
|
const doctors = await User.findAll({
|
|
175
195
|
where: { role: 'doctor' },
|
|
176
196
|
attributes: ['id', 'fullName', 'specialty'],
|
|
@@ -182,52 +202,45 @@ api.get('/admin/list-staff', verifyCookie, requireRole(['admin']), async (req, r
|
|
|
182
202
|
}
|
|
183
203
|
});
|
|
184
204
|
|
|
185
|
-
// 2. Создать новую запись на прием (Бронирование талона)
|
|
186
205
|
api.post('/patient/book', verifyCookie, requireRole(['patient']), async (req, res) => {
|
|
187
206
|
try {
|
|
188
|
-
// ID пациента безопасно берем из расшифрованной HttpOnly-куки (req.user.id)
|
|
189
207
|
const patientId = req.user.id;
|
|
190
|
-
|
|
191
208
|
const appointment = await PatientService.bookAppointment(patientId, req.body);
|
|
192
209
|
res.status(212).json({ success: true, data: appointment });
|
|
193
210
|
} catch (err) {
|
|
194
211
|
res.status(400).json({ success: false, error: err.message });
|
|
195
212
|
}
|
|
196
213
|
});
|
|
197
|
-
|
|
214
|
+
|
|
198
215
|
api.get('/doctor/diseases-list', verifyCookie, requireRole(['doctor', 'admin']), async (req, res) => {
|
|
199
216
|
try {
|
|
200
|
-
|
|
217
|
+
// ИСПРАВЛЕНИЕ: Используем готовую модель Disease
|
|
201
218
|
const diseases = await Disease.findAll({ order: [['name', 'ASC']] });
|
|
202
219
|
res.json({ success: true, data: diseases });
|
|
203
220
|
} catch (err) {
|
|
204
221
|
res.status(500).json({ success: false, error: err.message });
|
|
205
222
|
}
|
|
206
223
|
});
|
|
207
|
-
|
|
224
|
+
|
|
208
225
|
api.post('/auth/change-password', verifyCookie, async (req, res) => {
|
|
209
226
|
try {
|
|
210
227
|
const { oldPassword, newPassword } = req.body;
|
|
211
|
-
const userId = req.user.id;
|
|
228
|
+
const userId = req.user.id;
|
|
212
229
|
|
|
213
230
|
if (!oldPassword || !newPassword) {
|
|
214
231
|
throw new Error('Необходимо указать старый и новый пароли');
|
|
215
232
|
}
|
|
216
233
|
|
|
217
|
-
// Ищем пользователя в базе
|
|
218
234
|
const user = await User.findByPk(userId);
|
|
219
235
|
if (!user) throw new Error('Пользователь не найден');
|
|
220
236
|
|
|
221
|
-
// Сверяем старый пароль с хешем в БД
|
|
222
237
|
const isMatch = await bcrypt.compare(oldPassword, user.password);
|
|
223
238
|
if (!isMatch) {
|
|
224
239
|
return res.status(400).json({ success: false, message: 'Старый пароль введен неверно' });
|
|
225
240
|
}
|
|
226
241
|
|
|
227
|
-
// Хешируем новый пароль перед записью в SQLite
|
|
228
242
|
const hashedNewPassword = await bcrypt.hash(newPassword, 10);
|
|
229
243
|
|
|
230
|
-
// Обновляем пароль и снимаем флаг временного пароля
|
|
231
244
|
await user.update({
|
|
232
245
|
password: hashedNewPassword,
|
|
233
246
|
isTemporaryPassword: false
|
|
@@ -238,18 +251,14 @@ api.get('/admin/list-staff', verifyCookie, requireRole(['admin']), async (req, r
|
|
|
238
251
|
}
|
|
239
252
|
});
|
|
240
253
|
|
|
241
|
-
// Обновим также роут выгрузки расписания, чтобы он отдавал данные о вошедшем враче для шапки кабинета
|
|
242
254
|
api.get('/doctor/me', verifyCookie, requireRole(['doctor']), async (req, res) => {
|
|
243
255
|
try {
|
|
244
256
|
const doctorData = await User.findByPk(req.user.id, { attributes: ['fullName', 'position', 'specialty'] });
|
|
245
257
|
res.json({ success: true, doctor: doctorData });
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return {web:web, api:api};
|
|
258
|
+
} catch (err) {
|
|
259
|
+
res.status(500).json({ success: false, error: err.message });
|
|
253
260
|
}
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
});
|
|
262
|
+
return {web:web, api:api};
|
|
263
|
+
}
|
|
264
|
+
module.exports = createMedRouter;
|