Phase 3: Gitea Integration - Migration 012: app_settings table + gitea_commit_sha on endpoint_versions - SettingsService: encrypted token storage (AES-256-GCM from JWT_SECRET) - GiteaService: full REST API client — repo management, file CRUD, branches, commit history, diff/compare, pull requests, sync all - giteaController + routes: 15 API endpoints for settings, branches, commits, PRs, endpoint info, sync - VersionService hooks: auto-sync to Gitea on publish/draft (non-blocking) - Frontend: Gitea tab in Settings (connection, sync status, branch mgmt), Gitea panel in EndpointEditor (file link, last commit SHA) - docker-compose.gitea.yml: optional companion Gitea container - Cache fix: index.html served with no-cache for instant deploys ESLint Setup - Backend: eslint 8 + @typescript-eslint configured - Frontend: eslint 8 + @typescript-eslint + react-hooks + react-refresh - Fixed 15 lint issues: unused imports, require statements, escapes, Function type, empty catch blocks - Added npm run lint / lint:fix scripts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
import { Request, Response } from 'express';
|
||
import bcrypt from 'bcrypt';
|
||
import jwt from 'jsonwebtoken';
|
||
import { mainPool } from '../config/database';
|
||
import { config } from '../config/environment';
|
||
|
||
export const login = async (req: Request, res: Response) => {
|
||
try {
|
||
const { username, password } = req.body;
|
||
|
||
if (!username || !password) {
|
||
return res.status(400).json({ error: 'Не заполнены обязательные поля' });
|
||
}
|
||
|
||
// Find user
|
||
const result = await mainPool.query(
|
||
'SELECT id, username, password_hash, role, is_superadmin FROM users WHERE username = $1',
|
||
[username]
|
||
);
|
||
|
||
if (result.rows.length === 0) {
|
||
return res.status(401).json({ error: 'Неверные учетные данные' });
|
||
}
|
||
|
||
const user = result.rows[0];
|
||
|
||
// Verify password
|
||
const isValidPassword = await bcrypt.compare(password, user.password_hash);
|
||
|
||
if (!isValidPassword) {
|
||
return res.status(401).json({ error: 'Неверные учетные данные' });
|
||
}
|
||
|
||
// Generate token
|
||
const token = jwt.sign(
|
||
{ userId: user.id },
|
||
config.jwt.secret,
|
||
{ expiresIn: config.jwt.expiresIn as any }
|
||
);
|
||
|
||
res.json({
|
||
user: {
|
||
id: user.id,
|
||
username: user.username,
|
||
role: user.role,
|
||
is_superadmin: user.is_superadmin,
|
||
},
|
||
token,
|
||
});
|
||
} catch (error) {
|
||
console.error('Login error:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
};
|
||
|
||
export const getMe = async (req: any, res: Response) => {
|
||
try {
|
||
const result = await mainPool.query(
|
||
'SELECT id, username, role, is_superadmin, created_at FROM users WHERE id = $1',
|
||
[req.user.id]
|
||
);
|
||
|
||
if (result.rows.length === 0) {
|
||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||
}
|
||
|
||
res.json(result.rows[0]);
|
||
} catch (error) {
|
||
console.error('Get me error:', error);
|
||
res.status(500).json({ error: 'Ошибка сервера' });
|
||
}
|
||
};
|