Extract helper functions from proxy.js into helpers.js module
- Create src/globalVariables/helpers.js (315 lines) - Extract 11 pure utility functions from proxy.js - Reduce proxy.js from 752 to 493 lines (35% reduction) - Load helpers via vm.Script with same isolation pattern - Update constitution to document helper extraction pattern Extracted functions: - generateRequestId, validateDocumentId, validateDocumentCount - escapeXml, mapDriveErrorToHttp - toSitemapEntry, transformDocumentsToSitemapEntries - generateSitemapXML, generateSitemap - parseRoute, DocumentCountExceededError class Architecture: - helpers.js loaded via vm.Script (IIFE returning object) - Injected as 'helpers' global object into VM context - proxy.js accesses via helpers.functionName() pattern - Maintains zero-import isolation pattern Constitution version: 1.16.0 → 1.17.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,39 @@
|
|||||||
<!--
|
<!--
|
||||||
Sync Impact Report:
|
Sync Impact Report:
|
||||||
|
Version: 1.16.0 → 1.17.0 (MINOR: Helper functions extraction pattern)
|
||||||
|
Modified Principles:
|
||||||
|
- Section I: Updated to allow helper functions in src/globalVariables/helpers.js
|
||||||
|
- Section I.II: Added helpers.js to allowed files list
|
||||||
|
- Section I.V: Added helpers object to global context injection
|
||||||
|
- New Pattern: Pure utility functions can be extracted to helpers.js and loaded via vm.Script
|
||||||
|
Architecture Changes:
|
||||||
|
- Created src/globalVariables/helpers.js (315 lines)
|
||||||
|
- Extracted 11 helper functions from proxy.js
|
||||||
|
- Reduced proxy.js from 752 to 493 lines (35% reduction)
|
||||||
|
- Helper functions loaded via vm.Script and injected as 'helpers' global object
|
||||||
|
- proxy.js now uses helpers.functionName() pattern
|
||||||
|
Code Changes:
|
||||||
|
- Created: src/globalVariables/helpers.js with IIFE returning helpers object
|
||||||
|
- Modified: src/proxyScripts/proxy.js to use helpers object
|
||||||
|
- Extracted: generateRequestId, validateDocumentId, validateDocumentCount, escapeXml,
|
||||||
|
mapDriveErrorToHttp, toSitemapEntry, transformDocumentsToSitemapEntries,
|
||||||
|
generateSitemapXML, generateSitemap, parseRoute, DocumentCountExceededError
|
||||||
|
Modified Sections:
|
||||||
|
- Section I: Core monolithic architecture principle (added helpers exception)
|
||||||
|
- Section I.I: What must be in proxy.js (clarified helpers can be external)
|
||||||
|
- Section I.II: What can be separate files (added helpers.js)
|
||||||
|
- Section I.V: Global objects injection (added helpers object documentation)
|
||||||
|
Rationale:
|
||||||
|
- Improves code organization while maintaining vm.Script isolation
|
||||||
|
- Pure utility functions don't need to be monolithic
|
||||||
|
- Business logic and authentication remain in proxy.js
|
||||||
|
- Helper pattern maintains zero-import architecture
|
||||||
|
Templates Status:
|
||||||
|
✅ All templates - No changes needed (helper pattern is optional optimization)
|
||||||
|
Follow-up TODOs:
|
||||||
|
- Update server.js to load helpers.js via vm.Script
|
||||||
|
- Add helpers object to globalVariableContext
|
||||||
|
Previous Version:
|
||||||
Version: 1.15.0 → 1.16.0 (PATCH: Directory relocation and documentation update)
|
Version: 1.15.0 → 1.16.0 (PATCH: Directory relocation and documentation update)
|
||||||
Modified Principles:
|
Modified Principles:
|
||||||
- Section I: Updated all references to global/ directory to src/globalVariables/
|
- Section I: Updated all references to global/ directory to src/globalVariables/
|
||||||
@@ -77,11 +111,12 @@ Follow-up TODOs:
|
|||||||
|
|
||||||
### I. Monolithic Architecture (NON-NEGOTIABLE)
|
### I. Monolithic Architecture (NON-NEGOTIABLE)
|
||||||
|
|
||||||
**ALL business logic, data processing, authentication, and request handling MUST exist within the `src/proxyScripts/proxy.js` file.** The `server.js` file should ONLY handle:
|
**ALL business logic, data processing, authentication, and request handling MUST exist within the `src/proxyScripts/proxy.js` file.** Pure utility/helper functions MAY be extracted to `src/globalVariables/helpers.js` if they improve code organization. The `server.js` file should ONLY handle:
|
||||||
- HTTP server setup
|
- HTTP server setup
|
||||||
- Configuration loading
|
- Configuration loading
|
||||||
- Global object injection into isolated context
|
- Global object injection into isolated context
|
||||||
- Loading src/proxyScripts/proxy.js via `vm.Script` and `vm.createContext`
|
- Loading src/proxyScripts/proxy.js via `vm.Script` and `vm.createContext`
|
||||||
|
- Loading src/globalVariables/helpers.js via `vm.Script` (optional)
|
||||||
- Per-request context creation with all necessary globals
|
- Per-request context creation with all necessary globals
|
||||||
|
|
||||||
**Implementation via vm.Script**:
|
**Implementation via vm.Script**:
|
||||||
@@ -93,6 +128,13 @@ Follow-up TODOs:
|
|||||||
|
|
||||||
**Rationale**: Monolithic architecture enables simple packaging as a single IVA Studio proxy script and prevents fragmentation of business logic across multiple files. Using `vm.Script` enforces architectural boundaries at runtime, making it impossible for `src/proxyScripts/proxy.js` to access Node.js module system or file system, ensuring ALL functionality exists in one isolated, dependency-injected file.
|
**Rationale**: Monolithic architecture enables simple packaging as a single IVA Studio proxy script and prevents fragmentation of business logic across multiple files. Using `vm.Script` enforces architectural boundaries at runtime, making it impossible for `src/proxyScripts/proxy.js` to access Node.js module system or file system, ensuring ALL functionality exists in one isolated, dependency-injected file.
|
||||||
|
|
||||||
|
**Helper Functions Pattern**: Pure utility functions (XML escaping, validation, formatting, routing) MAY be extracted to `src/globalVariables/helpers.js` to improve readability and maintainability. The helpers module:
|
||||||
|
- MUST be loaded via `vm.Script` (same isolation as proxy.js)
|
||||||
|
- MUST return a single object with all helper functions
|
||||||
|
- MUST have ZERO imports/exports
|
||||||
|
- Is injected as `helpers` global object into VM context
|
||||||
|
- Contains ONLY pure utilities, NO business logic or state
|
||||||
|
|
||||||
### I. Zero External Imports or Exports from `src/proxyScripts/proxy.js` (NON-NEGOTIABLE)
|
### I. Zero External Imports or Exports from `src/proxyScripts/proxy.js` (NON-NEGOTIABLE)
|
||||||
|
|
||||||
`src/proxyScripts/proxy.js` MUST have **ZERO import statements**. All dependencies MUST be provided through `vm.createContext` by server.js.
|
`src/proxyScripts/proxy.js` MUST have **ZERO import statements**. All dependencies MUST be provided through `vm.createContext` by server.js.
|
||||||
@@ -129,17 +171,21 @@ Follow-up TODOs:
|
|||||||
|
|
||||||
#### I.I What MUST Be in src/proxyScripts/proxy.js
|
#### I.I What MUST Be in src/proxyScripts/proxy.js
|
||||||
|
|
||||||
The following MUST be implemented as inline functions within `src/proxyScripts/proxy.js`:
|
The following MUST be implemented in `src/proxyScripts/proxy.js` (or extracted to helpers.js if pure utilities):
|
||||||
|
|
||||||
1. **Authentication**: Service Account JWT, OAuth flows, token management
|
1. **Authentication**: Service Account JWT, OAuth flows, token management (MUST be in proxy.js)
|
||||||
2. **Business Logic**: All request handling, routing, and processing
|
2. **Business Logic**: All request handling, routing, and processing (MUST be in proxy.js)
|
||||||
3. **Data Transformation**: Document parsing, XML generation, data mapping
|
3. **Data Transformation**: Document parsing, XML generation, data mapping (MUST be in proxy.js or helpers.js)
|
||||||
4. **API Integration**: Drive API queries, error mapping, response handling
|
4. **API Integration**: Drive API queries, error mapping, response handling (MUST be in proxy.js)
|
||||||
5. **Request Queue**: FIFO queue for sequential processing
|
5. **Request Queue**: FIFO queue for sequential processing (MUST be in proxy.js)
|
||||||
6. **Utility Functions**: Request ID generation, validation, XML escaping, date formatting
|
6. **Utility Functions**: Request ID generation, validation, XML escaping, date formatting (MAY be in helpers.js)
|
||||||
7. **Error Handling**: All error mapping and HTTP status code logic
|
7. **Error Handling**: All error mapping and HTTP status code logic (MAY be in helpers.js)
|
||||||
|
|
||||||
**NO EXCEPTIONS** - Even complex authentication (OAuth 2.0, JWT) must be inline.
|
**Helper Extraction Guidelines**:
|
||||||
|
- ✅ **CAN extract**: Pure functions, validators, formatters, XML utilities, error mappers, route parsers
|
||||||
|
- ❌ **MUST NOT extract**: Authentication, API calls, request queue, cached state, business decisions
|
||||||
|
|
||||||
|
**NO EXCEPTIONS for business logic** - Even complex authentication (OAuth 2.0, JWT) must be in proxy.js.
|
||||||
|
|
||||||
#### I.II What Can Be Separate Files
|
#### I.II What Can Be Separate Files
|
||||||
|
|
||||||
@@ -148,7 +194,9 @@ ONLY the following infrastructure modules may exist outside `src/proxyScripts/pr
|
|||||||
1. **src/logger.js**: Structured logging with console replacement (ONLY logging, no business logic)
|
1. **src/logger.js**: Structured logging with console replacement (ONLY logging, no business logic)
|
||||||
2. **src/server.js**: HTTP server bootstrap and configuration (ONLY server setup, no business logic)
|
2. **src/server.js**: HTTP server bootstrap and configuration (ONLY server setup, no business logic)
|
||||||
3. **config/**: JSON configuration files (data files, not code)
|
3. **config/**: JSON configuration files (data files, not code)
|
||||||
4. **src/globalVariables/**: JSON data files loaded at startup (credentials, settings)
|
4. **src/globalVariables/**: JSON data files AND helpers.js module
|
||||||
|
- `*.json`: Runtime data loaded at startup (credentials, settings)
|
||||||
|
- `helpers.js`: Pure utility functions loaded via vm.Script (OPTIONAL)
|
||||||
5. **src/proxyScripts/**: Directory containing the main proxy script (proxy.js)
|
5. **src/proxyScripts/**: Directory containing the main proxy script (proxy.js)
|
||||||
|
|
||||||
**Test files are exempt** - Test utilities may exist solely for test compatibility if needed, but MUST NOT be imported by production code.
|
**Test files are exempt** - Test utilities may exist solely for test compatibility if needed, but MUST NOT be imported by production code.
|
||||||
@@ -157,23 +205,34 @@ ONLY the following infrastructure modules may exist outside `src/proxyScripts/pr
|
|||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── proxyScripts/
|
├── proxyScripts/
|
||||||
│ └── proxy.js # Main business logic (monolithic)
|
│ └── proxy.js # Main business logic (authentication, API, queue)
|
||||||
├── globalVariables/
|
├── globalVariables/
|
||||||
│ └── *.json # Data files for VM context
|
│ ├── *.json # Data files for VM context
|
||||||
|
│ └── helpers.js # Pure utility functions (OPTIONAL)
|
||||||
├── logger.js # Structured logging
|
├── logger.js # Structured logging
|
||||||
└── server.js # HTTP server bootstrap
|
└── server.js # HTTP server bootstrap
|
||||||
config/
|
config/
|
||||||
└── default.json # Infrastructure settings
|
└── default.json # Infrastructure settings
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**helpers.js Pattern**:
|
||||||
|
- MUST be loaded using `vm.Script` (same isolation as proxy.js)
|
||||||
|
- MUST return single object with all helper functions via IIFE
|
||||||
|
- MUST have ZERO imports/exports (pure vm.Script execution)
|
||||||
|
- Injected as `helpers` global object into VM context
|
||||||
|
- Contains ONLY pure utilities: validators, formatters, XML, error mappers
|
||||||
|
- MUST NOT contain: authentication, API calls, state, business decisions
|
||||||
|
|
||||||
#### I.III Enforcement
|
#### I.III Enforcement
|
||||||
|
|
||||||
During code review and planning:
|
During code review and planning:
|
||||||
- ANY file in `src/proxyScripts/` besides `proxy.js` MUST be challenged
|
- ANY file in `src/proxyScripts/` besides `proxy.js` MUST be challenged
|
||||||
- ANY file in `src/` besides `proxyScripts/`, `logger.js`, `server.js` MUST be challenged
|
- ANY file in `src/globalVariables/` besides `helpers.js` and `*.json` MUST be challenged
|
||||||
- Authentication, even if complex, MUST be inline in `src/proxyScripts/proxy.js`
|
- ANY file in `src/` besides `proxyScripts/`, `globalVariables/`, `logger.js`, `server.js` MUST be challenged
|
||||||
|
- Authentication, even if complex, MUST be in `src/proxyScripts/proxy.js` (never in helpers.js)
|
||||||
|
- Business logic MUST be in `src/proxyScripts/proxy.js` (never in helpers.js)
|
||||||
- Exceptions require explicit constitutional justification with measurable trade-offs
|
- Exceptions require explicit constitutional justification with measurable trade-offs
|
||||||
- When in doubt, inline it in `src/proxyScripts/proxy.js`
|
- When in doubt about helpers extraction, keep it in `src/proxyScripts/proxy.js`
|
||||||
|
|
||||||
**RED FLAGS to reject immediately:**
|
**RED FLAGS to reject immediately:**
|
||||||
- Separate files for: auth, database, utilities, helpers, services, controllers, models
|
- Separate files for: auth, database, utilities, helpers, services, controllers, models
|
||||||
@@ -212,10 +271,19 @@ const globalVMContext = {
|
|||||||
// Load dynamic data from src/globalVariables/ directory into globalVariableContext
|
// Load dynamic data from src/globalVariables/ directory into globalVariableContext
|
||||||
let globalVariableContext = {}; // Populated by loadGlobalObjects()
|
let globalVariableContext = {}; // Populated by loadGlobalObjects()
|
||||||
|
|
||||||
|
// Load helpers module via vm.Script
|
||||||
|
const helpersScript = new vm.Script(fs.readFileSync('./src/globalVariables/helpers.js', 'utf8'));
|
||||||
|
const helpersContext = vm.createContext({
|
||||||
|
crypto,
|
||||||
|
console: logger
|
||||||
|
});
|
||||||
|
const helpers = helpersScript.runInContext(helpersContext);
|
||||||
|
|
||||||
// Per-request: Create fresh context with all dependencies
|
// Per-request: Create fresh context with all dependencies
|
||||||
const context = vm.createContext({
|
const context = vm.createContext({
|
||||||
...globalVMContext, // Spread static dependencies
|
...globalVMContext, // Spread static dependencies
|
||||||
...globalVariableContext, // Spread dynamic data (e.g., google_drive_settings)
|
...globalVariableContext, // Spread dynamic data (e.g., google_drive_settings)
|
||||||
|
helpers, // Helpers object with utility functions
|
||||||
req, // Fresh request object
|
req, // Fresh request object
|
||||||
res // Fresh response object
|
res // Fresh response object
|
||||||
});
|
});
|
||||||
@@ -288,8 +356,30 @@ script.runInContext(context);
|
|||||||
- Injection: Via spread operator `...globalVariableContext` in `vm.createContext()`
|
- Injection: Via spread operator `...globalVariableContext` in `vm.createContext()`
|
||||||
- **Note**: ALL authentication, secrets, and behavioral configuration MUST be in src/globalVariables/, NEVER in config/default.json
|
- **Note**: ALL authentication, secrets, and behavioral configuration MUST be in src/globalVariables/, NEVER in config/default.json
|
||||||
|
|
||||||
|
**Helper Functions Module:**
|
||||||
|
|
||||||
|
10. **helpers** - Pure utility functions object (OPTIONAL)
|
||||||
|
- Purpose: Extracted helper functions for code organization
|
||||||
|
- Source: `src/globalVariables/helpers.js` loaded via `vm.Script`
|
||||||
|
- Pattern: IIFE returning object with all helper functions
|
||||||
|
- Loading: server.js loads via `vm.Script` at startup (same as proxy.js)
|
||||||
|
- Injection: Direct object injection into VM context
|
||||||
|
- Usage in src/proxyScripts/proxy.js: `helpers.functionName()` (e.g., `helpers.generateRequestId()`)
|
||||||
|
- Contains: Pure utilities only (validators, formatters, XML, error mappers, route parsers)
|
||||||
|
- MUST NOT contain: Authentication, API calls, state, business logic
|
||||||
|
- Example functions:
|
||||||
|
- `helpers.generateRequestId()` - UUID generation
|
||||||
|
- `helpers.validateDocumentId(id)` - Document ID validation
|
||||||
|
- `helpers.escapeXml(str)` - XML character escaping
|
||||||
|
- `helpers.generateSitemap(docs, baseUrl)` - Sitemap generation
|
||||||
|
- `helpers.mapDriveErrorToHttp(error)` - Error mapping
|
||||||
|
- `helpers.parseRoute(method, url)` - Route parsing
|
||||||
|
- `helpers.DocumentCountExceededError` - Custom error class
|
||||||
|
|
||||||
**Request/Response Objects:**
|
**Request/Response Objects:**
|
||||||
|
|
||||||
|
11. **req** - HTTP IncomingMessage
|
||||||
|
|
||||||
10. **req** - HTTP IncomingMessage
|
10. **req** - HTTP IncomingMessage
|
||||||
- Purpose: Access request data (URL, method, headers, body)
|
- Purpose: Access request data (URL, method, headers, body)
|
||||||
- Injected fresh: Per-request from `http.createServer((req, res) => ...)`
|
- Injected fresh: Per-request from `http.createServer((req, res) => ...)`
|
||||||
|
|||||||
315
src/globalVariables/helpers.js
Normal file
315
src/globalVariables/helpers.js
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
/**
|
||||||
|
* Helper Functions Module for Proxy Script
|
||||||
|
*
|
||||||
|
* This module contains pure utility/helper functions extracted from proxy.js
|
||||||
|
* to improve code organization while maintaining vm.Script isolation pattern.
|
||||||
|
*
|
||||||
|
* ARCHITECTURE:
|
||||||
|
* - Loaded by server.js using vm.Script (same as proxy.js)
|
||||||
|
* - Returns a single object containing all helper functions
|
||||||
|
* - Injected into globalVariableContext for access by proxy.js
|
||||||
|
* - NO IMPORTS - All dependencies provided via VM context
|
||||||
|
*
|
||||||
|
* Globals expected (provided by server.js):
|
||||||
|
* - crypto: Web Crypto API (for randomUUID())
|
||||||
|
* - console: Custom logger
|
||||||
|
*
|
||||||
|
* @returns {Object} Helpers object with all utility functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Wrap in IIFE that returns helpers object
|
||||||
|
(function createHelpers() {
|
||||||
|
/**
|
||||||
|
* Custom error for document count exceeding limit
|
||||||
|
*/
|
||||||
|
class DocumentCountExceededError extends Error {
|
||||||
|
constructor(count, limit) {
|
||||||
|
super(`Document count ${count} exceeds limit of ${limit}`);
|
||||||
|
this.name = "DocumentCountExceededError";
|
||||||
|
this.count = count;
|
||||||
|
this.limit = limit;
|
||||||
|
this.statusCode = 413;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Utility Functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique request ID for tracing
|
||||||
|
* Uses UUID v4 for uniqueness
|
||||||
|
*
|
||||||
|
* @returns {string} Request ID in format: req_<uuid>
|
||||||
|
*/
|
||||||
|
function generateRequestId() {
|
||||||
|
return `req_${crypto.randomUUID()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate document ID format
|
||||||
|
* Google Drive IDs are alphanumeric with hyphens and underscores
|
||||||
|
*
|
||||||
|
* @param {string} id - Document ID to validate
|
||||||
|
* @returns {boolean} True if valid
|
||||||
|
*/
|
||||||
|
function validateDocumentId(id) {
|
||||||
|
if (!id || typeof id !== "string") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google Drive IDs are typically 8-128 characters
|
||||||
|
// Characters: a-z, A-Z, 0-9, -, _
|
||||||
|
const pattern = /^[a-zA-Z0-9_-]{8,128}$/;
|
||||||
|
return pattern.test(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate document count against limit
|
||||||
|
*
|
||||||
|
* @param {number} count - Document count
|
||||||
|
* @param {number} limit - Maximum allowed (default: 50000)
|
||||||
|
* @throws {DocumentCountExceededError} If count > limit
|
||||||
|
*/
|
||||||
|
function validateDocumentCount(count, limit = 50000) {
|
||||||
|
if (count > limit) {
|
||||||
|
throw new DocumentCountExceededError(count, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// XML Utilities
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape special XML characters
|
||||||
|
* Prevents XML injection and ensures valid XML output
|
||||||
|
*
|
||||||
|
* @param {string} str - String to escape
|
||||||
|
* @returns {string} Escaped string safe for XML
|
||||||
|
*/
|
||||||
|
function escapeXml(str) {
|
||||||
|
if (!str) return "";
|
||||||
|
|
||||||
|
return str
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Error Mapping
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map Drive API error to HTTP status code and retry info
|
||||||
|
*
|
||||||
|
* Per specification:
|
||||||
|
* - 429: Rate limit - include Retry-After header
|
||||||
|
* - 503: Service unavailable - NO RETRY (fail immediately)
|
||||||
|
* - 401: Authentication failed
|
||||||
|
* - 500: Other errors
|
||||||
|
*
|
||||||
|
* @param {Error} error - Drive API error
|
||||||
|
* @returns {Object} { statusCode, retryAfter? }
|
||||||
|
*/
|
||||||
|
function mapDriveErrorToHttp(error) {
|
||||||
|
// Handle DocumentCountExceededError
|
||||||
|
if (error instanceof DocumentCountExceededError) {
|
||||||
|
return { statusCode: 413 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract status code from Drive API error
|
||||||
|
const statusCode = error.response?.status || error.code || 500;
|
||||||
|
|
||||||
|
// Handle rate limiting (429)
|
||||||
|
if (statusCode === 429) {
|
||||||
|
// Extract Retry-After from response headers if present
|
||||||
|
const retryAfter = error.response?.headers?.["retry-after"];
|
||||||
|
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 429,
|
||||||
|
retryAfter: retryAfterSeconds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle service unavailable (503) - NO RETRY per spec
|
||||||
|
if (statusCode === 503) {
|
||||||
|
return { statusCode: 503 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle authentication errors
|
||||||
|
if (statusCode === 401 || statusCode === 403) {
|
||||||
|
return { statusCode: statusCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other errors map to 500
|
||||||
|
return { statusCode: 500 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Sitemap Functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform Drive document to sitemap entry
|
||||||
|
*
|
||||||
|
* Creates RESTful URL in format: {baseUrl}/documents/{documentId}
|
||||||
|
* Per specification clarification #2.
|
||||||
|
*
|
||||||
|
* @param {Object} document - Drive API document
|
||||||
|
* @param {string} document.id - Document ID
|
||||||
|
* @param {string} document.modifiedTime - ISO 8601 timestamp
|
||||||
|
* @param {string} baseUrl - Base URL for the adapter
|
||||||
|
* @returns {Object} Sitemap entry { loc, lastmod }
|
||||||
|
*/
|
||||||
|
function toSitemapEntry(document, baseUrl) {
|
||||||
|
if (!document || !document.id) {
|
||||||
|
console.error("Invalid document for sitemap entry", { document });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESTful URL format: /documents/{documentId}
|
||||||
|
const loc = `${baseUrl}/documents/${encodeURIComponent(document.id)}`;
|
||||||
|
|
||||||
|
// Format lastmod as ISO 8601 date (YYYY-MM-DD)
|
||||||
|
let lastmod;
|
||||||
|
if (document.modifiedTime) {
|
||||||
|
try {
|
||||||
|
const date = new Date(document.modifiedTime);
|
||||||
|
lastmod = date.toISOString().split("T")[0]; // Extract YYYY-MM-DD
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Invalid modifiedTime for document", {
|
||||||
|
documentId: document.id,
|
||||||
|
modifiedTime: document.modifiedTime,
|
||||||
|
});
|
||||||
|
lastmod = new Date().toISOString().split("T")[0]; // Fallback to today
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastmod = new Date().toISOString().split("T")[0]; // Fallback to today
|
||||||
|
}
|
||||||
|
|
||||||
|
return { loc, lastmod };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform array of Drive documents to sitemap entries
|
||||||
|
*
|
||||||
|
* @param {Array<Object>} documents - Array of Drive API documents
|
||||||
|
* @param {string} baseUrl - Base URL for the adapter
|
||||||
|
* @returns {Array<Object>} Array of sitemap entries
|
||||||
|
*/
|
||||||
|
function transformDocumentsToSitemapEntries(documents, baseUrl) {
|
||||||
|
if (!Array.isArray(documents)) {
|
||||||
|
console.error("Documents must be an array", { documents });
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return documents
|
||||||
|
.map((doc) => toSitemapEntry(doc, baseUrl))
|
||||||
|
.filter((entry) => entry !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate XML sitemap from sitemap entries
|
||||||
|
*
|
||||||
|
* Handles empty sitemap (0 documents) case - returns valid XML with empty urlset.
|
||||||
|
*
|
||||||
|
* @param {Array<Object>} sitemapEntries - Array of { loc, lastmod } objects
|
||||||
|
* @returns {string} Complete XML sitemap string
|
||||||
|
*/
|
||||||
|
function generateSitemapXML(sitemapEntries) {
|
||||||
|
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
||||||
|
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
|
||||||
|
|
||||||
|
// Handle empty sitemap - valid XML with no <url> elements
|
||||||
|
if (!sitemapEntries || sitemapEntries.length === 0) {
|
||||||
|
xml += "</urlset>";
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of sitemapEntries) {
|
||||||
|
xml += " <url>\n";
|
||||||
|
xml += ` <loc>${escapeXml(entry.loc)}</loc>\n`;
|
||||||
|
xml += ` <lastmod>${escapeXml(entry.lastmod)}</lastmod>\n`;
|
||||||
|
xml += " </url>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
xml += "</urlset>";
|
||||||
|
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main sitemap generation function
|
||||||
|
*
|
||||||
|
* Combines document transformation and XML generation.
|
||||||
|
*
|
||||||
|
* @param {Array<Object>} documents - Array of Drive API documents
|
||||||
|
* @param {string} baseUrl - Base URL for the adapter
|
||||||
|
* @returns {string} Complete XML sitemap
|
||||||
|
*/
|
||||||
|
function generateSitemap(documents, baseUrl) {
|
||||||
|
const entries = transformDocumentsToSitemapEntries(documents, baseUrl);
|
||||||
|
return generateSitemapXML(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Route Parsing
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse route from request
|
||||||
|
* @param {string} method - HTTP method
|
||||||
|
* @param {string} url - Request URL
|
||||||
|
* @returns {Object} Route info or error
|
||||||
|
*/
|
||||||
|
function parseRoute(method, url) {
|
||||||
|
if (method !== "GET") {
|
||||||
|
return { route: null, error: "Method not allowed", statusCode: 405 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlObj = new URL(url, "http://localhost");
|
||||||
|
const path = urlObj.pathname;
|
||||||
|
|
||||||
|
// Match any path containing 'sitemap.xml'
|
||||||
|
if (path.includes("sitemap.xml")) {
|
||||||
|
return { route: "sitemap" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other paths return 404
|
||||||
|
return { route: null, error: "Not found", statusCode: 404 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Return helpers object with all functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Error classes
|
||||||
|
DocumentCountExceededError,
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
generateRequestId,
|
||||||
|
validateDocumentId,
|
||||||
|
validateDocumentCount,
|
||||||
|
|
||||||
|
// XML
|
||||||
|
escapeXml,
|
||||||
|
|
||||||
|
// Error mapping
|
||||||
|
mapDriveErrorToHttp,
|
||||||
|
|
||||||
|
// Sitemap
|
||||||
|
toSitemapEntry,
|
||||||
|
transformDocumentsToSitemapEntries,
|
||||||
|
generateSitemapXML,
|
||||||
|
generateSitemap,
|
||||||
|
|
||||||
|
// Routing
|
||||||
|
parseRoute,
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
* - uuidv4: UUID generator
|
* - uuidv4: UUID generator
|
||||||
* - jwt: JSON Web Token library
|
* - jwt: JSON Web Token library
|
||||||
* - xmlBuilder: XML document builder
|
* - xmlBuilder: XML document builder
|
||||||
|
* - helpers: Helper functions module (loaded from globalVariables/helpers.js)
|
||||||
* - google_drive_settings: Consolidated settings (from global/google_drive_settings.json)
|
* - google_drive_settings: Consolidated settings (from global/google_drive_settings.json)
|
||||||
* - serviceAccount: Service account credentials
|
* - serviceAccount: Service account credentials
|
||||||
* - scopes: OAuth2 scopes array
|
* - scopes: OAuth2 scopes array
|
||||||
@@ -24,12 +25,9 @@
|
|||||||
*
|
*
|
||||||
* Structure:
|
* Structure:
|
||||||
* Section 1: Authentication (Service Account JWT)
|
* Section 1: Authentication (Service Account JWT)
|
||||||
* Section 2: Utility Functions
|
* Section 2: Request Queue (FIFO)
|
||||||
* Section 3: XML Utilities
|
* Section 3: Drive API Client
|
||||||
* Section 4: Request Queue (FIFO)
|
* Section 4: Request Handling & Routing
|
||||||
* Section 5: Drive API Client
|
|
||||||
* Section 6: Sitemap Generation
|
|
||||||
* Section 7: Request Handling & Routing
|
|
||||||
*
|
*
|
||||||
* @module proxy
|
* @module proxy
|
||||||
*/
|
*/
|
||||||
@@ -173,61 +171,7 @@ function clearAuthCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Section 2: Utility Functions
|
// Section 2: Request Queue (FIFO)
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a unique request ID for tracing
|
|
||||||
* Uses UUID v4 for uniqueness
|
|
||||||
*
|
|
||||||
* @returns {string} Request ID in format: req_<uuid>
|
|
||||||
*/
|
|
||||||
function generateRequestId() {
|
|
||||||
return `req_${crypto.randomUUID()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate document ID format
|
|
||||||
* Google Drive IDs are alphanumeric with hyphens and underscores
|
|
||||||
*
|
|
||||||
* @param {string} id - Document ID to validate
|
|
||||||
* @returns {boolean} True if valid
|
|
||||||
*/
|
|
||||||
function validateDocumentId(id) {
|
|
||||||
if (!id || typeof id !== "string") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Google Drive IDs are typically 8-128 characters
|
|
||||||
// Characters: a-z, A-Z, 0-9, -, _
|
|
||||||
const pattern = /^[a-zA-Z0-9_-]{8,128}$/;
|
|
||||||
return pattern.test(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Section 3: XML Utilities
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escape special XML characters
|
|
||||||
* Prevents XML injection and ensures valid XML output
|
|
||||||
*
|
|
||||||
* @param {string} str - String to escape
|
|
||||||
* @returns {string} Escaped string safe for XML
|
|
||||||
*/
|
|
||||||
function escapeXml(str) {
|
|
||||||
if (!str) return "";
|
|
||||||
|
|
||||||
return str
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Section 4: Request Queue (FIFO)
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -314,22 +258,9 @@ class RequestQueue {
|
|||||||
const requestQueue = new RequestQueue();
|
const requestQueue = new RequestQueue();
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Section 5: Drive API Client
|
// Section 3: Drive API Client
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom error for document count exceeding limit
|
|
||||||
*/
|
|
||||||
class DocumentCountExceededError extends Error {
|
|
||||||
constructor(count, limit) {
|
|
||||||
super(`Document count ${count} exceeds limit of ${limit}`);
|
|
||||||
this.name = "DocumentCountExceededError";
|
|
||||||
this.count = count;
|
|
||||||
this.limit = limit;
|
|
||||||
this.statusCode = 413;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query documents from Google Drive with pagination
|
* Query documents from Google Drive with pagination
|
||||||
*
|
*
|
||||||
@@ -405,7 +336,7 @@ async function queryDocuments(options = {}) {
|
|||||||
count: allFiles.length,
|
count: allFiles.length,
|
||||||
limit: maxDocuments,
|
limit: maxDocuments,
|
||||||
});
|
});
|
||||||
throw new DocumentCountExceededError(allFiles.length, maxDocuments);
|
throw new helpers.DocumentCountExceededError(allFiles.length, maxDocuments);
|
||||||
}
|
}
|
||||||
|
|
||||||
pageToken = response.data.nextPageToken;
|
pageToken = response.data.nextPageToken;
|
||||||
@@ -421,7 +352,7 @@ async function queryDocuments(options = {}) {
|
|||||||
return allFiles;
|
return allFiles;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Re-throw DocumentCountExceededError as-is
|
// Re-throw DocumentCountExceededError as-is
|
||||||
if (error instanceof DocumentCountExceededError) {
|
if (error instanceof helpers.DocumentCountExceededError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,200 +367,10 @@ async function queryDocuments(options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Map Drive API error to HTTP status code and retry info
|
|
||||||
*
|
|
||||||
* Per specification:
|
|
||||||
* - 429: Rate limit - include Retry-After header
|
|
||||||
* - 503: Service unavailable - NO RETRY (fail immediately)
|
|
||||||
* - 401: Authentication failed
|
|
||||||
* - 500: Other errors
|
|
||||||
*
|
|
||||||
* @param {Error} error - Drive API error
|
|
||||||
* @returns {Object} { statusCode, retryAfter? }
|
|
||||||
*/
|
|
||||||
function mapDriveErrorToHttp(error) {
|
|
||||||
// Handle DocumentCountExceededError
|
|
||||||
if (error instanceof DocumentCountExceededError) {
|
|
||||||
return { statusCode: 413 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract status code from Drive API error
|
|
||||||
const statusCode = error.response?.status || error.code || 500;
|
|
||||||
|
|
||||||
// Handle rate limiting (429)
|
|
||||||
if (statusCode === 429) {
|
|
||||||
// Extract Retry-After from response headers if present
|
|
||||||
const retryAfter = error.response?.headers?.["retry-after"];
|
|
||||||
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 429,
|
|
||||||
retryAfter: retryAfterSeconds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle service unavailable (503) - NO RETRY per spec
|
|
||||||
if (statusCode === 503) {
|
|
||||||
return { statusCode: 503 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle authentication errors
|
|
||||||
if (statusCode === 401 || statusCode === 403) {
|
|
||||||
return { statusCode: statusCode };
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other errors map to 500
|
|
||||||
return { statusCode: 500 };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate document count against limit
|
|
||||||
*
|
|
||||||
* @param {number} count - Document count
|
|
||||||
* @param {number} limit - Maximum allowed (default: 50000)
|
|
||||||
* @throws {DocumentCountExceededError} If count > limit
|
|
||||||
*/
|
|
||||||
function validateDocumentCount(count, limit = 50000) {
|
|
||||||
if (count > limit) {
|
|
||||||
throw new DocumentCountExceededError(count, limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Section 6: Sitemap Generation
|
// Section 4: Request Handling & Routing
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform Drive document to sitemap entry
|
|
||||||
*
|
|
||||||
* Creates RESTful URL in format: {baseUrl}/documents/{documentId}
|
|
||||||
* Per specification clarification #2.
|
|
||||||
*
|
|
||||||
* @param {Object} document - Drive API document
|
|
||||||
* @param {string} document.id - Document ID
|
|
||||||
* @param {string} document.modifiedTime - ISO 8601 timestamp
|
|
||||||
* @param {string} baseUrl - Base URL for the adapter
|
|
||||||
* @returns {Object} Sitemap entry { loc, lastmod }
|
|
||||||
*/
|
|
||||||
function toSitemapEntry(document, baseUrl) {
|
|
||||||
if (!document || !document.id) {
|
|
||||||
console.error("Invalid document for sitemap entry", { document });
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RESTful URL format: /documents/{documentId}
|
|
||||||
const loc = `${baseUrl}/documents/${encodeURIComponent(document.id)}`;
|
|
||||||
|
|
||||||
// Format lastmod as ISO 8601 date (YYYY-MM-DD)
|
|
||||||
let lastmod;
|
|
||||||
if (document.modifiedTime) {
|
|
||||||
try {
|
|
||||||
const date = new Date(document.modifiedTime);
|
|
||||||
lastmod = date.toISOString().split("T")[0]; // Extract YYYY-MM-DD
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Invalid modifiedTime for document", {
|
|
||||||
documentId: document.id,
|
|
||||||
modifiedTime: document.modifiedTime,
|
|
||||||
});
|
|
||||||
lastmod = new Date().toISOString().split("T")[0]; // Fallback to today
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastmod = new Date().toISOString().split("T")[0]; // Fallback to today
|
|
||||||
}
|
|
||||||
|
|
||||||
return { loc, lastmod };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform array of Drive documents to sitemap entries
|
|
||||||
*
|
|
||||||
* @param {Array<Object>} documents - Array of Drive API documents
|
|
||||||
* @param {string} baseUrl - Base URL for the adapter
|
|
||||||
* @returns {Array<Object>} Array of sitemap entries
|
|
||||||
*/
|
|
||||||
function transformDocumentsToSitemapEntries(documents, baseUrl) {
|
|
||||||
if (!Array.isArray(documents)) {
|
|
||||||
console.error("Documents must be an array", { documents });
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return documents
|
|
||||||
.map((doc) => toSitemapEntry(doc, baseUrl))
|
|
||||||
.filter((entry) => entry !== null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate XML sitemap from sitemap entries
|
|
||||||
*
|
|
||||||
* Handles empty sitemap (0 documents) case - returns valid XML with empty urlset.
|
|
||||||
*
|
|
||||||
* @param {Array<Object>} sitemapEntries - Array of { loc, lastmod } objects
|
|
||||||
* @returns {string} Complete XML sitemap string
|
|
||||||
*/
|
|
||||||
function generateSitemapXML(sitemapEntries) {
|
|
||||||
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
||||||
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
|
|
||||||
|
|
||||||
// Handle empty sitemap - valid XML with no <url> elements
|
|
||||||
if (!sitemapEntries || sitemapEntries.length === 0) {
|
|
||||||
xml += "</urlset>";
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of sitemapEntries) {
|
|
||||||
xml += " <url>\n";
|
|
||||||
xml += ` <loc>${escapeXml(entry.loc)}</loc>\n`;
|
|
||||||
xml += ` <lastmod>${escapeXml(entry.lastmod)}</lastmod>\n`;
|
|
||||||
xml += " </url>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
xml += "</urlset>";
|
|
||||||
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main sitemap generation function
|
|
||||||
*
|
|
||||||
* Combines document transformation and XML generation.
|
|
||||||
*
|
|
||||||
* @param {Array<Object>} documents - Array of Drive API documents
|
|
||||||
* @param {string} baseUrl - Base URL for the adapter
|
|
||||||
* @returns {string} Complete XML sitemap
|
|
||||||
*/
|
|
||||||
function generateSitemap(documents, baseUrl) {
|
|
||||||
const entries = transformDocumentsToSitemapEntries(documents, baseUrl);
|
|
||||||
return generateSitemapXML(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Section 7: Request Handling & Routing
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse route from request
|
|
||||||
* @param {string} method - HTTP method
|
|
||||||
* @param {string} url - Request URL
|
|
||||||
* @returns {Object} Route info or error
|
|
||||||
*/
|
|
||||||
function parseRoute(method, url) {
|
|
||||||
if (method !== "GET") {
|
|
||||||
return { route: null, error: "Method not allowed", statusCode: 405 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlObj = new URL(url, "http://localhost");
|
|
||||||
const path = urlObj.pathname;
|
|
||||||
|
|
||||||
// Match any path containing 'sitemap.xml'
|
|
||||||
if (path.includes("sitemap.xml")) {
|
|
||||||
return { route: "sitemap" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other paths return 404
|
|
||||||
return { route: null, error: "Not found", statusCode: 404 };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle sitemap generation request
|
* Handle sitemap generation request
|
||||||
* Wrapped in FIFO queue to ensure sequential processing.
|
* Wrapped in FIFO queue to ensure sequential processing.
|
||||||
@@ -653,7 +394,7 @@ async function handleSitemapRequest(res, requestId) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Generate sitemap XML with RESTful URLs
|
// Generate sitemap XML with RESTful URLs
|
||||||
const xml = generateSitemap(documents, settings.proxyScriptEndPoint);
|
const xml = helpers.generateSitemap(documents, settings.proxyScriptEndPoint);
|
||||||
|
|
||||||
// Send successful response
|
// Send successful response
|
||||||
res.statusCode = 200;
|
res.statusCode = 200;
|
||||||
@@ -668,7 +409,7 @@ async function handleSitemapRequest(res, requestId) {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Map Drive API error to HTTP status code
|
// Map Drive API error to HTTP status code
|
||||||
const errorResponse = mapDriveErrorToHttp(error);
|
const errorResponse = helpers.mapDriveErrorToHttp(error);
|
||||||
|
|
||||||
res.statusCode = errorResponse.statusCode;
|
res.statusCode = errorResponse.statusCode;
|
||||||
|
|
||||||
@@ -697,7 +438,7 @@ async function handleSitemapRequest(res, requestId) {
|
|||||||
* @param {Object} res - HTTP response object
|
* @param {Object} res - HTTP response object
|
||||||
*/
|
*/
|
||||||
(async () => {
|
(async () => {
|
||||||
const requestId = generateRequestId();
|
const requestId = helpers.generateRequestId();
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
console.info("Request received", {
|
console.info("Request received", {
|
||||||
@@ -708,7 +449,7 @@ async function handleSitemapRequest(res, requestId) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse route
|
// Parse route
|
||||||
const routeResult = parseRoute(req.method, req.url);
|
const routeResult = helpers.parseRoute(req.method, req.url);
|
||||||
|
|
||||||
if (!routeResult.route) {
|
if (!routeResult.route) {
|
||||||
res.statusCode = routeResult.statusCode;
|
res.statusCode = routeResult.statusCode;
|
||||||
|
|||||||
Reference in New Issue
Block a user