241 lines
7.1 KiB
JavaScript
241 lines
7.1 KiB
JavaScript
/**
|
|
* More info about this file:
|
|
* https://v2.quasar.dev/quasar-cli-vite/developing-ssr/ssr-webserver
|
|
*
|
|
* Runs in Node context.
|
|
*/
|
|
|
|
/**
|
|
* Make sure to yarn add / npm install (in your project root)
|
|
* anything you import here (except for express and compression).
|
|
*/
|
|
import express from 'express';
|
|
import compression from 'compression';
|
|
import session from 'express-session'; // Added for session management
|
|
import { v4 as uuidv4 } from 'uuid'; // Added for generating session IDs
|
|
import {
|
|
defineSsrCreate,
|
|
defineSsrListen,
|
|
defineSsrClose,
|
|
defineSsrServeStaticContent,
|
|
defineSsrRenderPreloadTag
|
|
} from '#q-app/wrappers';
|
|
|
|
import prisma from './database.js'; // Import the prisma client instance
|
|
import apiRoutes from './routes/api.js';
|
|
import authRoutes from './routes/auth.js'; // Added for WebAuthn routes
|
|
import chatRoutes from './routes/chat.js'; // Added for Chat routes
|
|
import cron from 'node-cron';
|
|
import { generateAndStoreMantisSummary } from './services/mantisSummarizer.js';
|
|
|
|
// Define Relying Party details (Update with your actual details)
|
|
export const rpID = process.env.NODE_ENV === 'production' ? 'your-production-domain.com' : 'localhost';
|
|
export const rpName = 'StylePoint';
|
|
export const origin = process.env.NODE_ENV === 'production' ? `https://${rpID}` : `http://${rpID}:9100`;
|
|
|
|
// In-memory store for challenges (Replace with a persistent store in production)
|
|
export const challengeStore = new Map();
|
|
|
|
/**
|
|
* Create your webserver and return its instance.
|
|
* If needed, prepare your webserver to receive
|
|
* connect-like middlewares.
|
|
*
|
|
* Can be async: defineSsrCreate(async ({ ... }) => { ... })
|
|
*/
|
|
export const create = defineSsrCreate((/* { ... } */) =>
|
|
{
|
|
const app = express();
|
|
|
|
// Session middleware configuration
|
|
app.use(session({
|
|
genid: (req) => uuidv4(), // Use UUIDs for session IDs
|
|
secret: process.env.SESSION_SECRET || 'a-very-strong-secret-key', // Use an environment variable for the secret
|
|
resave: false,
|
|
saveUninitialized: true,
|
|
cookie: {
|
|
secure: process.env.NODE_ENV === 'production', // Use secure cookies in production
|
|
httpOnly: true,
|
|
maxAge: 1000 * 60 * 60 * 24 // 1 day
|
|
}
|
|
}));
|
|
|
|
// Initialize the database (now synchronous)
|
|
try
|
|
{
|
|
console.log('Prisma Client is ready.'); // Log Prisma readiness
|
|
|
|
// Schedule the Mantis summary task after DB initialization
|
|
// Run daily at 1:00 AM server time (adjust as needed)
|
|
cron.schedule('0 1 * * *', async() =>
|
|
{
|
|
console.log('Running scheduled Mantis summary task...');
|
|
try
|
|
{
|
|
await generateAndStoreMantisSummary();
|
|
console.log('Scheduled Mantis summary task completed.');
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error('Error running scheduled Mantis summary task:', error);
|
|
}
|
|
}, {
|
|
scheduled: true,
|
|
timezone: 'Europe/London' // Example: Set to your server's timezone
|
|
});
|
|
console.log('Mantis summary cron job scheduled.');
|
|
|
|
// Optional: Run once immediately on server start if needed
|
|
generateAndStoreMantisSummary().catch(err => console.error('Initial Mantis summary failed:', err));
|
|
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error('Error during server setup:', error);
|
|
// Optionally handle the error more gracefully, e.g., prevent server start
|
|
process.exit(1); // Exit if setup fails
|
|
}
|
|
|
|
// attackers can use this header to detect apps running Express
|
|
// and then launch specifically-targeted attacks
|
|
app.disable('x-powered-by');
|
|
|
|
// Add JSON body parsing middleware
|
|
app.use(express.json());
|
|
|
|
// Add API routes
|
|
app.use('/api', apiRoutes);
|
|
app.use('/auth', authRoutes); // Added WebAuthn auth routes
|
|
app.use('/api/chat', chatRoutes); // Added Chat routes
|
|
|
|
// place here any middlewares that
|
|
// absolutely need to run before anything else
|
|
if (process.env.PROD)
|
|
{
|
|
app.use(compression());
|
|
}
|
|
|
|
return app;
|
|
});
|
|
|
|
/**
|
|
* You need to make the server listen to the indicated port
|
|
* and return the listening instance or whatever you need to
|
|
* close the server with.
|
|
*
|
|
* The "listenResult" param for the "close()" definition below
|
|
* is what you return here.
|
|
*
|
|
* For production, you can instead export your
|
|
* handler for serverless use or whatever else fits your needs.
|
|
*
|
|
* Can be async: defineSsrListen(async ({ app, devHttpsApp, port }) => { ... })
|
|
*/
|
|
export const listen = defineSsrListen(({ app, devHttpsApp, port }) =>
|
|
{
|
|
const server = devHttpsApp || app;
|
|
return server.listen(port, () =>
|
|
{
|
|
if (process.env.PROD)
|
|
{
|
|
console.log('Server listening at port ' + port);
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Should close the server and free up any resources.
|
|
* Will be used on development mode when the server needs
|
|
* to be restarted, or when the application shuts down.
|
|
*
|
|
* Can be async: defineSsrClose(async ({ ... }) => { ... })
|
|
*/
|
|
export const close = defineSsrClose(async({ listenResult }) =>
|
|
{
|
|
// Close the database connection when the server shuts down
|
|
try
|
|
{
|
|
await prisma.$disconnect();
|
|
console.log('Prisma Client disconnected.');
|
|
}
|
|
catch (e)
|
|
{
|
|
console.error('Error disconnecting Prisma Client:', e);
|
|
}
|
|
|
|
return listenResult.close();
|
|
});
|
|
|
|
const maxAge = process.env.DEV
|
|
? 0
|
|
: 1000 * 60 * 60 * 24 * 30;
|
|
|
|
/**
|
|
* Should return a function that will be used to configure the webserver
|
|
* to serve static content at "urlPath" from "pathToServe" folder/file.
|
|
*
|
|
* Notice resolve.urlPath(urlPath) and resolve.public(pathToServe) usages.
|
|
*
|
|
* Can be async: defineSsrServeStaticContent(async ({ app, resolve }) => {
|
|
* Can return an async function: return async ({ urlPath = '/', pathToServe = '.', opts = {} }) => {
|
|
*/
|
|
export const serveStaticContent = defineSsrServeStaticContent(({ app, resolve }) =>
|
|
{
|
|
return ({ urlPath = '/', pathToServe = '.', opts = {} }) =>
|
|
{
|
|
const serveFn = express.static(resolve.public(pathToServe), { maxAge, ...opts });
|
|
app.use(resolve.urlPath(urlPath), serveFn);
|
|
};
|
|
});
|
|
|
|
const jsRE = /\.js$/;
|
|
const cssRE = /\.css$/;
|
|
const woffRE = /\.woff$/;
|
|
const woff2RE = /\.woff2$/;
|
|
const gifRE = /\.gif$/;
|
|
const jpgRE = /\.jpe?g$/;
|
|
const pngRE = /\.png$/;
|
|
|
|
/**
|
|
* Should return a String with HTML output
|
|
* (if any) for preloading indicated file
|
|
*/
|
|
export const renderPreloadTag = defineSsrRenderPreloadTag((file/* , { ssrContext } */) =>
|
|
{
|
|
if (jsRE.test(file) === true)
|
|
{
|
|
return `<link rel="modulepreload" href="${file}" crossorigin>`;
|
|
}
|
|
|
|
if (cssRE.test(file) === true)
|
|
{
|
|
return `<link rel="stylesheet" href="${file}" crossorigin>`;
|
|
}
|
|
|
|
if (woffRE.test(file) === true)
|
|
{
|
|
return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`;
|
|
}
|
|
|
|
if (woff2RE.test(file) === true)
|
|
{
|
|
return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`;
|
|
}
|
|
|
|
if (gifRE.test(file) === true)
|
|
{
|
|
return `<link rel="preload" href="${file}" as="image" type="image/gif" crossorigin>`;
|
|
}
|
|
|
|
if (jpgRE.test(file) === true)
|
|
{
|
|
return `<link rel="preload" href="${file}" as="image" type="image/jpeg" crossorigin>`;
|
|
}
|
|
|
|
if (pngRE.test(file) === true)
|
|
{
|
|
return `<link rel="preload" href="${file}" as="image" type="image/png" crossorigin>`;
|
|
}
|
|
|
|
return '';
|
|
});
|