feat: полный бэк и фронт (попытки, разбор, импорт, ИИ, назначения)
- Сервисы: testAttemptService, testAccess, document import/gen/extract, LLM, assignment, aiEditor - Конфиг: devAuthor, featureFlags; messages/ru; интеграция V.9 (skip без БД) - API/роуты: app, auth, server; Dockerfile и env example - Фронт: TestAttempt, TestAttemptReview, AttemptReviewBlock, стили, правки App/api/login/vite - compose и README; смоук-тесты расширены Закрывает отсутствие модулей в origin после клона. Made-with: Cursor
This commit is contained in:
+49
-16
@@ -11,6 +11,12 @@ import {
|
||||
isHrAuthEnabled,
|
||||
HR_MANAGED_PASSWORD_PLACEHOLDER,
|
||||
} from '../config/authConstants.js';
|
||||
import { RU } from '../messages/ru.js';
|
||||
import {
|
||||
getAssignmentDirectory,
|
||||
getHrDepartmentNames,
|
||||
} from '../services/assignmentDirectoryService.js';
|
||||
import { isAssignmentFeatureEnabled } from '../config/featureFlags.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -18,12 +24,12 @@ router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { login, password } = req.body;
|
||||
if (!login || !password) {
|
||||
return res.status(400).json({ error: 'Login and password are required' });
|
||||
return res.status(400).json({ error: RU.loginAndPasswordRequired });
|
||||
}
|
||||
|
||||
if (isHrAuthEnabled()) {
|
||||
if (!getHrPool()) {
|
||||
return res.status(500).json({ error: 'HR_DATABASE_URL is not set' });
|
||||
return res.status(500).json({ error: RU.hrDatabaseUrlMissing });
|
||||
}
|
||||
const u = await queryHr(
|
||||
`SELECT id, username, password_hash, role
|
||||
@@ -32,12 +38,12 @@ router.post('/login', async (req, res) => {
|
||||
[login]
|
||||
);
|
||||
if (u.rows.length === 0 || !u.rows[0].password_hash) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
return res.status(401).json({ error: RU.invalidCredentials });
|
||||
}
|
||||
const row = u.rows[0];
|
||||
const ok = await comparePassword(password, row.password_hash);
|
||||
if (!ok) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
return res.status(401).json({ error: RU.invalidCredentials });
|
||||
}
|
||||
const s = await queryHr(
|
||||
`SELECT id, fio FROM staff_members
|
||||
@@ -45,9 +51,7 @@ router.post('/login', async (req, res) => {
|
||||
[login]
|
||||
);
|
||||
if (s.rows.length === 0) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ error: 'No staff link for this login (web_login)' });
|
||||
return res.status(403).json({ error: RU.noStaffForLogin });
|
||||
}
|
||||
const staffId = s.rows[0].id;
|
||||
const fio = s.rows[0].fio || login;
|
||||
@@ -93,15 +97,15 @@ router.post('/login', async (req, res) => {
|
||||
[login]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
return res.status(401).json({ error: RU.invalidCredentials });
|
||||
}
|
||||
const user = result.rows[0];
|
||||
if (user.password_hash === HR_MANAGED_PASSWORD_PLACEHOLDER) {
|
||||
return res.status(401).json({ error: 'Use HR login' });
|
||||
return res.status(401).json({ error: RU.useHrLogin });
|
||||
}
|
||||
const isValidPassword = await comparePassword(password, user.password_hash);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
return res.status(401).json({ error: RU.invalidCredentials });
|
||||
}
|
||||
const token = generateToken(user.id, user.role, user.department_id);
|
||||
res.cookie('token', token, {
|
||||
@@ -122,10 +126,10 @@ router.post('/login', async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message?.includes('HR database not configured')) {
|
||||
return res.status(500).json({ error: 'HR database not configured' });
|
||||
return res.status(500).json({ error: RU.hrDatabaseNotConfigured });
|
||||
}
|
||||
console.error('Login error:', error);
|
||||
return res.status(500).json({ error: 'Login failed' });
|
||||
return res.status(500).json({ error: RU.loginFailed });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -136,19 +140,48 @@ router.post('/logout', (req, res) => {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'strict',
|
||||
});
|
||||
res.json({ message: 'Logged out successfully' });
|
||||
res.json({ message: RU.loggedOut });
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
res.status(500).json({ error: 'Logout failed' });
|
||||
res.status(500).json({ error: RU.logoutFailed });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/me', authenticate, async (req, res) => {
|
||||
try {
|
||||
res.json({ user: req.user });
|
||||
const devUi = process.env.NODE_ENV === 'development';
|
||||
const assignmentUi = isAssignmentFeatureEnabled();
|
||||
res.json({ user: req.user, devUi, assignmentUi });
|
||||
} catch (error) {
|
||||
console.error('Get current user error:', error);
|
||||
res.status(500).json({ error: 'Failed to get user data' });
|
||||
res.status(500).json({ error: RU.userDataFailed });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Каталог сотрудников для назначения: HR (все) + отделы + поиск. Как `POST .../assign`: см. `isAssignmentFeatureEnabled()`.
|
||||
* Query: q, department (имя отдела или __all__), clinic=all|with|without
|
||||
*/
|
||||
router.get('/dev/assignment-directory', authenticate, async (req, res) => {
|
||||
if (!isAssignmentFeatureEnabled()) {
|
||||
return res.status(404).json({ error: RU.notFound });
|
||||
}
|
||||
try {
|
||||
const q = typeof req.query.q === 'string' ? req.query.q : '';
|
||||
const department = typeof req.query.department === 'string' ? req.query.department : '';
|
||||
const c = req.query.clinic;
|
||||
const clinicFilter =
|
||||
c === 'with' || c === 'without' ? c : 'all';
|
||||
const { people, source } = await getAssignmentDirectory({
|
||||
q,
|
||||
department,
|
||||
clinicFilter,
|
||||
});
|
||||
const departments = await getHrDepartmentNames();
|
||||
res.json({ people, source, departments });
|
||||
} catch (error) {
|
||||
console.error('dev assignment directory:', error);
|
||||
res.status(500).json({ error: RU.userDataFailed });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user