* Add next suggestions and remove console logs * Add pomodoro timer * Add pomodoro switch in settings * Fix pomodoro setting * Add timezones to settings * Fix an issue with password reset * Cleanup * Sort tags alphabetically * Clean up today's view * Add an indicator for repeatedly added to today * Refactor tags * Add due date today item * Move recurrence to the subtitle area * Fix today layout * Add a badge to Inbox items * Move inbox badge to sidebar * Add quotes and progress bar * Add translations for quotes * Fix test issues * Add helper script for docker local * Set up overdue tasks * Add linux/arm/v7 build to deploy script * Add linux/arm/v7 build to deploy script pt2 * Fix an issue with helmet and SSL * Add volume db persistence * Fix cog icon issues
181 lines
No EOL
5.8 KiB
JavaScript
181 lines
No EOL
5.8 KiB
JavaScript
require('dotenv').config();
|
|
const express = require('express');
|
|
const path = require('path');
|
|
const cors = require('cors');
|
|
const helmet = require('helmet');
|
|
const compression = require('compression');
|
|
const morgan = require('morgan');
|
|
const session = require('express-session');
|
|
const SequelizeStore = require('connect-session-sequelize')(session.Store);
|
|
const { sequelize } = require('./models');
|
|
const { initializeTelegramPolling } = require('./services/telegramInitializer');
|
|
const taskScheduler = require('./services/taskScheduler');
|
|
|
|
const app = express();
|
|
|
|
// Session store
|
|
const sessionStore = new SequelizeStore({
|
|
db: sequelize,
|
|
});
|
|
|
|
// Middlewares
|
|
const sslEnabled = process.env.NODE_ENV === 'production' && process.env.TUDUDI_INTERNAL_SSL_ENABLED === 'true';
|
|
app.use(helmet({
|
|
hsts: sslEnabled, // Only enable HSTS when SSL is enabled
|
|
forceHTTPS: sslEnabled, // Only force HTTPS when SSL is enabled
|
|
contentSecurityPolicy: false // Disable CSP for now to avoid conflicts
|
|
}));
|
|
app.use(compression());
|
|
app.use(morgan('combined'));
|
|
|
|
// CORS configuration
|
|
const allowedOrigins = process.env.TUDUDI_ALLOWED_ORIGINS
|
|
? process.env.TUDUDI_ALLOWED_ORIGINS.split(',').map(origin => origin.trim())
|
|
: ['http://localhost:8080', 'http://localhost:9292', 'http://127.0.0.1:8080', 'http://127.0.0.1:9292'];
|
|
|
|
app.use(cors({
|
|
origin: allowedOrigins,
|
|
credentials: true,
|
|
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
allowedHeaders: ['Authorization', 'Content-Type', 'Accept', 'X-Requested-With'],
|
|
exposedHeaders: ['Content-Type'],
|
|
maxAge: 1728000
|
|
}));
|
|
|
|
// Body parsing
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
|
|
// Session configuration
|
|
const secureFlag = process.env.NODE_ENV === 'production' && process.env.TUDUDI_INTERNAL_SSL_ENABLED === 'true';
|
|
app.use(session({
|
|
secret: process.env.TUDUDI_SESSION_SECRET || require('crypto').randomBytes(64).toString('hex'),
|
|
store: sessionStore,
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: secureFlag,
|
|
maxAge: 2592000000, // 30 days
|
|
sameSite: secureFlag ? 'none' : 'lax'
|
|
}
|
|
}));
|
|
|
|
// Static files
|
|
if (process.env.NODE_ENV === 'production') {
|
|
app.use(express.static(path.join(__dirname, 'dist')));
|
|
} else {
|
|
app.use(express.static('public'));
|
|
}
|
|
|
|
// Serve locales
|
|
if (process.env.NODE_ENV === 'production') {
|
|
app.use('/locales', express.static(path.join(__dirname, 'dist/locales')));
|
|
} else {
|
|
app.use('/locales', express.static(path.join(__dirname, '../public/locales')));
|
|
}
|
|
|
|
// Serve uploaded files
|
|
app.use('/api/uploads', express.static(path.join(__dirname, 'uploads')));
|
|
|
|
// Authentication middleware
|
|
const { requireAuth } = require('./middleware/auth');
|
|
|
|
// Health check (before auth middleware) - ensure it's completely bypassed
|
|
app.get('/api/health', (req, res) => {
|
|
res.status(200).json({
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime(),
|
|
environment: process.env.NODE_ENV || 'development'
|
|
});
|
|
});
|
|
|
|
// Routes
|
|
app.use('/api', require('./routes/auth'));
|
|
app.use('/api', requireAuth, require('./routes/tasks'));
|
|
app.use('/api', requireAuth, require('./routes/projects'));
|
|
app.use('/api', requireAuth, require('./routes/areas'));
|
|
app.use('/api', requireAuth, require('./routes/notes'));
|
|
app.use('/api', requireAuth, require('./routes/tags'));
|
|
app.use('/api', requireAuth, require('./routes/users'));
|
|
app.use('/api', requireAuth, require('./routes/inbox'));
|
|
app.use('/api', requireAuth, require('./routes/url'));
|
|
app.use('/api', requireAuth, require('./routes/telegram'));
|
|
app.use('/api', requireAuth, require('./routes/quotes'));
|
|
app.use('/api', requireAuth, require('./routes/task-events'));
|
|
app.use('/api/calendar', require('./routes/calendar'));
|
|
|
|
// SPA fallback
|
|
app.get('*', (req, res) => {
|
|
if (!req.path.startsWith('/api/') && !req.path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) {
|
|
if (process.env.NODE_ENV === 'production') {
|
|
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
|
} else {
|
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
}
|
|
} else {
|
|
res.status(404).json({ error: 'Not Found', message: 'The requested resource could not be found.' });
|
|
}
|
|
});
|
|
|
|
// Error handling
|
|
app.use((err, req, res, next) => {
|
|
console.error(err.stack);
|
|
res.status(500).json({ error: 'Internal Server Error', message: err.message });
|
|
});
|
|
|
|
const PORT = process.env.PORT || 3002;
|
|
|
|
// Initialize database and start server
|
|
async function startServer() {
|
|
try {
|
|
// Create session store table
|
|
await sessionStore.sync();
|
|
|
|
// Sync database
|
|
await sequelize.sync();
|
|
|
|
// Auto-create user if not exists
|
|
if (process.env.TUDUDI_USER_EMAIL && process.env.TUDUDI_USER_PASSWORD) {
|
|
const { User } = require('./models');
|
|
const bcrypt = require('bcrypt');
|
|
|
|
const [user, created] = await User.findOrCreate({
|
|
where: { email: process.env.TUDUDI_USER_EMAIL },
|
|
defaults: {
|
|
email: process.env.TUDUDI_USER_EMAIL,
|
|
password_digest: await bcrypt.hash(process.env.TUDUDI_USER_PASSWORD, 10)
|
|
}
|
|
});
|
|
|
|
if (created) {
|
|
console.log('Default user created:', user.email);
|
|
}
|
|
}
|
|
|
|
// Initialize Telegram polling after database is ready
|
|
await initializeTelegramPolling();
|
|
|
|
// Initialize task scheduler
|
|
await taskScheduler.initialize();
|
|
|
|
const server = app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`Server running on port ${PORT}`);
|
|
console.log(`Server listening on http://localhost:${PORT}`);
|
|
});
|
|
|
|
server.on('error', (err) => {
|
|
console.error('Server error:', err);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to start server:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
startServer();
|
|
}
|
|
|
|
module.exports = app; |