- Move express.static middleware after custom routes - This prevents index.html from being served on / route - Now homepage correctly shows SaaS platform instead of old counter - Static files still accessible via direct URLs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
346 lines
No EOL
11 KiB
JavaScript
346 lines
No EOL
11 KiB
JavaScript
const express = require('express');
|
||
const path = require('path');
|
||
const session = require('express-session');
|
||
const bcrypt = require('bcrypt');
|
||
const helmet = require('helmet');
|
||
const cors = require('cors');
|
||
require('dotenv').config();
|
||
|
||
// Инициализация базы данных
|
||
const { db, initDatabase } = require('./database/init');
|
||
|
||
const app = express();
|
||
const port = process.env.PORT || 3000;
|
||
|
||
// Middleware
|
||
app.use(helmet({
|
||
contentSecurityPolicy: false, // Отключаем для удобства разработки
|
||
}));
|
||
app.use(cors());
|
||
app.use(express.json());
|
||
app.use(express.urlencoded({ extended: true }));
|
||
|
||
// Сессии
|
||
app.use(session({
|
||
secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production',
|
||
resave: false,
|
||
saveUninitialized: false,
|
||
cookie: {
|
||
secure: process.env.NODE_ENV === 'production',
|
||
maxAge: 24 * 60 * 60 * 1000 // 24 часа
|
||
}
|
||
}));
|
||
|
||
// Middleware для проверки аутентификации
|
||
function requireAuth(req, res, next) {
|
||
if (req.session.userId) {
|
||
next();
|
||
} else {
|
||
res.status(401).json({ error: 'Authentication required' });
|
||
}
|
||
}
|
||
|
||
// Основная страница - перенаправление на SaaS платформу
|
||
app.get('/', (req, res) => {
|
||
res.sendFile(path.join(__dirname, 'public', 'dashboard.html'));
|
||
});
|
||
|
||
// Старая функциональность счетчика кликов (доступна по /counter)
|
||
app.get('/counter', (req, res) => {
|
||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||
});
|
||
|
||
// SaaS платформа
|
||
app.get('/dashboard', (req, res) => {
|
||
res.sendFile(path.join(__dirname, 'public', 'dashboard.html'));
|
||
});
|
||
|
||
// Health check
|
||
app.get('/health', (req, res) => {
|
||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||
});
|
||
|
||
// API Routes
|
||
app.post('/api/auth/login', (req, res) => {
|
||
const { email, password } = req.body;
|
||
|
||
if (!email || !password) {
|
||
return res.status(400).json({ error: 'Email and password are required' });
|
||
}
|
||
|
||
db.get('SELECT * FROM users WHERE email = ?', [email], (err, user) => {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
|
||
if (!user) {
|
||
return res.status(401).json({ error: 'Invalid credentials' });
|
||
}
|
||
|
||
bcrypt.compare(password, user.password_hash, (err, isValid) => {
|
||
if (err || !isValid) {
|
||
return res.status(401).json({ error: 'Invalid credentials' });
|
||
}
|
||
|
||
req.session.userId = user.id;
|
||
req.session.username = user.username;
|
||
|
||
res.json({
|
||
user: {
|
||
id: user.id,
|
||
username: user.username,
|
||
email: user.email,
|
||
subscription_type: user.subscription_type
|
||
}
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
app.post('/api/auth/register', (req, res) => {
|
||
const { username, email, password } = req.body;
|
||
|
||
if (!username || !email || !password) {
|
||
return res.status(400).json({ error: 'Username, email and password are required' });
|
||
}
|
||
|
||
if (password.length < 6) {
|
||
return res.status(400).json({ error: 'Password must be at least 6 characters' });
|
||
}
|
||
|
||
bcrypt.hash(password, 10, (err, hash) => {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Error hashing password' });
|
||
}
|
||
|
||
db.run('INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)',
|
||
[username, email, hash], function(err) {
|
||
if (err) {
|
||
if (err.code === 'SQLITE_CONSTRAINT') {
|
||
return res.status(400).json({ error: 'Username or email already exists' });
|
||
}
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
|
||
req.session.userId = this.lastID;
|
||
req.session.username = username;
|
||
|
||
res.status(201).json({
|
||
user: {
|
||
id: this.lastID,
|
||
username,
|
||
email,
|
||
subscription_type: 'free'
|
||
}
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
app.post('/api/auth/logout', (req, res) => {
|
||
req.session.destroy((err) => {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Error logging out' });
|
||
}
|
||
res.json({ message: 'Logged out successfully' });
|
||
});
|
||
});
|
||
|
||
// Получение информации о пользователе
|
||
app.get('/api/user/profile', requireAuth, (req, res) => {
|
||
db.get('SELECT id, username, email, subscription_type, created_at FROM users WHERE id = ?',
|
||
[req.session.userId], (err, user) => {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
res.json({ user });
|
||
});
|
||
});
|
||
|
||
// Управление credentials
|
||
app.get('/api/user/credentials', requireAuth, (req, res) => {
|
||
db.all(`SELECT id, service_name, credential_type, description, created_at
|
||
FROM user_credentials WHERE user_id = ? AND is_active = 1`,
|
||
[req.session.userId], (err, credentials) => {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
res.json({ credentials });
|
||
});
|
||
});
|
||
|
||
app.post('/api/user/credentials', requireAuth, (req, res) => {
|
||
const { service_name, credential_type, value, description } = req.body;
|
||
|
||
if (!service_name || !credential_type || !value) {
|
||
return res.status(400).json({ error: 'Missing required fields' });
|
||
}
|
||
|
||
// Простое "шифрование" - в продакшене используйте настоящее шифрование
|
||
const encrypted_value = Buffer.from(value).toString('base64');
|
||
|
||
db.run(`INSERT OR REPLACE INTO user_credentials
|
||
(user_id, service_name, credential_type, encrypted_value, description)
|
||
VALUES (?, ?, ?, ?, ?)`,
|
||
[req.session.userId, service_name, credential_type, encrypted_value, description],
|
||
function(err) {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
res.status(201).json({
|
||
id: this.lastID,
|
||
message: 'Credential saved successfully'
|
||
});
|
||
});
|
||
});
|
||
|
||
app.delete('/api/user/credentials/:id', requireAuth, (req, res) => {
|
||
const credentialId = req.params.id;
|
||
|
||
db.run('DELETE FROM user_credentials WHERE id = ? AND user_id = ?',
|
||
[credentialId, req.session.userId], function(err) {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: 'Credential not found' });
|
||
}
|
||
res.json({ message: 'Credential deleted successfully' });
|
||
});
|
||
});
|
||
|
||
// Шаблоны рабочих процессов
|
||
app.get('/api/templates', (req, res) => {
|
||
const { category, featured } = req.query;
|
||
let query = 'SELECT * FROM workflow_templates WHERE is_active = 1';
|
||
const params = [];
|
||
|
||
if (category) {
|
||
query += ' AND category = ?';
|
||
params.push(category);
|
||
}
|
||
|
||
if (featured === 'true') {
|
||
query += ' AND is_featured = 1';
|
||
}
|
||
|
||
query += ' ORDER BY is_featured DESC, created_at DESC';
|
||
|
||
db.all(query, params, (err, templates) => {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
|
||
// Парсим JSON поля
|
||
const processedTemplates = templates.map(template => ({
|
||
...template,
|
||
template_data: JSON.parse(template.template_data),
|
||
required_credentials: JSON.parse(template.required_credentials)
|
||
}));
|
||
|
||
res.json({ templates: processedTemplates });
|
||
});
|
||
});
|
||
|
||
app.get('/api/templates/:id', (req, res) => {
|
||
const templateId = req.params.id;
|
||
|
||
db.get('SELECT * FROM workflow_templates WHERE id = ? AND is_active = 1',
|
||
[templateId], (err, template) => {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
if (!template) {
|
||
return res.status(404).json({ error: 'Template not found' });
|
||
}
|
||
|
||
// Парсим JSON поля
|
||
template.template_data = JSON.parse(template.template_data);
|
||
template.required_credentials = JSON.parse(template.required_credentials);
|
||
|
||
res.json({ template });
|
||
});
|
||
});
|
||
|
||
// Пользовательские рабочие процессы
|
||
app.get('/api/user/workflows', requireAuth, (req, res) => {
|
||
db.all(`SELECT uw.*, wt.name as template_name
|
||
FROM user_workflows uw
|
||
LEFT JOIN workflow_templates wt ON uw.template_id = wt.id
|
||
WHERE uw.user_id = ?
|
||
ORDER BY uw.created_at DESC`,
|
||
[req.session.userId], (err, workflows) => {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
res.json({ workflows });
|
||
});
|
||
});
|
||
|
||
app.post('/api/user/workflows', requireAuth, (req, res) => {
|
||
const { template_id, workflow_name, configuration } = req.body;
|
||
|
||
if (!workflow_name) {
|
||
return res.status(400).json({ error: 'Workflow name is required' });
|
||
}
|
||
|
||
db.run(`INSERT INTO user_workflows (user_id, template_id, workflow_name, configuration)
|
||
VALUES (?, ?, ?, ?)`,
|
||
[req.session.userId, template_id || null, workflow_name, JSON.stringify(configuration || {})],
|
||
function(err) {
|
||
if (err) {
|
||
return res.status(500).json({ error: 'Database error' });
|
||
}
|
||
res.status(201).json({
|
||
id: this.lastID,
|
||
message: 'Workflow created successfully'
|
||
});
|
||
});
|
||
});
|
||
|
||
// Статистика использования
|
||
app.get('/api/user/stats', requireAuth, (req, res) => {
|
||
const queries = [
|
||
'SELECT COUNT(*) as total_workflows FROM user_workflows WHERE user_id = ?',
|
||
'SELECT COUNT(*) as total_credentials FROM user_credentials WHERE user_id = ? AND is_active = 1',
|
||
'SELECT COUNT(*) as total_executions FROM usage_stats WHERE user_id = ?'
|
||
];
|
||
|
||
Promise.all(queries.map(query => new Promise((resolve, reject) => {
|
||
db.get(query, [req.session.userId], (err, result) => {
|
||
if (err) reject(err);
|
||
else resolve(result);
|
||
});
|
||
}))).then(results => {
|
||
res.json({
|
||
stats: {
|
||
total_workflows: results[0].total_workflows,
|
||
total_credentials: results[1].total_credentials,
|
||
total_executions: results[2].total_executions
|
||
}
|
||
});
|
||
}).catch(() => {
|
||
res.status(500).json({ error: 'Database error' });
|
||
});
|
||
});
|
||
|
||
// Static файлы (после пользовательских маршрутов)
|
||
app.use(express.static(path.join(__dirname, 'public')));
|
||
|
||
// Инициализация приложения
|
||
async function startApp() {
|
||
try {
|
||
await initDatabase();
|
||
|
||
app.listen(port, () => {
|
||
console.log(`🚀 SaaS Automation Platform running on http://localhost:${port}`);
|
||
console.log(`📊 Health check available at: http://localhost:${port}/health`);
|
||
console.log(`🔑 Demo credentials: demo@example.com / demo123`);
|
||
});
|
||
} catch (error) {
|
||
console.error('❌ Failed to initialize application:', error);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
startApp(); |