feat(lstv2 move): moved lstv2 into this app to keep them combined and easier to maintain

This commit is contained in:
2025-09-19 22:22:05 -05:00
parent caf2315191
commit e4477402ad
847 changed files with 165801 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
import type { Context } from "hono";
import { z, ZodError } from "zod";
import { getConnInfo } from "@hono/node-server/conninfo";
import { tryCatch } from "./tryCatch.js";
import { db } from "../../database/dbclient.js";
import { apiHits } from "../../database/schema/apiHits.js";
import { sql } from "drizzle-orm";
// Define the request body schema
const requestSchema = z.object({
ip: z.string().optional(),
endpoint: z.string(),
action: z.string().optional(),
lastBody: z.array(z.object({})).or(z.object({})).optional(),
stats: z.string().optional(),
});
type ApiHitData = z.infer<typeof requestSchema>;
export const apiHit = async (
c: Context,
data: ApiHitData
): Promise<{ success: boolean; data?: ApiHitData; errors?: any[] }> => {
const info = getConnInfo(c);
// console.log(`Your remote address is ${info.remote.address}`);
try {
// Extract IP from request headers or connection info
const forwarded = c.req.header("host");
//console.log(forwarded);
// Validate the data
const checkData = {
ip: info.remote.address!,
endpoint: data?.endpoint,
lastBody: data?.lastBody,
action: data?.action,
//stats: data?.stats,
};
const validatedData = requestSchema.parse(checkData);
const { data: apitHitData, error } = await tryCatch(
db
.insert(apiHits)
.values(checkData)
.onConflictDoUpdate({
target: [apiHits.endpoint, apiHits.ip],
set: {
stats: sql`${apiHits.stats} + 1`,
lastBody: data?.lastBody,
action: data?.action,
upd_date: sql`NOW()`,
},
})
);
if (error) {
console.log(error);
}
// Proceed with the validated data
return { success: true, data: validatedData };
} catch (error) {
// Explicitly check if the error is an instance of ZodError
if (error instanceof ZodError) {
console.log({ success: false, errors: error.errors });
return { success: false, errors: error.errors };
}
// Catch other unexpected errors
return {
success: false,
errors: [{ message: "An unknown error occurred" }],
};
}
};

View File

@@ -0,0 +1,15 @@
import type {Context} from "hono";
import type {ContentfulStatusCode} from "hono/utils/http-status";
export const apiReturn = async (
c: Context,
success: boolean,
message: string,
data: any,
code: ContentfulStatusCode
): Promise<Response> => {
/**
* This is just a global return function to reduce constacnt typing the same thing lol
*/
return c.json({success, message, data}, code);
};

View File

@@ -0,0 +1,15 @@
import path from "path";
import {createLog} from "../services/logger/logger.js";
import fs from "fs";
export const getAppInfo = async (appLock: string) => {
try {
const packagePath = path.join(appLock, "package.json");
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
//const version = packageJson.version;
return packageJson;
} catch (error) {
createLog("error", "lst", "zipUpBuild", `Error in getting the version: ${error}`);
return error;
}
};

View File

@@ -0,0 +1,67 @@
import { query } from "../services/sqlServer/prodSqlServer.js";
import { plantInfo } from "../services/sqlServer/querys/dataMart/plantInfo.js";
import { createLog } from "../services/logger/logger.js";
import { getSettings } from "../services/server/controller/settings/getSettings.js";
export const createSSCC = async (runningNumber: number) => {
// get the token
let serverSettings = (await getSettings()) as any;
const plantToken = serverSettings?.filter(
(n: any) => n.name === "plantToken"
);
// const plantToken = await db
// .select()
// .from(settings)
// .where(eq(settings.name, "plantToken"));
let global: any = []; // get from plant address in basis enter the entire string here.
try {
const res: any = await query(
plantInfo.replaceAll("[token]", plantToken[0].value),
"plantInfo"
);
global = res.data;
} catch (error) {
createLog(
"error",
"lst",
"globalUtils",
`There was an error getting the GLN: Error: ${error}`
);
}
// create the sscc without the check diget and make sure we have it all correct
let step1SSCC =
global[0].gln.toString().slice(0, 7).padStart(10, "0") +
runningNumber.toString().padStart(9, "0");
let sum = 0;
for (let i = 0; i < step1SSCC.length; i++) {
let digit = parseInt(step1SSCC[i], 10);
if (i % 2 === 0) {
// Even index in 0-based index system means odd position in 1-based index system
sum += digit * 3;
} else {
sum += digit;
}
}
let checkDigit = (10 - (sum % 10)) % 10;
// make sure the check digit is never 10
// let checkDigit = 10 - ((oddSum * 3 + evenSum) % 10) === 10 ? 0 : (oddSum * 3 + evenSum) % 10;
// return the true sscc
let sscc = step1SSCC + checkDigit;
// console.log(step1SSCC);
// console.log(checkDigit);
return sscc.padStart(20, "0");
};
// let rn = 518475;
// console.log(`Creating sscc`);
// let sscc = await createSSCC(rn);
// console.log(sscc);

