med-cms 0.0.1 → 1.0.0-beta.1

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.
Files changed (39) hide show
  1. package/README +161 -0
  2. package/database/config/db-init.js +44 -0
  3. package/database/models/appointments.js +39 -0
  4. package/database/models/clinics.js +18 -0
  5. package/database/models/diseases.js +37 -0
  6. package/database/models/user-addresses.js +34 -0
  7. package/database/models/users.js +31 -0
  8. package/database/services/admin/create-clinic.js +19 -0
  9. package/database/services/admin/db-analytics.js +34 -0
  10. package/database/services/admin/index.js +12 -0
  11. package/database/services/admin/manage-staff.js +41 -0
  12. package/database/services/doctor/conduct-exam.js +33 -0
  13. package/database/services/doctor/get-schedule.js +23 -0
  14. package/database/services/doctor/index.js +10 -0
  15. package/database/services/doctor/patient-access.js +34 -0
  16. package/database/services/patient/book-appointment.js +45 -0
  17. package/database/services/patient/calc-district.js +49 -0
  18. package/database/services/patient/get-profile.js +47 -0
  19. package/database/services/patient/index.js +10 -0
  20. package/index.d.ts +25 -0
  21. package/index.js +67 -0
  22. package/migrate-diseases.js +134 -0
  23. package/package.json +15 -5
  24. package/public/css/admin.css +129 -0
  25. package/public/css/auth.css +55 -0
  26. package/public/css/doctor.css +204 -0
  27. package/public/html/dashboard-admin.html +112 -0
  28. package/public/html/dashboard-doctor.html +78 -0
  29. package/public/html/dashboard-patient.html +113 -0
  30. package/public/html/home.html +0 -0
  31. package/public/html/login.html +48 -0
  32. package/public/js/admin.js +199 -0
  33. package/public/js/auth.js +55 -0
  34. package/public/js/doctor.js +137 -0
  35. package/public/js/patient.js +141 -0
  36. package/server/midleware.js +65 -0
  37. package/server/permissions.js +47 -0
  38. package/server/server.js +255 -0
  39. package/test.js +27 -0
