SaaS/server.js
Claude Code 15532139f4 Fix Express middleware order to resolve homepage routing issue
- 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>
2025-09-29 11:30:17 +01:00

346 lines
No EOL
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();