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