tududi/backend/app.js
Chris 03f38f05dc
Setup intelligence (#84)
* 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
2025-06-27 14:02:18 +03:00

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;