View File

@@ -0,0 +1,44 @@
import { eq } from "drizzle-orm";
import { db } from "../../database/dbclient.js";
import { getSettings } from "../services/server/controller/settings/getSettings.js";
// create the test server stuff
const testServers = [
{ token: "test1", port: 8940 },
{ token: "test2", port: 8941 },
{ token: "test3", port: 8942 },
];
export const prodEndpointCreation = async (endpoint: string) => {
let url = "";
//get the plant token
let serverSettings = await getSettings();
const plantToken = serverSettings?.filter((n) => n.name === "plantToken");
// await db
// .select()
// .from(settings)
// .where(eq(settings.name, "plantToken"));
// check if we are a test server
const testServer = testServers.some(
(server) => server.token === plantToken[0]?.value
);
const server = serverSettings?.filter((n) => n.name === "dbServer");
// await db
// .select()
// .from(settings)
// .where(eq(settings.name, "dbServer"));
if (testServer) {
//filter out what testserver we are
const test = testServers.filter((t) => t.token === plantToken[0].value);
// "https://usmcd1vms036.alpla.net:8942/application/public/v1.0/DemandManagement/ORDERS"
// "https://usmcd1vms036.alpla.net:8492/application/public/v1.0/DemandManagement/ORDERS"
url = `https://${server[0]?.value}.alpla.net:${test[0]?.port}/application${endpoint}`;
return url;
} else {
url = `https://${plantToken[0]?.value}prod.alpla.net/application${endpoint}`;
return url;
}
};

View File

@@ -0,0 +1,23 @@
import { lt } from "drizzle-orm";
import { db } from "../../../database/dbclient.js";
import { prodlabels } from "../../../database/schema/prodLabels.js";
import { addDays } from "date-fns";
import { createLog } from "../../services/logger/logger.js";
export const deleteLabels = async () => {
/**
* Deletes labels older than 90 days from lst... all label data can be found in alpla prod.
*/
try {
await db
.delete(prodlabels)
.where(lt(prodlabels.upd_date, addDays(new Date(Date.now()), -90)));
} catch (error) {
createLog(
"error",
"labeling",
"ocp",
`Error deleting labels older than 90 days`
);
}
};

View File

@@ -0,0 +1,3 @@
export const delay = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};

View File

@@ -0,0 +1,69 @@
export const freightClass = (
weight: number,
length: number,
width: number,
height: number
) => {
// mm to in conversion
const convertMM = 25.4;
const convertKG = 2.20462;
// Inputs
const weightPounds = weight * convertKG;
const lengthInches = length / convertMM;
const widthInches = width / convertMM;
const heightInches = height / convertMM;
// Calculate volume in cubic inches
const volumeCubicInches = lengthInches * widthInches * heightInches;
// Convert cubic inches to cubic feet
const volumeCubicFeet = volumeCubicInches / 1728;
// Calculate density
const density = weightPounds / volumeCubicFeet;
// Determine freight class
let freightClass;
if (density >= 50) {
freightClass = 50;
} else if (density >= 35) {
freightClass = 55;
} else if (density >= 30) {
freightClass = 60;
} else if (density >= 22.5) {
freightClass = 65;
} else if (density >= 15) {
freightClass = 70;
} else if (density >= 13.5) {
freightClass = 77.5;
} else if (density >= 12) {
freightClass = 85;
} else if (density >= 10.5) {
freightClass = 92.5;
} else if (density >= 9) {
freightClass = 100;
} else if (density >= 8) {
freightClass = 110;
} else if (density >= 7) {
freightClass = 125;
} else if (density >= 6) {
freightClass = 150;
} else if (density >= 5) {
freightClass = 175;
} else if (density >= 4) {
freightClass = 200;
} else if (density >= 3) {
freightClass = 250;
} else if (density >= 2) {
freightClass = 300;
} else if (density >= 1) {
freightClass = 400;
} else {
freightClass = 500;
}
// Output the freight class
return freightClass;
};

View File

@@ -0,0 +1,12 @@
import { getHours } from "date-fns";
export const greetingStuff = async (date = new Date()) => {
const hour = getHours(date);
if (hour < 12) {
return "Good morning";
} else if (hour < 18) {
return "Good afternoon";
} else {
return "Good evening";
}
};

View File

