Initial Version of sitemap.xml spec

This commit is contained in:
2026-03-06 23:34:00 -06:00
parent fec5bfa5c7
commit e9495f65b5
41 changed files with 10665 additions and 35 deletions

212
src/server.js Normal file
View File

@@ -0,0 +1,212 @@
import http from 'node:http';
import { join } from 'node:path';
import { readFileSync, readdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';
import jwt from 'jsonwebtoken';
import { create as xmlBuilder } from 'xmlbuilder2';
import { logger } from './logger.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Note: crypto is already available as globalThis.crypto (Web Crypto API)
// No need to import or set it - Node.js provides it by default
// Replace global console with custom logger
globalThis.console = logger;
// Make libraries available globally for proxy.js
globalThis.axios = axios;
globalThis.uuidv4 = uuidv4;
globalThis.jwt = jwt;
globalThis.xmlBuilder = xmlBuilder;
/**
* Load all JSON files from global/ directory and make them available as global objects
* Pattern: global/filename.json -> globalThis['filename']
*/
function loadGlobalObjects() {
const globalDir = join(__dirname, '..', 'global');
try {
const files = readdirSync(globalDir).filter(f => f.endsWith('.json') && !f.endsWith('.example'));
files.forEach(file => {
const objectName = file.replace('.json', '');
const filePath = join(globalDir, file);
try {
const content = readFileSync(filePath, 'utf-8');
const data = JSON.parse(content);
globalThis[objectName] = data;
logger.info(`Loaded global object: ${objectName}`, {
file: file,
keys: Object.keys(data)
});
} catch (error) {
logger.error(`Failed to load global object from ${file}`, {
error: error.message
});
throw error;
}
});
logger.info(`Loaded ${files.length} global objects from ${globalDir}`);
} catch (error) {
logger.error('Failed to load global objects', {
directory: globalDir,
error: error.message
});
throw error;
}
}
/**
* Load configuration from config/default.json
* Merges with environment variables (ENV takes precedence)
*
* @returns {Object} Configuration object
*/
function loadConfig() {
const configPath = join(__dirname, '..', 'config', 'default.json');
const configData = readFileSync(configPath, 'utf-8');
const config = JSON.parse(configData);
// Merge environment variables (ENV vars take precedence)
config.server.port = process.env.PORT ? parseInt(process.env.PORT, 10) : config.server.port;
config.server.host = process.env.HOST || config.server.host;
config.logging.level = process.env.LOG_LEVEL || config.logging.level;
return config;
}
/**
* Validate configuration
* @param {Object} config - Configuration object
* @throws {Error} If configuration is invalid
*/
function validateConfig(config) {
const errors = [];
// Validate server configuration
if (!config.server.port || config.server.port < 1 || config.server.port > 65535) {
errors.push('Invalid server.port (must be 1-65535)');
}
// Validate consolidated Google Drive settings from global
const settings = globalThis['google_drive_settings'];
if (!settings) {
errors.push('Missing google_drive_settings in global/ directory (required for all functionality)');
} else {
// Validate service account
if (!settings.serviceAccount) {
errors.push('Missing serviceAccount in google_drive_settings');
} else {
if (!settings.serviceAccount.client_email || !settings.serviceAccount.private_key) {
errors.push('Invalid serviceAccount format - missing client_email or private_key');
}
}
// Validate scopes (optional, will use default if missing)
if (settings.scopes) {
if (!Array.isArray(settings.scopes) || settings.scopes.length === 0) {
errors.push('Invalid scopes (must be a non-empty array)');
}
} else {
logger.warn('No scopes found in google_drive_settings - using default: ["https://www.googleapis.com/auth/drive.readonly"]');
}
// Validate sitemap config (optional)
if (settings.sitemap) {
if (settings.sitemap.maxUrls && (settings.sitemap.maxUrls < 1 || settings.sitemap.maxUrls > 50000)) {
errors.push('Invalid sitemap.maxUrls (must be 1-50000)');
}
} else {
logger.warn('No sitemap config found in google_drive_settings - using default maxUrls: 50000');
}
// Validate drive query (optional)
if (settings.driveQuery) {
if (typeof settings.driveQuery !== 'string') {
errors.push('Invalid driveQuery (must be a string)');
}
} else {
logger.warn('No driveQuery found in google_drive_settings - using default: "trashed = false"');
}
}
if (errors.length > 0) {
throw new Error(`Configuration validation failed:\n${errors.join('\n')}`);
}
}
/**
* Start the HTTP server
*/
async function startServer() {
try {
// Load configuration into global.config
global.config = loadConfig();
// Load global objects from global/ directory (e.g., service account keys)
loadGlobalObjects();
logger.info('Starting Proxy Script Server...');
logger.info(`Configuration loaded: ${JSON.stringify({
port: global.config.server.port,
host: global.config.server.host,
logLevel: global.config.logging.level
})}`);
// Validate configuration
validateConfig(global.config);
logger.info('Configuration validated successfully');
// Import proxy after global.config is set
const { handleRequest } = await import('./proxy.js');
// Create HTTP server that delegates all requests to proxy
const server = http.createServer((req, res) => {
handleRequest(req, res);
});
// Graceful shutdown
const shutdown = () => {
logger.info('\nShutting down gracefully...');
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
// Force shutdown after 10 seconds
setTimeout(() => {
logger.error('Forced shutdown after timeout');
process.exit(1);
}, 10000);
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
// Start listening
server.listen(global.config.server.port, global.config.server.host, () => {
logger.info('Server listening', {
port: global.config.server.port,
host: global.config.server.host,
});
});
} catch (error) {
logger.error('Failed to start server', {
error: error.message,
stack: error.stack
});
process.exit(1);
}
}
// Start the server
startServer();