SaaS/server.js
Claude Code d1b5b72c46 🚀 Transform into full SaaS Automation Platform
Major Update: From simple click counter to comprehensive automation platform

## New Features:
 Full user authentication (register/login/logout)
 SQLite database with user management
 API credentials management system
 Workflow templates library (4 ready-to-use templates)
 User workflow management
 Comprehensive dashboard interface
 Detailed n8n integration instructions
 Security features (bcrypt, sessions, helmet)
 Automated testing suite (14 passing tests)

## Technical Stack:
- Backend: Node.js + Express + SQLite
- Frontend: Vanilla JS + Modern CSS
- Security: bcrypt, express-session, helmet
- Database: SQLite with proper schemas and indexes
- Testing: Mocha + Chai + Supertest

## Templates Included:
1. 📱 Telegram Bot Notifications
2. 📧 Email to Slack Integration
3. 💾 Google Drive Backup Automation
4. 📊 Lead Scoring Automation

## Access Points:
- Legacy app: http://localhost:3000/
- SaaS Platform: http://localhost:3000/dashboard
- API endpoints: /api/*

## Demo Credentials:
- Email: demo@example.com
- Password: demo123

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-29 11:05:29 +01:00

339 lines
No EOL
11 KiB
JavaScript
Raw 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(express.static(path.join(__dirname, 'public')));
// Сессии
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' });
}
}
// Основная страница - старая функциональность счетчика кликов
app.get('/', (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' });
});
});
// Инициализация приложения
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();