@@ -0,0 +1,65 @@
import dns from "dns";
import net from "net";
// Usage example
//const hostnamePort = "example.com:80"; // Replace with your hostname:port
//checkHostnamePort(hostnamePort);
// Function to resolve a hostname to an IP address
export function resolveHostname(hostname: string) {
return new Promise((resolve, reject) => {
dns.lookup(hostname, (err, address) => {
if (err) {
reject(err);
} else {
resolve(address);
}
});
});
}
// Function to check if a port is open
export function checkPort(ip: string, port: number): Promise<boolean> {
return new Promise((resolve, reject) => {
const socket = new net.Socket();
socket.setTimeout(2000); // Set a timeout for the connection attempt
socket.on("connect", () => {
socket.destroy(); // Close the connection
resolve(true); // Port is open
});
socket.on("timeout", () => {
socket.destroy(); // Close the connection
reject(new Error("Connection timed out")); // Port is not reachable
});
socket.on("error", (err: any) => {
reject(new Error(`Unknown error: ${err}`)); // Handle non-Error types
});
socket.connect(port, ip);
});
}
// Main function to check hostname:port
export async function checkHostnamePort(hostnamePort: string): Promise<boolean> {
try {
// Split the input into hostname and port
const [hostname, port] = hostnamePort.split(":");
if (!hostname || !port) {
return false; // Invalid format
}
// Resolve the hostname to an IP address
const ip = (await resolveHostname(hostname)) as string;
// Check if the port is open
const portCheck = await checkPort(ip, parseInt(port, 10));
return true; // Hostname:port is reachable
} catch (err) {
return false; // Any error means the hostname:port is not reachable
}
}

View File

@@ -0,0 +1,77 @@
import { Hono } from "hono";
import { type Context, type Next } from "hono";
const app = new Hono();
// --- In-Memory Store for Rate Limits ---
// This Map will store when each user/key last accessed a rate-limited endpoint.
// Key: string (e.g., 'ip_address' or 'user_id_endpoint')
// Value: number (timestamp of last access in milliseconds)
const rateLimitStore = new Map<string, number>();
// --- Configuration ---
const FIFTEEN_MINUTES_MS = 5 * 60 * 1000; // 15 minutes in milliseconds
// --- Rate Limiting Middleware ---
export const simpleRateLimit = async (c: Context, next: Next) => {
// 1. Define a unique key for the rate limit
// For simplicity, we'll use a placeholder for user identification.
// In a real app:
// - If unauthenticated: Use c.req.header('x-forwarded-for') or c.req.ip (if configured/available)
// - If authenticated: Get user ID from c.req.user or similar after authentication middleware
const userIdentifier = c.req.header("x-forwarded-for") || "anonymous_user"; // Basic IP-like identifier
// You can also make the key specific to the route to have different limits per route
const routeKey = `${userIdentifier}:${c.req.path}`;
const now = Date.now();
const lastAccessTime = rateLimitStore.get(routeKey);
if (lastAccessTime) {
const timeElapsed = now - lastAccessTime;
if (timeElapsed < FIFTEEN_MINUTES_MS) {
// Limit exceeded
const timeRemainingMs = FIFTEEN_MINUTES_MS - timeElapsed;
const timeRemainingSeconds = Math.ceil(timeRemainingMs / 1000);
c.status(429); // HTTP 429: Too Many Requests
return c.json({
error: "Too Many Requests",
message: `Please wait ${timeRemainingSeconds} seconds before trying again.`,
retryAfter: timeRemainingSeconds, // Standard header for rate limiting clients
});
}
}
// If no previous access, or the 15 minutes have passed, allow the request
// and update the last access time.
rateLimitStore.set(routeKey, now);
// Continue to the next middleware or route handler
await next();
};
// --- Apply the Middleware to Specific Routes ---
app.get("/", (c) => {
return c.text("Welcome! This is a public endpoint.");
});
// This endpoint will be rate-limited
app.get("/privileged", simpleRateLimit, (c) => {
return c.text("You successfully accessed the privileged endpoint!");
});
// Another rate-limited endpoint
app.post("/submit-data", simpleRateLimit, async (c) => {
// In a real app, you'd process form data or JSON here
return c.text("Data submitted successfully (rate-limited).");
});
// Example of an endpoint that is NOT rate-limited
app.get("/health", (c) => {
return c.text("Server is healthy!");
});
export default app;

View File

@@ -0,0 +1,8 @@
export const apiOptions = () => {
return {
tags: ["rfid"],
summary: "Add new reader",
method: "post",
path: "/addreader",
};
};

View File

