tududi/backend/app.js
Chris 02b493d61f
Universal search (#412)
* Global search scaffold

* Add search preview text

* Add generic fallback for preview text in search

* fixup! Add generic fallback for preview text in search

* Add more tweaks

* fixup! Add more tweaks

* Fix an issue with criteria

* fixup! Fix an issue with criteria

* fixup! fixup! Fix an issue with criteria

* fixup! fixup! fixup! Fix an issue with criteria

* Fix an issue with priority filter

* fixup! Fix an issue with priority filter

* Add sortable pins

* fixup! Add sortable pins

* Make options collapsed by default

* Tweak UI

* Add tests

* Add translations

* Add more translations

* fixup! Add more translations

* Add minor tweaks
2025-10-22 22:00:45 +03:00

184 lines
5.2 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 { setConfig, getConfig } = require('./config/config');
const config = getConfig();
const app = express();
// Session store
const sessionStore = new SequelizeStore({
db: sequelize,
});
// Middlewares
app.use(
helmet({
hsts: false,
forceHTTPS: false,
contentSecurityPolicy: false,
})
);
app.use(compression());
app.use(morgan('combined'));
// CORS configuration
app.use(
cors({
origin: config.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
app.use(
session({
secret: config.secret,
store: sessionStore,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: false,
maxAge: 2592000000, // 30 days
sameSite: 'lax',
},
})
);
// Static files
if (config.production) {
app.use(express.static(path.join(__dirname, 'dist')));
} else {
app.use(express.static('public'));
}
// Serve locales
if (config.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(config.uploadPath));
// Authentication middleware
const { requireAuth } = require('./middleware/auth');
const { logError } = require('./services/logService');
// 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: config.environment,
});
});
// 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/admin'));
app.use('/api', requireAuth, require('./routes/shares'));
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/search', requireAuth, require('./routes/search'));
app.use('/api/views', requireAuth, require('./routes/views'));
// SPA fallback
app.get('*', (req, res) => {
if (
!req.path.startsWith('/api/') &&
!req.path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)
) {
if (config.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 fallback.
// We shouldn't be here normally!
// Each route should properly handle
// and log its own errors.
app.use((err, req, res, next) => {
logError(err);
res.status(500).json({
error: 'Internal Server Error',
// message: err.message,
});
});
// Initialize database and start server
async function startServer() {
try {
// Create session store table
await sessionStore.sync();
// Initialize Telegram polling after database is ready
await initializeTelegramPolling();
// Initialize task scheduler
await taskScheduler.initialize();
const server = app.listen(config.port, config.host, () => {
console.log(`Server running on port ${config.port}`);
console.log(`Server listening on http://localhost:${config.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;