Add analysis trigger
This commit is contained in:
50
server.ts
50
server.ts
@@ -2,7 +2,7 @@ import "source-map-support/register";
|
|||||||
import bodyParser from "body-parser";
|
import bodyParser from "body-parser";
|
||||||
import compression from "compression";
|
import compression from "compression";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import dotenv from "dotenv";
|
import dotenv, { config } from "dotenv";
|
||||||
import express, { Express } from "express";
|
import express, { Express } from "express";
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import morgan from "morgan";
|
import morgan from "morgan";
|
||||||
@@ -30,13 +30,13 @@ dotenv.config({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Global error handlers
|
// Global error handlers
|
||||||
process.on('uncaughtException', (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
console.error('Uncaught Exception:', error);
|
console.error("Uncaught Exception:", error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, promise) => {
|
process.on("unhandledRejection", (reason, promise) => {
|
||||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
console.error("Unhandled Rejection at:", promise, "reason:", reason);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,6 +121,46 @@ const morganMiddleware = morgan("combined", {
|
|||||||
});
|
});
|
||||||
app.use(morganMiddleware);
|
app.use(morganMiddleware);
|
||||||
|
|
||||||
|
//Asynchronously check the headers for a bodyshopid. If it exists, write it to a json file in the root directory. Only do this if the bodyshopid has not already been logged once since server start.
|
||||||
|
const loggedBodyshopIds = new Set<string>();
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
//Asynchronously check the headers for a bodyshopid. If it exists, write it to a json file in the root directory. Only do this if the bodyshopid has not already been logged once since server start.
|
||||||
|
const bodyshopId = req.headers.bodyshopid as string;
|
||||||
|
|
||||||
|
console.log("*** ~ loggedBodyshopIds:", loggedBodyshopIds);
|
||||||
|
if (bodyshopId && !loggedBodyshopIds.has(bodyshopId)) {
|
||||||
|
loggedBodyshopIds.add(bodyshopId);
|
||||||
|
|
||||||
|
// Asynchronously write to file without blocking the request
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const fs = await import("fs/promises");
|
||||||
|
const filePath = path.join(FolderPaths.Root, "config.json");
|
||||||
|
|
||||||
|
let existingIds: string[] = [];
|
||||||
|
try {
|
||||||
|
const fileContent = await fs.readFile(filePath, "utf-8");
|
||||||
|
const configFile = JSON.parse(fileContent);
|
||||||
|
existingIds = configFile.bodyshopIds || [];
|
||||||
|
} catch {
|
||||||
|
// File doesn't exist or is invalid, start with empty array
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existingIds.includes(bodyshopId)) {
|
||||||
|
existingIds.push(bodyshopId);
|
||||||
|
await fs.writeFile(filePath, JSON.stringify({ bodyshopIds: existingIds }, null, 2));
|
||||||
|
logger.info(`Logged new bodyshop ID: ${bodyshopId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to log bodyshop ID:", error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
// Job endpoints
|
// Job endpoints
|
||||||
app.post("/jobs/list", ValidateImsToken, validateJobRequest, JobsListMedia);
|
app.post("/jobs/list", ValidateImsToken, validateJobRequest, JobsListMedia);
|
||||||
app.post("/jobs/upload", ValidateImsToken, JobMediaUploadMulter.array("file"), validateJobRequest, jobsUploadMedia);
|
app.post("/jobs/upload", ValidateImsToken, JobMediaUploadMulter.array("file"), validateJobRequest, jobsUploadMedia);
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ export class DailyS3Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test S3 connection before starting scheduler
|
// Test S3 connection before starting scheduler
|
||||||
const connectionTest = await this.s3Sync.testConnection();
|
// const connectionTest = await this.s3Sync.testConnection();
|
||||||
if (!connectionTest) {
|
// if (!connectionTest) {
|
||||||
logger.error("S3 connection test failed. S3 sync scheduler will not be started.");
|
// logger.error("S3 connection test failed. S3 sync scheduler will not be started.");
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Cron expression for midnight PST
|
// Cron expression for midnight PST
|
||||||
// Note: This uses PST timezone. During PDT (daylight time), it will still run at midnight local time
|
// Note: This uses PST timezone. During PDT (daylight time), it will still run at midnight local time
|
||||||
@@ -35,10 +35,11 @@ export class DailyS3Scheduler {
|
|||||||
this.cronJob = cron.schedule(
|
this.cronJob = cron.schedule(
|
||||||
cronExpression,
|
cronExpression,
|
||||||
async () => {
|
async () => {
|
||||||
await this.performDailySync();
|
//await this.performDailySync();
|
||||||
|
await this.triggerJobAnalysis();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timezone: timezone,
|
timezone: timezone
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -92,6 +93,21 @@ export class DailyS3Scheduler {
|
|||||||
await this.performDailySync();
|
await this.performDailySync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async triggerJobAnalysis(): Promise<void> {
|
||||||
|
if (!this.s3Sync) {
|
||||||
|
logger.error("S3 sync not configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Triggering jobs directory analysis...");
|
||||||
|
try {
|
||||||
|
const analysis = await this.s3Sync.analyzeJobsDirectory();
|
||||||
|
logger.info("Jobs directory analysis completed:", analysis);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Jobs directory analysis failed:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the next scheduled run time
|
* Get the next scheduled run time
|
||||||
*/
|
*/
|
||||||
@@ -103,7 +119,7 @@ export class DailyS3Scheduler {
|
|||||||
// Create a date object for midnight PST today
|
// Create a date object for midnight PST today
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const pstNow = new Date(now.toLocaleString("en-US", { timeZone: "America/Los_Angeles" }));
|
const pstNow = new Date(now.toLocaleString("en-US", { timeZone: "America/Los_Angeles" }));
|
||||||
|
|
||||||
// If it's past midnight today, next run is tomorrow at midnight
|
// If it's past midnight today, next run is tomorrow at midnight
|
||||||
const nextRun = new Date(pstNow);
|
const nextRun = new Date(pstNow);
|
||||||
if (pstNow.getHours() > 0 || pstNow.getMinutes() > 0 || pstNow.getSeconds() > 0) {
|
if (pstNow.getHours() > 0 || pstNow.getMinutes() > 0 || pstNow.getSeconds() > 0) {
|
||||||
@@ -111,7 +127,7 @@ export class DailyS3Scheduler {
|
|||||||
}
|
}
|
||||||
nextRun.setHours(0, 0, 0, 0);
|
nextRun.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
return nextRun.toLocaleString("en-US", {
|
return nextRun.toLocaleString("en-US", {
|
||||||
timeZone: "America/Los_Angeles",
|
timeZone: "America/Los_Angeles",
|
||||||
weekday: "long",
|
weekday: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
@@ -145,10 +161,10 @@ export class DailyS3Scheduler {
|
|||||||
isConfigured: this.s3Sync !== null,
|
isConfigured: this.s3Sync !== null,
|
||||||
isRunning: this.cronJob !== null,
|
isRunning: this.cronJob !== null,
|
||||||
nextRun: this.getNextRunTime(),
|
nextRun: this.getNextRunTime(),
|
||||||
syncStats,
|
syncStats
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export a singleton instance
|
// Export a singleton instance
|
||||||
export const dailyS3Scheduler = new DailyS3Scheduler();
|
export const dailyS3Scheduler = new DailyS3Scheduler();
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ export class S3Sync {
|
|||||||
jobStats.push(folderStats);
|
jobStats.push(folderStats);
|
||||||
totalDocuments += folderStats.documentCount;
|
totalDocuments += folderStats.documentCount;
|
||||||
totalSizeBytes += folderStats.totalSizeBytes;
|
totalSizeBytes += folderStats.totalSizeBytes;
|
||||||
|
|
||||||
// Aggregate file type stats
|
// Aggregate file type stats
|
||||||
for (const [ext, count] of Object.entries(folderStats.fileTypeStats)) {
|
for (const [ext, count] of Object.entries(folderStats.fileTypeStats)) {
|
||||||
aggregatedFileTypeStats[ext] = (aggregatedFileTypeStats[ext] || 0) + count;
|
aggregatedFileTypeStats[ext] = (aggregatedFileTypeStats[ext] || 0) + count;
|
||||||
@@ -236,7 +236,9 @@ export class S3Sync {
|
|||||||
/**
|
/**
|
||||||
* Recursively get document count and total size for a directory
|
* Recursively get document count and total size for a directory
|
||||||
*/
|
*/
|
||||||
private async getDirectoryStats(dirPath: string): Promise<{ documentCount: number; totalSizeBytes: number; fileTypeStats: { [extension: string]: number } }> {
|
private async getDirectoryStats(
|
||||||
|
dirPath: string
|
||||||
|
): Promise<{ documentCount: number; totalSizeBytes: number; fileTypeStats: { [extension: string]: number } }> {
|
||||||
let documentCount = 0;
|
let documentCount = 0;
|
||||||
let totalSizeBytes = 0;
|
let totalSizeBytes = 0;
|
||||||
const fileTypeStats: { [extension: string]: number } = {};
|
const fileTypeStats: { [extension: string]: number } = {};
|
||||||
@@ -253,7 +255,7 @@ export class S3Sync {
|
|||||||
const subStats = await this.getDirectoryStats(itemPath);
|
const subStats = await this.getDirectoryStats(itemPath);
|
||||||
documentCount += subStats.documentCount;
|
documentCount += subStats.documentCount;
|
||||||
totalSizeBytes += subStats.totalSizeBytes;
|
totalSizeBytes += subStats.totalSizeBytes;
|
||||||
|
|
||||||
// Merge file type stats
|
// Merge file type stats
|
||||||
for (const [ext, count] of Object.entries(subStats.fileTypeStats)) {
|
for (const [ext, count] of Object.entries(subStats.fileTypeStats)) {
|
||||||
fileTypeStats[ext] = (fileTypeStats[ext] || 0) + count;
|
fileTypeStats[ext] = (fileTypeStats[ext] || 0) + count;
|
||||||
@@ -262,9 +264,9 @@ export class S3Sync {
|
|||||||
// Count files as documents
|
// Count files as documents
|
||||||
documentCount++;
|
documentCount++;
|
||||||
totalSizeBytes += stat.size;
|
totalSizeBytes += stat.size;
|
||||||
|
|
||||||
// Track file extension
|
// Track file extension
|
||||||
const ext = path.extname(item).toLowerCase() || 'no-extension';
|
const ext = path.extname(item).toLowerCase() || "no-extension";
|
||||||
fileTypeStats[ext] = (fileTypeStats[ext] || 0) + 1;
|
fileTypeStats[ext] = (fileTypeStats[ext] || 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,7 +342,7 @@ export async function analyzeJobsDirectory(): Promise<JobsDirectoryAnalysis> {
|
|||||||
jobStats.push(folderStats);
|
jobStats.push(folderStats);
|
||||||
totalDocuments += folderStats.documentCount;
|
totalDocuments += folderStats.documentCount;
|
||||||
totalSizeBytes += folderStats.totalSizeBytes;
|
totalSizeBytes += folderStats.totalSizeBytes;
|
||||||
|
|
||||||
// Aggregate file type stats
|
// Aggregate file type stats
|
||||||
for (const [ext, count] of Object.entries(folderStats.fileTypeStats)) {
|
for (const [ext, count] of Object.entries(folderStats.fileTypeStats)) {
|
||||||
aggregatedFileTypeStats[ext] = (aggregatedFileTypeStats[ext] || 0) + count;
|
aggregatedFileTypeStats[ext] = (aggregatedFileTypeStats[ext] || 0) + count;
|
||||||
@@ -360,6 +362,9 @@ export async function analyzeJobsDirectory(): Promise<JobsDirectoryAnalysis> {
|
|||||||
logger.info(
|
logger.info(
|
||||||
`Jobs directory analysis complete: ${analysis.totalJobs} jobs, ${analysis.totalDocuments} documents, ${analysis.totalSizeMB} MB`
|
`Jobs directory analysis complete: ${analysis.totalJobs} jobs, ${analysis.totalDocuments} documents, ${analysis.totalSizeMB} MB`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//Add an upload to the IO database to categorize all of this.
|
||||||
|
|
||||||
return analysis;
|
return analysis;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to analyze Jobs directory:", error);
|
logger.error("Failed to analyze Jobs directory:", error);
|
||||||
@@ -389,7 +394,9 @@ async function analyzeJobFolder(jobsPath: string, jobId: string): Promise<JobFol
|
|||||||
/**
|
/**
|
||||||
* Recursively get document count and total size for a directory (standalone helper function)
|
* Recursively get document count and total size for a directory (standalone helper function)
|
||||||
*/
|
*/
|
||||||
async function getDirectoryStats(dirPath: string): Promise<{ documentCount: number; totalSizeBytes: number; fileTypeStats: { [extension: string]: number } }> {
|
async function getDirectoryStats(
|
||||||
|
dirPath: string
|
||||||
|
): Promise<{ documentCount: number; totalSizeBytes: number; fileTypeStats: { [extension: string]: number } }> {
|
||||||
let documentCount = 0;
|
let documentCount = 0;
|
||||||
let totalSizeBytes = 0;
|
let totalSizeBytes = 0;
|
||||||
const fileTypeStats: { [extension: string]: number } = {};
|
const fileTypeStats: { [extension: string]: number } = {};
|
||||||
@@ -406,7 +413,7 @@ async function getDirectoryStats(dirPath: string): Promise<{ documentCount: numb
|
|||||||
const subStats = await getDirectoryStats(itemPath);
|
const subStats = await getDirectoryStats(itemPath);
|
||||||
documentCount += subStats.documentCount;
|
documentCount += subStats.documentCount;
|
||||||
totalSizeBytes += subStats.totalSizeBytes;
|
totalSizeBytes += subStats.totalSizeBytes;
|
||||||
|
|
||||||
// Merge file type stats
|
// Merge file type stats
|
||||||
for (const [ext, count] of Object.entries(subStats.fileTypeStats)) {
|
for (const [ext, count] of Object.entries(subStats.fileTypeStats)) {
|
||||||
fileTypeStats[ext] = (fileTypeStats[ext] || 0) + count;
|
fileTypeStats[ext] = (fileTypeStats[ext] || 0) + count;
|
||||||
@@ -415,9 +422,9 @@ async function getDirectoryStats(dirPath: string): Promise<{ documentCount: numb
|
|||||||
// Count files as documents
|
// Count files as documents
|
||||||
documentCount++;
|
documentCount++;
|
||||||
totalSizeBytes += stat.size;
|
totalSizeBytes += stat.size;
|
||||||
|
|
||||||
// Track file extension
|
// Track file extension
|
||||||
const ext = path.extname(item).toLowerCase() || 'no-extension';
|
const ext = path.extname(item).toLowerCase() || "no-extension";
|
||||||
fileTypeStats[ext] = (fileTypeStats[ext] || 0) + 1;
|
fileTypeStats[ext] = (fileTypeStats[ext] || 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user