@@ -0,0 +1,45 @@
import {z} from "@hono/zod-openapi";
const responseSchema = z.object({
success: z.boolean().openapi({example: true}),
message: z.string().optional(),
data: z
.array(z.object({}).optional())
.optional()
.openapi({example: [{data: "hi"}]}),
});
export const responses = () => {
return {
200: {
content: {
"application/json": {schema: responseSchema},
},
description: "Response message",
},
400: {
content: {
"application/json": {
schema: z.object({message: z.string().optional().openapi({example: "Internal Server error"})}),
},
},
description: "Internal Server Error",
},
401: {
content: {
"application/json": {
schema: z.object({message: z.string().optional().openapi({example: "Unauthenticated"})}),
},
},
description: "Unauthorized",
},
500: {
content: {
"application/json": {
schema: z.object({message: z.string().optional().openapi({example: "Internal Server error"})}),
},
},
description: "Internal Server Error",
},
};
};

View File

@@ -0,0 +1,49 @@
import { createLog } from "../../services/logger/logger.js";
export function returnRes<T>(
success: true,
message: string,
service: string,
user: string,
level: "info" | "error",
data: T
): { success: true; message: string; data: T };
export function returnRes<T>(
success: false,
message: string,
service: string,
user: string,
level: "info" | "error",
data?: T
): { success: false; message: string; error: T | string };
export function returnRes<T>(
success: boolean,
message: string,
service: string,
user: string,
level: "info" | "error",
data?: T
) {
createLog(level, user, service, message);
if (success) {
return { success: true, message, data: data as T };
} else {
return {
success: false,
message,
error: data ?? "An unknown error occurred",
};
}
}
// export const returnApi = (c:Context,success: boolean, message: string, data?: any, code: number)=>{
// /**
// * just a simple return to reduce the typing and make sure we are always consitant with our returns.
// *
// * data can be an error as well.
// */
// return c.json({success, message, data}, code);
// }

View File

@@ -0,0 +1,92 @@
import axios from "axios";
import { prodEndpointCreation } from "./createUrl.js";
import { tryCatch } from "./tryCatch.js";
import { createLog } from "../services/logger/logger.js";
type bodyData = any;
type Data = {
endpoint: string;
data: bodyData[];
};
export const runProdApi = async (data: Data) => {
/**
* Detachs a silo
*/
let url = await prodEndpointCreation(data.endpoint);
const { data: d, error } = await tryCatch(
axios.post(url, data.data[0], {
headers: {
"X-API-Key": process.env.TEC_API_KEY || "",
"Content-Type": "application/json",
},
})
);
let e = error as any;
if (e) {
//console.log(e.response);
if (e.status === 401) {
createLog(
"error",
"lst",
"logistics",
`Not autorized: ${JSON.stringify(e.response?.data)}`
);
const data = {
success: false,
message: `Not autorized: ${JSON.stringify(e.response?.data)}`,
data: {
status: e.response?.status,
statusText: e.response?.statusText,
data: e.response?.data,
},
};
return data;
} else {
createLog(
"error",
"lst",
"logistics",
`There was an error processing the endpoint: ${JSON.stringify(
e.response?.data
)}`
);
return {
success: false,
message: `There was an error processing the endpoint: ${JSON.stringify(
e.response?.data
)}`,
data: {
status: e.response?.status,
statusText: e.response?.statusText,
data: e.response?.data,
},
};
}
}
if (d?.status !== 200) {
return {
success: false,
message: "Error processing endpoint",
data: {
status: d?.status,
statusText: d?.statusText,
data: d?.data,
},
};
} else {
return {
success: true,
message: "Endpoint was processed",
data: {
status: d.status,
statusText: d.statusText,
data: d.data,
},
};
}
};

View File

@@ -0,0 +1,11 @@
import crypto from "crypto";
export const generateOneTimeKey = async (length = 32) => {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let key = "";
const bytes = crypto.randomBytes(length);
for (let i = 0; i < length; i++) {
key += chars[bytes[i] % chars.length];
}
return key.match(/.{1,4}/g)!.join("-"); // group by 4 chars
};

View File

@@ -0,0 +1,13 @@
import { addHours } from "date-fns";
export const timeZoneFix = () => {
/**
* Returns iso date based on current timezone.
*/
const rawDate = new Date(Date.now()).toISOString();
const offsetMinutes = new Date().getTimezoneOffset(); // in minutes
const offsetHours =
-offsetMinutes / 60 >= 0 ? offsetMinutes / 60 : -offsetMinutes / 60;
return addHours(rawDate, offsetHours).toISOString();
};

View File

@@ -0,0 +1,24 @@
// Types for the result object with discriminated union
type Success<T> = {
data: T;
error: null;
};
type Failure<E> = {
data: null;
error: E;
};
type Result<T, E = Error> = Success<T> | Failure<E>;
// Main wrapper function
export async function tryCatch<T, E = Error>(
promise: Promise<T>
): Promise<Result<T, E>> {
try {
const data = await promise;
return { data, error: null };
} catch (error) {
return { data: null, error: error as E };
}
}