@@ -0,0 +1,255 @@
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const bcrypt = require('bcrypt');
4
+ const jwt = require('jsonwebtoken');
5
+ const User = require(path.join(__dirname, '..', 'database', 'models', 'users'));
6
+ const Disease = require(path.join(__dirname, '..', 'database', 'models', 'diseases'));
7
+ const createMiddleware = require(path.join(__dirname, 'midleware'));
8
+ const { MED_PERMISSIONS } = require(path.join(__dirname, 'permissions'));
9
+ const AdminService = require(path.join(__dirname, '..', 'database', 'services', 'admin'));
10
+ const DoctorService = require(path.join(__dirname, '..', 'database', 'services', 'doctor'));
11
+ const PatientService = require(path.join(__dirname, '..', 'database', 'services', 'patient'));
12
+ function createMedRouter(jwtSecret) {
13
+ const api = express.Router();
14
+ const web = express.Router();
15
+ web.use("/", express.static(path.join(__dirname, "..", "public")));
16
+ const { verifyCookie, requireRole, checkPermission } = createMiddleware(jwtSecret);
17
+ api.post('/auth/register', async (req, res) => {
18
+ try {
19
+ const { email, password, fullName } = req.body;
20
+ if (!email || !password || !fullName) throw new Error('Заполните обязательные поля: email, password, fullName');
21
+ const candidate = await User.findOne({ where: { email } });
22
+ if (candidate) return res.status(400).json({ success: false, message: 'Email уже занят' });
23
+ const hashedPassword = await bcrypt.hash(password, 10);
24
+ await User.create({ email, password: hashedPassword, fullName, role: 'patient' });
25
+ res.status(201).json({ success: true, message: 'Регистрация успешна' });
26
+ } catch (err) {
27
+ res.status(400).json({ success: false, error: err.message });
28
+ }
29
+ });
30
+ api.post('/auth/login', async (req, res) => {
31
+ try {
32
+ const { email, password } = req.body;
33
+ const user = await User.findOne({ where: { email } });
34
+ if (!user || !(await bcrypt.compare(password, user.password))) {
35
+ return res.status(400).json({ success: false, message: 'Неверный логин или пароль' });
36
+ }
37
+ const token = jwt.sign(
38
+ { id: user.id, role: user.role, specialty: user.specialty },
39
+ jwtSecret,
40
+ { expiresIn: '7d' }
41
+ );
42
+ res.cookie('med_auth_token', token, {
43
+ httpOnly: true,
44
+ secure: process.env.NODE_ENV === 'production',
45
+ maxAge: 7 * 24 * 60 * 60 * 1000,
46
+ sameSite: 'lax'
47
+ });
48
+ res.json({ success: true, user: { id: user.id, fullName: user.fullName, role: user.role } });
49
+ } catch (err) {
50
+ res.status(500).json({ success: false, error: err.message });
51
+ }
52
+ });
53
+ api.post('/auth/logout', (req, res) => {
54
+ res.clearCookie('med_auth_token');
55
+ res.json({ success: true, message: 'Успешный выход' });
56
+ });
57
+ web.get('/med-patient', verifyCookie, requireRole(['patient']), (req, res) => {
58
+ res.sendFile(path.join(__dirname, '..', 'public', 'html', 'dashboard-patient.html'));
59
+ });
60
+ api.get('/patient/me', verifyCookie, async (req, res) => {
61
+ try {
62
+ const fullData = await PatientService.getFullMyData(req.user.id);
63
+ res.json({ success: true, data: fullData });
64
+ } catch (err) {
65
+ res.status(500).json({ success: false, error: err.message });
66
+ }
67
+ });
68
+ api.post('/patient/address', verifyCookie, async (req, res) => {
69
+ try {
70
+ const updatedAddress = await PatientService.savePatientAddress(req.user.id, req.body);
71
+ res.json({ success: true, address: updatedAddress });
72
+ } catch (err) {
73
+ res.status(400).json({ success: false, error: err.message });
74
+ }
75
+ });
76
+ web.get('/med-doctor', verifyCookie, requireRole(['doctor']), (req, res) => {
77
+ res.sendFile(path.join(__dirname, '..', 'public', 'html', 'dashboard-doctor.html'));
78
+ });
79
+ api.get('/doctor/schedule', verifyCookie, requireRole(['doctor']), async (req, res) => {
80
+ try {
81
+ const schedule = await DoctorService.getMySchedule(req.user.id);
82
+ res.json({ success: true, schedule });
83
+ } catch (err) {
84
+ res.status(500).json({ success: false, error: err.message });
85
+ }
86
+ });
87
+ api.get('/doctor/patient-history/:id', verifyCookie, requireRole(['doctor']), async (req, res) => {
88
+ try {
89
+ const history = await DoctorService.getPatientHistory(req.params.id);
90
+ res.json({ success: true, history });
91
+ } catch (err) {
92
+ res.status(404).json({ success: false, error: err.message });
93
+ }
94
+ });
95
+ api.post('/doctor/complete-examination', verifyCookie, requireRole(['doctor']), async (req, res) => {
96
+ try {
97
+ const result = await DoctorService.saveExamination(req.body);
98
+ res.json({ success: true, appointment: result });
99
+ } catch (err) {
100
+ res.status(400).json({ success: false, error: err.message });
101
+ }
102
+ });
103
+ api.post('/doctor/schedule-surgery', verifyCookie, requireRole(['doctor']), checkPermission(MED_PERMISSIONS.SCHEDULE_SURGERY), (req, res) => {
104
+ res.json({ success: true, message: 'Операция успешно добавлена в график хирургического отделения.' });
105
+ });
106
+ web.get('/med-admin', verifyCookie, requireRole(['admin']), (req, res) => {
107
+ res.sendFile(path.join(__dirname, '..', 'public', 'html', 'dashboard-admin.html'));
108
+ });
109
+ api.post('/admin/clinics', verifyCookie, requireRole(['admin']), async (req, res) => {
110
+ try {
111
+ const newClinic = await AdminService.createClinic(req.body);
112
+ res.status(201).json({ success: true, data: newClinic });
113
+ } catch (err) {
114
+ res.status(400).json({ success: false, error: err.message });
115
+ }
116
+ });
117
+ api.post('/admin/staff', verifyCookie, requireRole(['admin']), async (req, res) => {
118
+ try {
119
+ if (req.body.clinicId && !req.body.ClinicId) {
120
+ req.body.ClinicId = req.body.clinicId;
121
+ }
122
+
123
+ const staffUser = await AdminService.createStaffAccount(req.body);
124
+ res.status(201).json({ success: true, data: staffUser });
125
+ } catch (err) {
126
+ res.status(400).json({ success: false, error: err.message });
127
+ }
128
+ });
129
+ api.delete('/admin/users/:id', verifyCookie, requireRole(['admin']), async (req, res) => {
130
+ try {
131
+ const result = await AdminService.deleteAccount(req.params.id);
132
+ res.json(result);
133
+ } catch (err) {
134
+ res.status(400).json({ success: false, error: err.message });
135
+ }
136
+ });
137
+ api.get('/admin/stats', verifyCookie, requireRole(['admin']), async (req, res) => {
138
+ try {
139
+ const stats = await AdminService.getSystemStats();
140
+ res.json({ success: true, stats });
141
+ } catch (err) {
142
+ res.status(500).json({ success: false, error: err.message });
143
+ }
144
+ });
145
+ api.get('/admin/list-clinics', verifyCookie, requireRole(['admin']), async (req, res) => {
146
+ try {
147
+ const Clinic = require('../database/models/clinics');
148
+ const clinics = await Clinic.findAll({ order: [['name', 'ASC']] });
149
+ res.json({ success: true, data: clinics });
150
+ } catch (err) {
151
+ res.status(500).json({ success: false, error: err.message });
152
+ }
153
+ });
154
+ api.get('/admin/list-staff', verifyCookie, requireRole(['admin']), async (req, res) => {
155
+ try {
156
+ const User = require('../database/models/users');
157
+ const Clinic = require('../database/models/clinics');
158
+ const staff = await User.findAll({
159
+ where: { role: 'doctor' },
160
+ include: [{ model: Clinic, attributes: ['name'] }],
161
+ order: [['fullName', 'ASC']]
162
+ });
163
+ res.json({ success: true, data: staff });
164
+ } catch (err) {
165
+ res.status(500).json({ success: false, error: err.message });
166
+ }
167
+ });
168
+ // 1. Получить список всех доступных врачей для записи (Публично для пациентов)
169
+ api.get('/patient/doctors-list', verifyCookie, requireRole(['patient']), async (req, res) => {
170
+ try {
171
+ const User = require('../database/models/users');
172
+ const Clinic = require('../database/models/clinics');
173
+
174
+ const doctors = await User.findAll({
175
+ where: { role: 'doctor' },
176
+ attributes: ['id', 'fullName', 'specialty'],
177
+ include: [{ model: Clinic, attributes: ['name', 'address'] }]
178
+ });
179
+ res.json({ success: true, data: doctors });
180
+ } catch (err) {
181
+ res.status(500).json({ success: false, error: err.message });
182
+ }
183
+ });
184
+
185
+ // 2. Создать новую запись на прием (Бронирование талона)
186
+ api.post('/patient/book', verifyCookie, requireRole(['patient']), async (req, res) => {
187
+ try {
188
+ // ID пациента безопасно берем из расшифрованной HttpOnly-куки (req.user.id)
189
+ const patientId = req.user.id;
190
+
191
+ const appointment = await PatientService.bookAppointment(patientId, req.body);
192
+ res.status(212).json({ success: true, data: appointment });
193
+ } catch (err) {
194
+ res.status(400).json({ success: false, error: err.message });
195
+ }
196
+ });
197
+ // ИСПРАВЛЕНИЕ: Добавляем 'admin' в requireRole, чтобы при тесте под админом справочник подгружался!
198
+ api.get('/doctor/diseases-list', verifyCookie, requireRole(['doctor', 'admin']), async (req, res) => {
199
+ try {
200
+ const Disease = require('../database/models/diseases');
201
+ const diseases = await Disease.findAll({ order: [['name', 'ASC']] });
202
+ res.json({ success: true, data: diseases });
203
+ } catch (err) {
204
+ res.status(500).json({ success: false, error: err.message });
205
+ }
206
+ });
207
+ // API Смены пароля для любого авторизованного пользователя (Врач, Пациент, Админ)
208
+ api.post('/auth/change-password', verifyCookie, async (req, res) => {
209
+ try {
210
+ const { oldPassword, newPassword } = req.body;
211
+ const userId = req.user.id; // Берем ID строго из HttpOnly куки
212
+
213
+ if (!oldPassword || !newPassword) {
214
+ throw new Error('Необходимо указать старый и новый пароли');
215
+ }
216
+
217
+ // Ищем пользователя в базе
218
+ const user = await User.findByPk(userId);
219
+ if (!user) throw new Error('Пользователь не найден');
220
+
221
+ // Сверяем старый пароль с хешем в БД
222
+ const isMatch = await bcrypt.compare(oldPassword, user.password);
223
+ if (!isMatch) {
224
+ return res.status(400).json({ success: false, message: 'Старый пароль введен неверно' });
225
+ }
226
+
227
+ // Хешируем новый пароль перед записью в SQLite
228
+ const hashedNewPassword = await bcrypt.hash(newPassword, 10);
229
+
230
+ // Обновляем пароль и снимаем флаг временного пароля
231
+ await user.update({
232
+ password: hashedNewPassword,
233
+ isTemporaryPassword: false
234
+ });
235
+ res.json({ success: true, message: 'Пароль успешно изменен' });
236
+ } catch (err) {
237
+ res.status(400).json({ success: false, error: err.message });
238
+ }
239
+ });
240
+
241
+ // Обновим также роут выгрузки расписания, чтобы он отдавал данные о вошедшем враче для шапки кабинета
242
+ api.get('/doctor/me', verifyCookie, requireRole(['doctor']), async (req, res) => {
243
+ try {
244
+ const doctorData = await User.findByPk(req.user.id, { attributes: ['fullName', 'position', 'specialty'] });
245
+ res.json({ success: true, doctor: doctorData });
246
+ } catch (err) {
247
+ res.status(500).json({ success: false, error: err.message });
248
+ }
249
+ });
250
+
251
+
252
+ return {web:web, api:api};
253
+ }
254
+
255
+ module.exports = createMedRouter;
package/test.js ADDED
@@ -0,0 +1,27 @@
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const initMedCms = require('./index.js'); // Импортируем наш модуль через главную точку входа
4
+ const chalk = require("chalk-palette");
5
+ const app = express();
6
+ const PORT = process.env.PORT || 3000;
7
+
8
+ (async()=>{
9
+ await initMedCms(app);
10
+ app.use((req, res) => {
11
+ res.status(404).send('<h1>404 | Медицинский модуль: Страница не найдена</h1>');
12
+ });
13
+ app.listen(PORT, () => {
14
+ console.log('\n==================================================');
15
+ console.log(`🚀 ТЕСТОВЫЙ СЕРВЕР MED-CMS УСПЕШНО ЗАПУЩЕН!`);
16
+ console.log(chalk.url(`🔗 Откройте в браузере: http://localhost:${PORT}`));
17
+ console.log('==================================================\n');
18
+ console.log('📝 ИНСТРУКЦИЯ ДЛЯ ТЕСТИРОВАНИЯ:');
19
+ console.log('1. Перейдите по ссылке, откроется страница входа.');
20
+ console.log('2. Нажмите "Зарегистрироваться как пациент" и создайте тестовый аккаунт.');
21
+ console.log('3. Проверьте в консоли разработчика (F12 -> Application -> Cookies), что кука med_auth_token установилась.');
22
+ console.log('4. Чтобы зайти под Админом или Врачом, вы можете использовать созданный файл test-database.sqlite.\n');
23
+ });
24
+ })()
25
+
26
+
27
+