feat: restructure into XFlow monorepo with Nuxt 4, Modular Onion Monolith, Android client, and Desktop CLI

This commit is contained in:
2026-09-28 10:39:19 +05:00
parent edcd30c829
commit be68f5e130
204 changed files with 24616 additions and 39 deletions
+25
View File
@@ -0,0 +1,25 @@
FROM node:20-alpine
# Install git for repository operations
RUN apk add --no-cache git
# Create app directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy app source
COPY src/ ./src/
# Create directories
RUN mkdir -p /app/mindmaps /app/logs
# Expose port
EXPOSE 8080
# Start the server
CMD ["npm", "start"]
+34
View File
@@ -0,0 +1,34 @@
{
"name": "xflow-api-server",
"version": "1.0.0",
"description": "API server for XFlow CRM",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"helmet": "^7.1.0",
"morgan": "^1.10.0",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3",
"axios": "^1.6.2",
"simple-git": "^3.21.0",
"fs-extra": "^11.2.0",
"uuid": "^9.0.1",
"dotenv": "^16.3.1",
"express-rate-limit": "^7.1.5",
"multer": "^1.4.5-lts.1",
"yaml": "^2.3.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"jest": "^29.7.0"
},
"engines": {
"node": ">=18.0.0"
}
}
+58
View File
@@ -0,0 +1,58 @@
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
require('dotenv').config();
const authRoutes = require('./routes/auth');
const { errorHandler } = require('./middleware/errorHandler');
const app = express();
const PORT = process.env.PORT || 8080;
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN || '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Logging
app.use(morgan('combined'));
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
version: '1.0.0'
});
});
// API Routes
app.use('/api/auth', authRoutes);
// Error handling
app.use(errorHandler);
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Not found' });
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`🚀 XFlow API Server running on port ${PORT}`);
});
@@ -0,0 +1,34 @@
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key';
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = user;
next();
});
};
const generateToken = (user) => {
return jwt.sign(
{
id: user.id,
email: user.email,
username: user.username
},
JWT_SECRET,
{ expiresIn: '7d' }
);
};
module.exports = { authenticateToken, generateToken };
@@ -0,0 +1,33 @@
const errorHandler = (err, req, res, next) => {
console.error('Error:', err);
// Default error
let statusCode = err.statusCode || 500;
let message = err.message || 'Internal Server Error';
// Handle specific error types
if (err.name === 'ValidationError') {
statusCode = 400;
message = 'Validation Error: ' + message;
} else if (err.name === 'UnauthorizedError') {
statusCode = 401;
message = 'Unauthorized';
} else if (err.name === 'ForbiddenError') {
statusCode = 403;
message = 'Forbidden';
} else if (err.name === 'NotFoundError') {
statusCode = 404;
message = 'Resource not found';
}
res.status(statusCode).json({
error: {
message,
status: statusCode,
timestamp: new Date().toISOString(),
path: req.path
}
});
};
module.exports = { errorHandler };
@@ -0,0 +1,97 @@
const express = require('express');
const router = express.Router();
const { generateToken } = require('../middleware/auth');
// Mock user storage (в продакшене использовать базу данных)
const users = new Map();
// Register
router.post('/register', async (req, res) => {
try {
const { email, password, username } = req.body;
if (!email || !password || !username) {
return res.status(400).json({ error: 'Email, password and username are required' });
}
if (users.has(email)) {
return res.status(409).json({ error: 'User already exists' });
}
const userId = Date.now().toString();
const user = {
id: userId,
email,
username,
password, // В продакшене хешировать!
createdAt: new Date().toISOString()
};
users.set(email, user);
const token = generateToken(user);
res.status(201).json({
message: 'User registered successfully',
token,
user: {
id: user.id,
email: user.email,
username: user.username
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Login
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
const user = users.get(email);
if (!user || user.password !== password) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = generateToken(user);
res.json({
message: 'Login successful',
token,
user: {
id: user.id,
email: user.email,
username: user.username
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Validate token
router.get('/validate', async (req, res) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ valid: false, error: 'No token provided' });
}
try {
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-super-secret-jwt-key');
res.json({ valid: true, user: decoded });
} catch (error) {
res.status(403).json({ valid: false, error: 'Invalid token' });
}
});
module.exports = router;