diff --git a/backend/app.js b/backend/app.js index b8d7b88..560ae5c 100644 --- a/backend/app.js +++ b/backend/app.js @@ -10,6 +10,7 @@ const SequelizeStore = require('connect-session-sequelize')(session.Store); const { sequelize } = require('./models'); const { initializeTelegramPolling } = require('./services/telegramInitializer'); const taskScheduler = require('./services/taskScheduler'); +const config = require('./config/config'); const app = express(); @@ -19,13 +20,10 @@ const sessionStore = new SequelizeStore({ }); // 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 + hsts: config.sslEnabled, // Only enable HSTS when SSL is enabled + forceHTTPS: config.sslEnabled, // Only force HTTPS when SSL is enabled contentSecurityPolicy: false, // Disable CSP for now to avoid conflicts }) ); @@ -33,20 +31,9 @@ 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, + origin: config.allowedOrigins, credentials: true, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: [ @@ -65,35 +52,30 @@ 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'), + secret: config.secret, store: sessionStore, resave: false, saveUninitialized: false, cookie: { httpOnly: true, - secure: secureFlag, + secure: config.sslEnabled, maxAge: 2592000000, // 30 days - sameSite: secureFlag ? 'none' : 'lax', + sameSite: config.sslEnabled ? 'none' : 'lax', }, }) ); // Static files -if (process.env.NODE_ENV === 'production') { +if (config.production) { app.use(express.static(path.join(__dirname, 'dist'))); } else { app.use(express.static('public')); } // Serve locales -if (process.env.NODE_ENV === 'production') { +if (config.production) { app.use('/locales', express.static(path.join(__dirname, 'dist/locales'))); } else { app.use( @@ -114,7 +96,7 @@ app.get('/api/health', (req, res) => { status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime(), - environment: process.env.NODE_ENV || 'development', + environment: config.environment, }); }); @@ -139,7 +121,7 @@ app.get('*', (req, res) => { !req.path.startsWith('/api/') && !req.path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/) ) { - if (process.env.NODE_ENV === 'production') { + if (config.production) { res.sendFile(path.join(__dirname, 'dist', 'index.html')); } else { res.sendFile(path.join(__dirname, '../public', 'index.html')); @@ -161,8 +143,6 @@ app.use((err, req, res, next) => { }); }); -const PORT = process.env.PORT || 3002; - // Initialize database and start server async function startServer() { try { @@ -173,18 +153,15 @@ async function startServer() { await sequelize.sync(); // Auto-create user if not exists - if (process.env.TUDUDI_USER_EMAIL && process.env.TUDUDI_USER_PASSWORD) { + if (config.email && config.password) { const { User } = require('./models'); const bcrypt = require('bcrypt'); const [user, created] = await User.findOrCreate({ - where: { email: process.env.TUDUDI_USER_EMAIL }, + where: { email: config.email }, defaults: { - email: process.env.TUDUDI_USER_EMAIL, - password_digest: await bcrypt.hash( - process.env.TUDUDI_USER_PASSWORD, - 10 - ), + email: config.email, + password_digest: await bcrypt.hash(config.password, 10), }, }); @@ -199,9 +176,9 @@ async function startServer() { // 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}`); + 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) => { diff --git a/backend/config/config.js b/backend/config/config.js new file mode 100644 index 0000000..af42e06 --- /dev/null +++ b/backend/config/config.js @@ -0,0 +1,79 @@ +const path = require('path'); + +if ( + process.env.NODE_ENV !== 'production' && + process.env.NODE_ENV !== 'development' && + process.env.NODE_ENV !== 'test' +) { + console.error( + "NODE_ENV should be one of 'production', 'development' or 'test'." + ); + process.exit(1); +} + +const environment = process.env.NODE_ENV; +const production = process.env.NODE_ENV === 'production'; +const projectRootPath = path.join(__dirname, '..'); // backend root path +const dbDir = process.env.DATABASE_URL + ? process.env.DATABASE_URL.replace('sqlite:///', '') + : path.join(projectRootPath, 'db'); + +const credentials = { + google: { + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + redirectUri: + process.env.GOOGLE_REDIRECT_URI || + 'http://localhost:3002/api/calendar/oauth/callback', + }, +}; + +const config = { + 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', + ], + + dbDir, + + dbFile: path.join(dbDir, `${environment}.sqlite3`), + + disableScheduler: process.env.DISABLE_SCHEDULER === 'true', + + disableTelegram: process.env.DISABLE_TELEGRAM === 'true', + + email: process.env.TUDUDI_USER_EMAIL, + + environment, + + frontendUrl: process.env.FRONTEND_URL || 'http://localhost:8080', + + host: process.env.HOST || '0.0.0.0', + + port: process.env.PORT || 3002, + + password: process.env.TUDUDI_USER_PASSWORD, + + production, + + secret: + process.env.TUDUDI_SESSION_SECRET || + require('crypto').randomBytes(64).toString('hex'), + + credentials, + + sslEnabled: + production && process.env.TUDUDI_INTERNAL_SSL_ENABLED === 'true', +}; + +if (environment !== 'production') { + console.log(`Configuration: ${JSON.stringify(config, null, 4)}`); +} + +module.exports = config; diff --git a/backend/config/database.js b/backend/config/database.js index 65f42c8..fe350f0 100644 --- a/backend/config/database.js +++ b/backend/config/database.js @@ -1,14 +1,11 @@ require('dotenv').config(); const path = require('path'); - -const dbPath = process.env.DATABASE_URL - ? process.env.DATABASE_URL.replace('sqlite:///', '') - : path.join(__dirname, '..', 'db'); +const config = require('./config'); module.exports = { development: { dialect: 'sqlite', - storage: path.join(dbPath, 'development.sqlite3'), + storage: path.join(config.dbDir, 'development.sqlite3'), logging: console.log, define: { timestamps: true, @@ -19,7 +16,7 @@ module.exports = { }, test: { dialect: 'sqlite', - storage: path.join(dbPath, 'test.sqlite3'), + storage: path.join(config.dbDir, 'test.sqlite3'), logging: false, define: { timestamps: true, @@ -30,7 +27,7 @@ module.exports = { }, production: { dialect: 'sqlite', - storage: path.join(dbPath, 'production.sqlite3'), + storage: path.join(config.dbDir, 'production.sqlite3'), logging: false, define: { timestamps: true, diff --git a/backend/models/index.js b/backend/models/index.js index b61678a..11d5fa4 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -1,46 +1,21 @@ const { Sequelize } = require('sequelize'); const path = require('path'); +const config = require('../config/config'); // Database configuration let dbConfig; -if (process.env.NODE_ENV === 'test') { - // Use temporary file database for tests to allow external script access - const testDbPath = path.join(__dirname, '../db', 'test.sqlite3'); - dbConfig = { - dialect: 'sqlite', - storage: testDbPath, - logging: false, - define: { - timestamps: true, - underscored: true, - createdAt: 'created_at', - updatedAt: 'updated_at', - }, - }; -} else { - const dbPath = process.env.DATABASE_URL - ? process.env.DATABASE_URL.replace('sqlite:///', '') - : path.join( - __dirname, - '../db', - process.env.NODE_ENV === 'production' - ? 'production.sqlite3' - : 'development.sqlite3' - ); - - dbConfig = { - dialect: 'sqlite', - storage: dbPath, - logging: process.env.NODE_ENV === 'development' ? console.log : false, - define: { - timestamps: true, - underscored: true, - createdAt: 'created_at', - updatedAt: 'updated_at', - }, - }; -} +dbConfig = { + dialect: 'sqlite', + storage: config.dbFile, + logging: config.environment === 'development' ? console.log : false, + define: { + timestamps: true, + underscored: true, + createdAt: 'created_at', + updatedAt: 'updated_at', + }, +}; const sequelize = new Sequelize(dbConfig); diff --git a/backend/routes/calendar.js b/backend/routes/calendar.js index f8e05dd..c06edd4 100644 --- a/backend/routes/calendar.js +++ b/backend/routes/calendar.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const { google } = require('googleapis'); const { requireAuth } = require('../middleware/auth'); +const config = require('../config/config'); // Google Calendar configuration const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']; @@ -9,10 +10,9 @@ const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']; // OAuth2 client setup const getOAuth2Client = () => { return new google.auth.OAuth2( - process.env.GOOGLE_CLIENT_ID, - process.env.GOOGLE_CLIENT_SECRET, - process.env.GOOGLE_REDIRECT_URI || - 'http://localhost:3002/api/calendar/oauth/callback' + config.credentials.google.clientId, + config.credentials.google.clientSecret, + config.credentials.google.redirectUri ); }; @@ -21,8 +21,8 @@ router.get('/auth', requireAuth, (req, res) => { try { // Check if Google credentials are configured if ( - !process.env.GOOGLE_CLIENT_ID || - !process.env.GOOGLE_CLIENT_SECRET + !config.credentials.google.clientId || + !config.credentials.google.clientSecret ) { // Demo mode - simulate successful connection console.log( @@ -31,10 +31,8 @@ router.get('/auth', requireAuth, (req, res) => { ); // Simulate the callback redirect with success - const frontendUrl = - process.env.FRONTEND_URL || 'http://localhost:8080'; return res.json({ - authUrl: `${frontendUrl}/calendar?demo=true&connected=true`, + authUrl: `${config.frontendUrl}/calendar?demo=true&connected=true`, demo: true, message: 'Demo mode: Google Calendar integration simulated', }); @@ -82,14 +80,10 @@ router.get('/oauth/callback', async (req, res) => { // await saveGoogleTokensForUser(userId, tokens); // Redirect to frontend with success - res.redirect( - `${process.env.FRONTEND_URL || 'http://localhost:8080'}/calendar?connected=true` - ); + res.redirect(`${config.frontendUrl}/calendar?connected=true`); } catch (error) { console.error('Error handling OAuth callback:', error); - res.redirect( - `${process.env.FRONTEND_URL || 'http://localhost:8080'}/calendar?error=auth_failed` - ); + res.redirect(`${config.frontendUrl}/calendar?error=auth_failed`); } }); @@ -98,8 +92,8 @@ router.get('/status', requireAuth, async (req, res) => { try { // Check if we're in demo mode or have real Google integration if ( - !process.env.GOOGLE_CLIENT_ID || - !process.env.GOOGLE_CLIENT_SECRET + !config.credentials.google.clientId || + !config.credentials.google.clientSecret ) { // Demo mode - check if user has been "connected" in this session // For demo purposes, we'll simulate connection status @@ -155,7 +149,7 @@ router.get('/events', requireAuth, async (req, res) => { oauth2Client.setCredentials(tokens); const calendar = google.calendar({ version: 'v3', auth: oauth2Client }); - + const response = await calendar.events.list({ calendarId: 'primary', timeMin: start || new Date().toISOString(), diff --git a/backend/routes/tags.js b/backend/routes/tags.js index 6c1cb66..b26c560 100644 --- a/backend/routes/tags.js +++ b/backend/routes/tags.js @@ -10,7 +10,6 @@ router.get('/tags', async (req, res) => { attributes: ['id', 'name'], order: [['name', 'ASC']], }); - res.json(tags); } catch (error) { console.error('Error fetching tags:', error); diff --git a/backend/scripts/seed-dev-data.js b/backend/scripts/seed-dev-data.js index b3f09d1..55605b6 100644 --- a/backend/scripts/seed-dev-data.js +++ b/backend/scripts/seed-dev-data.js @@ -2,17 +2,10 @@ const path = require('path'); const { seedDatabase } = require('../seeders/dev-seeder'); - -// Set up the environment -process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - -// Ensure we're using the correct database path -if (!process.env.DATABASE_URL) { - process.env.DATABASE_URL = `sqlite:///${path.join(__dirname, '../db/development.sqlite3')}`; -} +const config = require('../config/config'); console.log('🌱 Starting development data seeding...'); -console.log(`📁 Database: ${process.env.DATABASE_URL}`); -console.log(`🌍 Environment: ${process.env.NODE_ENV}`); +console.log(`📁 Database: ${config.dbFile}`); +console.log(`🌍 Environment: ${config.environment}`); seedDatabase(); diff --git a/backend/services/taskScheduler.js b/backend/services/taskScheduler.js index 5881549..bacbda4 100644 --- a/backend/services/taskScheduler.js +++ b/backend/services/taskScheduler.js @@ -2,6 +2,7 @@ const cron = require('node-cron'); const { User } = require('../models'); const TaskSummaryService = require('./taskSummaryService'); const RecurringTaskService = require('./recurringTaskService'); +const config = require('../config/config'); // Create scheduler state const createSchedulerState = () => ({ @@ -14,7 +15,7 @@ let schedulerState = createSchedulerState(); // Check if scheduler should be disabled const shouldDisableScheduler = () => - process.env.NODE_ENV === 'test' || process.env.DISABLE_SCHEDULER === 'true'; + config.environment === 'test' || config.disableScheduler; // Create job configuration const createJobConfig = () => ({ diff --git a/backend/services/telegramInitializer.js b/backend/services/telegramInitializer.js index a5e7e7a..706e4f6 100644 --- a/backend/services/telegramInitializer.js +++ b/backend/services/telegramInitializer.js @@ -1,11 +1,9 @@ const telegramPoller = require('./telegramPoller'); const { User } = require('../models'); +const config = require('../config/config'); async function initializeTelegramPolling() { - if ( - process.env.NODE_ENV === 'test' || - process.env.DISABLE_TELEGRAM === 'true' - ) { + if (config.environment === 'test' || config.disableTelegram) { return; } diff --git a/backend/start.sh b/backend/start.sh index e65ab8e..54a85af 100755 --- a/backend/start.sh +++ b/backend/start.sh @@ -8,4 +8,4 @@ echo " TUDUDI_USER_EMAIL=your_email@example.com" echo " TUDUDI_USER_PASSWORD=your_password" echo "" -PORT=3002 npm start \ No newline at end of file +NODE_ENV=development PORT=3002 npm start diff --git a/backend/tests/helpers/setup.js b/backend/tests/helpers/setup.js index fd6432f..b405983 100644 --- a/backend/tests/helpers/setup.js +++ b/backend/tests/helpers/setup.js @@ -2,8 +2,6 @@ process.env.NODE_ENV = 'test'; const { sequelize } = require('../../models'); -const fs = require('fs'); -const path = require('path'); beforeAll(async () => { // Ensure test database is clean and created