Compare commits

...

12 Commits

45 changed files with 19242 additions and 12194 deletions

View File

@@ -1,5 +1,46 @@
# All CHanges to LST can be found below.
## [2.7.0](https://git.tuffraid.net/cowch/lstV2/compare/v2.6.0...v2.7.0) (2025-03-15)
### 📝 Chore
* bump build number to 10 ([245ba19](https://git.tuffraid.net/cowch/lstV2/commits/245ba19cdc7e67fec9343bb9eba90287d8951854))
* bump build number to 11 ([c9a5203](https://git.tuffraid.net/cowch/lstV2/commits/c9a520313160959ed929351f55bd18606b6500a9))
* bump build number to 12 ([b1d25c7](https://git.tuffraid.net/cowch/lstV2/commits/b1d25c7ba27a72c42803a3b482234c41670a07a2))
* bump build number to 13 ([bcd65b4](https://git.tuffraid.net/cowch/lstV2/commits/bcd65b4b91cf6cb24a32323ead5ff6a73d3c9d3d))
* bump build number to 14 ([a2fb845](https://git.tuffraid.net/cowch/lstV2/commits/a2fb845e2efbeb8becf91082343770b2d730330c))
* bump build number to 15 ([0b72ffa](https://git.tuffraid.net/cowch/lstV2/commits/0b72ffa935be81476a15166fd60ae8cabe29d297))
* bump build number to 16 ([44da09d](https://git.tuffraid.net/cowch/lstV2/commits/44da09d22c4d946c981d408d797ea121d26690b6))
* bump build number to 17 ([1ce5f9a](https://git.tuffraid.net/cowch/lstV2/commits/1ce5f9acf752f5f4bcb4b2ff1c71f54ffb9331f3))
* bump build number to 18 ([b6f1cfd](https://git.tuffraid.net/cowch/lstV2/commits/b6f1cfdc6c71e351fb90ec7729e0d0f89322174f))
### 🛠️ Code Refactor
* **frontend:** removed tanstack devTools ([3594278](https://git.tuffraid.net/cowch/lstV2/commits/359427824bae319b7f2f406eb52b8f9c43be198f))
* **frontend:** removed the caption from settings table ([35acd2b](https://git.tuffraid.net/cowch/lstV2/commits/35acd2b0b3af7b9d010cb8f78f088ab3a539c54b))
### 🌟 Enhancements
* **frontend:** added in proper links for settings and servers to the sidebar ([cbdd218](https://git.tuffraid.net/cowch/lstV2/commits/cbdd218fe454e38a7cf0c4d0ddf60d1f20e15ee2))
* **frontend:** added in update server page only for sysAdmin ([625d596](https://git.tuffraid.net/cowch/lstV2/commits/625d5969be2dbde9e97f6607c33c5e5b14e3d192))
* **new setting:** added in devDir ment for updating servers ([5b97d07](https://git.tuffraid.net/cowch/lstV2/commits/5b97d078c583f3e78d52dbc135da99a0175c8e54))
* **server upgrade:** added in a catch incase we try to upgrade again ([ab16059](https://git.tuffraid.net/cowch/lstV2/commits/ab16059387b7cb46e2a3d86f6da09a31899bd5d6))
* **server:** added in update server as well as get serverdata ([df252e7](https://git.tuffraid.net/cowch/lstV2/commits/df252e72b39d811ffbbb0af49aaf43ff22081f48))
* **serverdata:** added catch if we are not on localhost we cant actaully see the devDir in set ([f3fa617](https://git.tuffraid.net/cowch/lstV2/commits/f3fa617aa53ed35f461b71f5e479ea521412936e))
* **serverdata:** added in bowling green 2 ([7529cc5](https://git.tuffraid.net/cowch/lstV2/commits/7529cc5b0cc2c6542e8f4af07d465b6a9f4295b1))
* **sql query:** added 2 catches if not connected dont run ([cb7a406](https://git.tuffraid.net/cowch/lstV2/commits/cb7a4068fcffc7a1d85c2e04f2eeaebdc264705c))
* **sql server:** added in the ping check to not spam if we are not connected ([e4d15ef](https://git.tuffraid.net/cowch/lstV2/commits/e4d15ef051f6c72d2cca9bb9fb61ff78849db8c4))
### 🐛 Bug fixes
* **frontend:** if the modules returns and error we want to use an empty array ([2370d45](https://git.tuffraid.net/cowch/lstV2/commits/2370d45220c5e1c3215a3f2fce9582d8f2bbd3ed))
* **rfid:** correction to the params and incorrect naming ([a73c63c](https://git.tuffraid.net/cowch/lstV2/commits/a73c63cefa67f43300f4695f40f2248bcab8f40e))
* **zippaths:** corrected the paths to the src that were moved the the env ([246b5a1](https://git.tuffraid.net/cowch/lstV2/commits/246b5a17bd4ffe47654416fa4a1be446f8bffbd0))
## [2.6.0](https://git.tuffraid.net/cowch/lstV2/compare/v2.5.1...v2.6.0) (2025-03-14)

View File

@@ -0,0 +1 @@
ALTER TABLE "rfidReaders" ADD COLUMN "lastTrigger" timestamp DEFAULT now();

View File

@@ -0,0 +1 @@
ALTER TABLE "rfidReaders" ADD COLUMN "active" boolean DEFAULT true;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "rfidTags" ADD COLUMN "antenna" numeric;--> statement-breakpoint
ALTER TABLE "rfidTags" ADD COLUMN "tagStrength" numeric;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "rfidTags" ALTER COLUMN "counts" SET DEFAULT '[]'::jsonb;--> statement-breakpoint
ALTER TABLE "rfidTags" ALTER COLUMN "counts" DROP NOT NULL;

View File

@@ -0,0 +1,4 @@
ALTER TABLE "rfidTags" ALTER COLUMN "runningNumber" SET DATA TYPE integer;--> statement-breakpoint
ALTER TABLE "rfidTags" ALTER COLUMN "runningNumber" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "rfidTags" ALTER COLUMN "antenna" SET DATA TYPE integer;--> statement-breakpoint
ALTER TABLE "rfidTags" ALTER COLUMN "tagStrength" SET DATA TYPE integer;

View File

@@ -0,0 +1 @@
ALTER TABLE "rfidReaders" ADD COLUMN "lastTiggerGood" boolean DEFAULT true;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -120,6 +120,48 @@
"when": 1742051878587,
"tag": "0016_wet_doctor_strange",
"breakpoints": true
},
{
"idx": 17,
"version": "7",
"when": 1742133439717,
"tag": "0017_bitter_brood",
"breakpoints": true
},
{
"idx": 18,
"version": "7",
"when": 1742133828291,
"tag": "0018_lovely_landau",
"breakpoints": true
},
{
"idx": 19,
"version": "7",
"when": 1742139737945,
"tag": "0019_greedy_justice",
"breakpoints": true
},
{
"idx": 20,
"version": "7",
"when": 1742143798267,
"tag": "0020_empty_thundra",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1742144973347,
"tag": "0021_premium_albert_cleary",
"breakpoints": true
},
{
"idx": 22,
"version": "7",
"when": 1742156466912,
"tag": "0022_amused_true_believers",
"breakpoints": true
}
]
}

View File

@@ -9,6 +9,9 @@ export const rfidReaders = pgTable(
reader: text("reader"),
readerIP: text("readerIP"),
lastHeartBeat: timestamp("lastHeartBeat").defaultNow(),
lastTrigger: timestamp("lastTrigger").defaultNow(),
lastTriggerGood: boolean("lastTiggerGood").default(true),
active: boolean("active").default(true),
},
(table) => [
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),

View File

@@ -1,4 +1,4 @@
import {text, pgTable, numeric, index, timestamp, boolean, uuid, uniqueIndex, jsonb} from "drizzle-orm/pg-core";
import {text, pgTable, timestamp, uuid, uniqueIndex, jsonb, integer} from "drizzle-orm/pg-core";
import {createInsertSchema, createSelectSchema} from "drizzle-zod";
import {z} from "zod";
@@ -9,9 +9,11 @@ export const rfidTags = pgTable(
tagHex: text("tagHex"),
tag: text("tag"),
lastRead: timestamp("timeStamp").defaultNow(),
counts: jsonb("counts").notNull(), //.default([{area: 1, timesHere: 5}]).notNull(),
counts: jsonb("counts").default([]), // example [{area: Line3.2, count: 1}, {area: line3.1, count: 6}]
lastareaIn: text("lastareaIn").notNull(),
runningNumber: numeric("runningNumber").notNull(),
runningNumber: integer("runningNumber"),
antenna: integer("antenna"),
tagStrength: integer("tagStrength"),
created_at: timestamp("created_at").defaultNow(),
},
(table) => [
@@ -21,8 +23,8 @@ export const rfidTags = pgTable(
);
// Schema for inserting a user - can be used to validate API requests
// export const insertRolesSchema = createInsertSchema(roles, {
// name: z.string().min(3, {message: "Role name must be more than 3 letters"}),
// });
export const insertRolesSchema = createInsertSchema(rfidTags, {
tagHex: z.string().min(3, {message: "Tag Should have more than 3 characters"}),
});
// Schema for selecting a Expenses - can be used to validate API responses
export const selectRolesSchema = createSelectSchema(rfidTags);

View File

@@ -33,6 +33,7 @@
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.476.0",
"next-themes": "^0.4.4",
"npm-check-updates": "^17.1.15",
"react": "^19.0.0",
"react-day-picker": "^8.10.1",
"react-dom": "^19.0.0",
@@ -4901,6 +4902,20 @@
"node": ">=0.10.0"
}
},
"node_modules/npm-check-updates": {
"version": "17.1.15",
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.1.15.tgz",
"integrity": "sha512-miATvKu5rjec/1wxc5TGDjpsucgtCHwRVZorZpDkS6NzdWXfnUWlN4abZddWb7XSijAuBNzzYglIdTm9SbgMVg==",
"license": "Apache-2.0",
"bin": {
"ncu": "build/cli.js",
"npm-check-updates": "build/cli.js"
},
"engines": {
"node": "^18.18.0 || >=20.0.0",
"npm": ">=8.12.1"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",

View File

@@ -8,7 +8,8 @@
"build": "rimraf dist && tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"shad": "npx shadcn@canary add "
"shad": "npx shadcn@canary add ",
"checkupdates": "npm-check-updates"
},
"dependencies": {
"@hookform/resolvers": "^4.1.2",
@@ -36,6 +37,7 @@
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.476.0",
"next-themes": "^0.4.4",
"npm-check-updates": "^17.1.15",
"react": "^19.0.0",
"react-day-picker": "^8.10.1",
"react-dom": "^19.0.0",

23935
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,75 +1,77 @@
{
"name": "lstv2",
"version": "2.6.0",
"type": "module",
"scripts": {
"dev": "concurrently -n \"server,frontend\" -c \"#007755,#2f6da3\" \"npm run dev:server\" \"cd frontend && npm run dev\"",
"dev:server": "dotenvx run -f .env -- tsx watch server/index.ts",
"dev:frontend": "cd frontend && npm run dev",
"dev:dbgen": " drizzle-kit generate --config=drizzle-dev.config.ts",
"dev:dbmigrate": " drizzle-kit migrate --config=drizzle-dev.config.ts",
"build": "npm run build:server && npm run build:frontend",
"build:server": "rimraf build && tsc --build && xcopy server\\scripts dist\\server\\scripts /E /I /Y && xcopy server\\services\\server\\utils\\serverData.json dist\\server\\services\\server\\utils /E /I /Y ",
"build:frontend": "cd frontend && npm run build",
"start": "set NODE_ENV=production && npm run start:server",
"start:server": "dotenvx run -f .env -- node dist/server/index.js",
"db:generate": "npx drizzle-kit generate",
"db:migrate": "npx drizzle-kit push",
"deploy": "standard-version --conventional-commits && npm run prodBuild",
"zipServer": "dotenvx run -f .env -- tsx server/scripts/zipUpBuild.ts \"C:\\Users\\matthes01\\Documents\\lstv2\"",
"prodBuild": "powershell -ExecutionPolicy Bypass -File server/scripts/build.ps1 -dir \"C:\\Users\\matthes01\\Documents\\lstv2\" && npm run zipServer",
"commit": "cz",
"prodinstall": "npm i --omit=dev && npm run db:migrate"
},
"dependencies": {
"@dotenvx/dotenvx": "^1.38.3",
"@hono/node-server": "^1.13.8",
"@hono/zod-openapi": "^0.18.4",
"@scalar/hono-api-reference": "^0.5.175",
"@types/jsonwebtoken": "^9.0.8",
"adm-zip": "^0.5.16",
"axios": "^1.7.9",
"bcrypt": "^5.1.1",
"compression": "^1.8.0",
"cookie": "^1.0.2",
"date-fns": "^4.1.0",
"dotenv": "^16.4.7",
"drizzle-kit": "^0.30.4",
"drizzle-orm": "^0.39.3",
"drizzle-zod": "^0.7.0",
"jsonwebtoken": "^9.0.2",
"mssql": "^11.0.1",
"nodemailer": "^6.10.0",
"nodemailer-express-handlebars": "^7.0.0",
"pg": "^8.13.3",
"pino": "^9.6.0",
"pino-abstract-transport": "^2.0.0",
"pino-pretty": "^13.0.0",
"postgres": "^3.4.5",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/adm-zip": "^0.5.7",
"@types/bcrypt": "^5.0.2",
"@types/js-cookie": "^3.0.6",
"@types/mssql": "^9.1.7",
"@types/node": "^22.13.5",
"@types/pg": "^8.11.11",
"concurrently": "^8.2.0",
"cz-conventional-changelog": "^3.3.0",
"dotenv": "^16.3.1",
"rimraf": "^6.0.1",
"standard-version": "^9.5.0",
"tsx": "^4.7.1",
"typescript": "~5.7.3"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
"name": "lstv2",
"version": "2.7.0",
"type": "module",
"scripts": {
"dev": "concurrently -n \"server,frontend\" -c \"#007755,#2f6da3\" \"npm run dev:server\" \"cd frontend && npm run dev\"",
"dev:server": "dotenvx run -f .env -- tsx watch server/index.ts",
"dev:frontend": "cd frontend && npm run dev",
"dev:dbgen": " drizzle-kit generate --config=drizzle-dev.config.ts",
"dev:dbmigrate": " drizzle-kit migrate --config=drizzle-dev.config.ts",
"build": "npm run build:server && npm run build:frontend",
"build:server": "rimraf build && tsc --build && xcopy server\\scripts dist\\server\\scripts /E /I /Y && xcopy server\\services\\server\\utils\\serverData.json dist\\server\\services\\server\\utils /E /I /Y ",
"build:frontend": "cd frontend && npm run build",
"start": "set NODE_ENV=production && npm run start:server",
"start:server": "dotenvx run -f .env -- node dist/server/index.js",
"db:generate": "npx drizzle-kit generate",
"db:migrate": "npx drizzle-kit push",
"db:dev": "npm run build && npm run db:generate && npm run db:migrate",
"deploy": "standard-version --conventional-commits && npm run prodBuild",
"zipServer": "dotenvx run -f .env -- tsx server/scripts/zipUpBuild.ts \"C:\\Users\\matthes01\\Documents\\lstv2\"",
"prodBuild": "powershell -ExecutionPolicy Bypass -File server/scripts/build.ps1 -dir \"C:\\Users\\matthes01\\Documents\\lstv2\" && npm run zipServer",
"commit": "cz",
"prodinstall": "npm i --omit=dev && npm run db:migrate",
"checkupdates": "npm-check-updates"
},
"dependencies": {
"@dotenvx/dotenvx": "^1.38.3",
"@hono/node-server": "^1.13.8",
"@hono/zod-openapi": "^0.18.4",
"@scalar/hono-api-reference": "^0.5.175",
"@types/jsonwebtoken": "^9.0.8",
"adm-zip": "^0.5.16",
"axios": "^1.7.9",
"bcrypt": "^5.1.1",
"compression": "^1.8.0",
"cookie": "^1.0.2",
"date-fns": "^4.1.0",
"dotenv": "^16.4.7",
"drizzle-kit": "^0.30.4",
"drizzle-orm": "^0.39.3",
"drizzle-zod": "^0.7.0",
"jsonwebtoken": "^9.0.2",
"mssql": "^11.0.1",
"nodemailer": "^6.10.0",
"nodemailer-express-handlebars": "^7.0.0",
"pg": "^8.13.3",
"pino": "^9.6.0",
"pino-abstract-transport": "^2.0.0",
"pino-pretty": "^13.0.0",
"postgres": "^3.4.5",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/adm-zip": "^0.5.7",
"@types/bcrypt": "^5.0.2",
"@types/js-cookie": "^3.0.6",
"@types/mssql": "^9.1.7",
"@types/node": "^22.13.5",
"@types/pg": "^8.11.11",
"concurrently": "^8.2.0",
"cz-conventional-changelog": "^3.3.0",
"dotenv": "^16.3.1",
"rimraf": "^6.0.1",
"standard-version": "^9.5.0",
"tsx": "^4.7.1",
"typescript": "~5.7.3"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"admConfig": {
"build": 19,
"oldBuild": "backend-0.1.2-217.zip"
}
},
"admConfig": {
"build": 18,
"oldBuild": "backend-0.1.2-217.zip"
}
}
}

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

@@ -16,6 +16,7 @@ import ocme from "./services/ocme/ocmeService.js";
import sqlService from "./services/sqlServer/sqlService.js";
import logistics from "./services/logistics/logisticsService.js";
import rfid from "./services/rfid/rfidService.js";
import printers from "./services/printers/printerService.js";
import {db} from "../database/dbclient.js";
import {settings} from "../database/schema/settings.js";
@@ -83,6 +84,7 @@ const routes = [
sqlService,
logistics,
rfid,
printers,
] as const;
const appRoutes = routes.forEach((route) => {

View File

@@ -0,0 +1,14 @@
import {OpenAPIHono} from "@hono/zod-openapi";
import alerts from "./route/printerAlert.js";
const app = new OpenAPIHono();
const port = 4000;
const routes = [alerts] as const;
// app.route("/server", modules);
const appRoutes = routes.forEach((route) => {
app.route("/printers", route);
});
export default app;

View File

@@ -0,0 +1,102 @@
//http://usday1vms006:4000/api/v1/zebra/wrapper1
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
// Define the response schema
const responseSchema = z.object({
success: z.boolean().openapi({example: true}),
message: z.string().optional(),
});
const app = new OpenAPIHono();
const ParamsSchema = z.object({
printer: z
.string()
.min(3)
.openapi({
param: {
name: "printer",
in: "path",
},
example: "Line1",
}),
});
app.openapi(
createRoute({
tags: ["printer"],
summary: "Printer Alert",
method: "post",
path: "/{printer}",
request: {
params: ParamsSchema,
},
responses: {
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",
},
},
}),
async (c) => {
const {printer} = c.req.valid("param");
const contentType = c.req.header("Content-Type") || "";
const boundaryMatch = contentType.match(/boundary=(.*)$/);
if (!boundaryMatch) {
return c.json({message: "No boundary found in Content-Type"}, 400);
}
const boundary = boundaryMatch[1];
const body = await c.req.text(); // Get the body of the request
// Split the body by the boundary (adding extra dashes before the boundary)
const parts = body.split(`--${boundary}`);
console.log(parts);
// Remove the first and last empty parts (they are just before and after the boundaries)
const formDataParts = parts.slice(1, parts.length - 1);
// console.log(formDataParts);
// formDataParts.forEach((part, index) => {
// // Split the part into headers and body
// const [headers, data] = part.split("\r\n\r\n");
// // Log the part index and data for debugging
// console.log(`Part ${index + 1}:`);
// console.log("Headers:", headers);
// console.log("Data:", decodeURIComponent(data));
// });
return c.json({success: true, message: `New info from ${printer}`}, 200);
}
);
export default app;

View File

@@ -0,0 +1,34 @@
import {z} from "zod";
import {createLog} from "../../logger/logger.js";
import {db} from "../../../../database/dbclient.js";
import {rfidReaders} from "../../../../database/schema/rfidReaders.js";
const ReaderData = z.object({
reader: z.string(),
readerIP: z.string(),
});
type ReaderData = z.infer<typeof ReaderData>;
export const addReader = async (data: ReaderData, user: any) => {
/**
* add a new reader with name and ip.
*/
ReaderData.parse(data);
if (!data.reader && !data.readerIP) {
createLog("error", user.username, "rfid", "Missing data please check that you have both a name and ip.");
return {success: false, message: "Missing data please check that you have both a name and ip."};
}
// try to add the reader
try {
await db.insert(rfidReaders).values(data).onConflictDoUpdate({target: rfidReaders.reader, set: data});
createLog("info", user.username, "rfid", `Just added ${data.reader}.`);
return {success: true, message: `${data.reader} was just added.`};
} catch (error) {
createLog("error", user.username, "rfid", `there was an error adding ${data.reader}, ${error}`);
return {success: false, message: `${data.reader} encountered and error: ${error}`};
}
};

View File

@@ -1,3 +0,0 @@
/**
* While in production we will monitor the readers if we have not gotten a heartbeat in the last 5 min we will send a reboot command along with an email.
*/

View File

@@ -0,0 +1,7 @@
/**
* For a no read we just want to put up a notification to the rfid dashboard stating this reader did not respond with any tag data.
*/
export const noRead = async (reader: string) => {
console.log(`${reader} just had a no read please check for a tag and manually trigger a read.`);
};

View File

@@ -1,51 +1,85 @@
import axios from "axios";
import {createLog} from "../../logger/logger.js";
import {db} from "../../../../database/dbclient.js";
import {rfidReaders} from "../../../../database/schema/rfidReaders.js";
import {eq} from "drizzle-orm";
const authData = btoa("admin:Zebra123!");
let token: string;
let ip: string;
export const readTags = async (reader: string) => {
/**
* Start the read for x seconds then auto stop it
*/
let token: string;
const readers = [{reader: "reader1", readerIP: "10.10.1.222", lastHeartBeat: new Date()}];
const readers = await db.select().from(rfidReaders).where(eq(rfidReaders.active, true));
if (readers.length === 0) {
createLog("error", "rfid", "rfid", `There are no active readers right now maybe one forgot to be activated`);
return;
}
// get the auth token
const ip = readers.find((r) => r.reader === reader)?.readerIP;
ip = readers.find((r) => r.reader === reader)?.readerIP as string;
try {
const res = await axios.get(`https://${ip}/cloud/localRestLogin`, {
headers: {Authorization: `Basic ${btoa("admin:Zebra123!")}`},
headers: {Authorization: `Basic ${authData}`},
});
token = res.data.message;
// start the read
try {
const res = await axios.put(
`https://${ip}/cloud/start`,
{},
{
headers: {Authorization: `Bearer ${token}`},
}
);
// stop after 5 seconds
try {
const res = await axios.put(
`https://${ip}/cloud/stop`,
{},
{
headers: {Authorization: `Bearer ${token}`},
}
);
} catch (error) {
createLog("error", "rfid", "rfid", `There was an error Stopping the read ${error}`);
}
} catch (error) {
createLog("error", "rfid", "rfid", `There was an error Starting the read ${error}`);
}
} catch (error) {
createLog("error", "rfid", "rfid", `There was an error Getting the token the read ${error}`);
startRead();
} catch (error: any) {
console.log(error);
createLog(
"error",
"rfid",
"rfid",
`There was an error Getting the token the read: ${error.response?.data.message}`
);
}
// start the read
};
const startRead = async () => {
try {
const res = await axios.put(
`https://${ip}/cloud/start`,
{},
{
headers: {Authorization: `Bearer ${token}`},
}
);
//console.log(res.data);
if (res.status === 200) {
setTimeout(() => {
stopRead();
}, 5 * 1000);
}
// stop after 5 seconds
} catch (error: any) {
if (error.response.data.code === 3) {
stopRead();
setTimeout(() => {
startRead();
}, 1000);
}
createLog("error", "rfid", "rfid", `There was an error Starting the read: ${error.response.data.message}`);
}
};
const stopRead = async () => {
try {
const res = await axios.put(
`https://${ip}/cloud/stop`,
{},
{
headers: {Authorization: `Bearer ${token}`},
}
);
} catch (error: any) {
createLog("error", "rfid", "rfid", `There was an error Stopping the read: ${error.response.data.message}`);
}
};

View File

@@ -0,0 +1,60 @@
/**
* While in production we will monitor the readers if we have not gotten a heartbeat in the last 5 min we will send a reboot command along with an email.
*/
import {eq, sql} from "drizzle-orm";
import {db} from "../../../../database/dbclient.js";
import {rfidReaders} from "../../../../database/schema/rfidReaders.js";
import {createLog} from "../../logger/logger.js";
export const newHeartBeat = async (reader: string) => {
/**
* When a heat beat is sent over for a reader we want to update the reader.
*/
try {
const heatBeat = await db
.update(rfidReaders)
.set({lastHeartBeat: sql`NOW()`})
.where(eq(rfidReaders.reader, reader));
createLog("info", "rfid", "rfid", `${reader} just updated its heatBeat.`);
return {success: true, message: `${reader} just updated its heatBeat.`};
} catch (error) {
createLog("error", "rfid", "rfid", `${reader} encountered an error while updating the heatbeat, ${error}`);
return {success: false, message: `${reader} encountered an error while updating the heatbeat, ${error}`};
}
};
export const badRead = async (reader: string) => {
/**
* When we have a bad read we want to make sure the reader shows this was well.
*/
try {
const badRead = await db
.update(rfidReaders)
.set({lastTrigger: sql`NOW()`, lastTriggerGood: false})
.where(eq(rfidReaders.reader, reader));
createLog("info", "rfid", "rfid", `${reader} just Triggered a bad read.`);
return {success: true, message: `${reader} just Triggered a bad read.`};
} catch (error) {
createLog("error", "rfid", "rfid", `${reader} encountered an error while updating the heatbeat, ${error}`);
return {success: false, message: `${reader} encountered an error while updating the heatbeat, ${error}`};
}
};
export const goodRead = async (reader: string) => {
/**
* When we have a bad read we want to make sure the reader shows this was well.
*/
try {
const goodRead = await db
.update(rfidReaders)
.set({lastTrigger: sql`NOW()`, lastTriggerGood: true})
.where(eq(rfidReaders.reader, reader));
createLog("info", "rfid", "rfid", `${reader} just Triggered a good read.`);
return {success: true, message: `${reader} just Triggered a good read.`};
} catch (error) {
createLog("error", "rfid", "rfid", `${reader} encountered an error while updating the heatbeat, ${error}`);
return {success: false, message: `${reader} encountered an error while updating the heatbeat, ${error}`};
}
};

View File

@@ -1,4 +0,0 @@
/**
* Phase 1 we link the tag to the line only the line3.x where x is the line number
* Phase 2 we will generate a label to be reprinted at staion 4
*/

View File

@@ -1,4 +0,0 @@
/**
* Phase 1 we will just follow the logic of printing a label when we are told requested to based on this tag.
* Phase 2 we will just reprint the tag that was generated at the line
*/

View File

@@ -0,0 +1,27 @@
/**
* Phase 1 we link the tag to the line only the line3.x where x is the line number
* Phase 2 we will generate a label to be reprinted at staion 4
*/
import {createLog} from "../../../logger/logger.js";
import type {TagData} from "../tagData.js";
import {tagStuff} from "../tags/crudTag.js";
export const station3Tags = async (tagData: TagData[]) => {
// make sure we only have one tag or dont update
if (tagData.length != 1) {
createLog(
"error",
"rfid",
"rfid",
`There are ${tagData.length} tags, and ${tagData[0].reader} only allows 1 tag to create a label.`
);
// get tag data
tagStuff(tagData);
} else {
//console.log("Generate the label and link it to the tag.");
const tagdata = await tagStuff(tagData);
createLog("info", "rfid", "rfid", "Generate a label and link it to this tag.");
}
};

View File

@@ -0,0 +1,43 @@
/**
* Phase 1 we will just follow the logic of printing a label when we are told requested to based on this tag.
* Phase 2 we will just reprint the tag that was generated at the line
*/
import {createLog} from "../../../logger/logger.js";
import type {TagData} from "../tagData.js";
import {tagStuff} from "../tags/crudTag.js";
export const wrapperStuff = async (tagData: TagData[]) => {
if (tagData.length != 1) {
createLog(
"error",
"rfid",
"rfid",
`There are ${tagData.length} tags and this ${tagData[0].reader} only allows 1 tag to create a label.`
);
tagStuff(tagData);
} else {
const tagdata = await tagStuff(tagData);
/**
* we want to make sure this pallet came from a line as its last spot if not we need to have a manual check.
*/
const station3 = tagdata.some((n: any) => n.lastareaIn.includes("line3"));
if (!station3) {
createLog(
"error",
"rfid",
"rfid",
`${tagdata.tag}, Did not come from a line please check the pallet and manually print the label.`
);
}
// check if a running number exists
if (station3.runningNumber) {
createLog("info", "rfid", "rfid", `Reprint label ${station3.runningNumber}`);
} else {
createLog("info", "rfid", "rfid", `A new labels will be created and linked to this ${tagdata.tag} tag`);
}
}
};

View File

@@ -1,4 +1,14 @@
type TagData = {tagHex: string; reader: string; tag: string; timeStamp: Date};
import {station3Tags} from "./stations/station3.js";
import {wrapperStuff} from "./stations/wrappers.js";
export type TagData = {
tagHex: string;
reader: string;
tag: string;
timeStamp: Date;
antenna: number;
tagStrength: number;
};
export const tagData = async (data: TagData[]) => {
/**
* We will always update a tag
@@ -14,7 +24,7 @@ export const tagData = async (data: TagData[]) => {
const station3 = data.some((n) => n.reader.includes("line3"));
// at the wrapper
const station4 = data.some((n) => n.reader.includes("wrapper"));
const wrappers = data.some((n) => n.reader.includes("wrapper"));
// station checks
if (station1 && data.length != 10) {
@@ -26,23 +36,10 @@ export const tagData = async (data: TagData[]) => {
}
if (station3) {
// make sure we only have one tag or dont update
if (data.length != 1) {
console.log(`There are ${data.length} tags, and ${data[0].reader} only allows 1 tag to create a label.`);
//throw new Error("There are more than 1 tag at this station and it is not allowed");
} else {
console.log("Generate the tag and link it to the tag.");
}
station3Tags(data);
}
if (station4) {
if (data.length != 1) {
console.log(
`There are ${data.length} tags and this ${data[0].reader} only allows 1 tag to create a label.`
);
//throw new Error("There are more than 1 tag at this station and it is not allowed");
} else {
console.log("reprint the label linked to the tag.");
}
if (wrappers) {
wrapperStuff(data);
}
};

View File

@@ -0,0 +1,87 @@
import {eq} from "drizzle-orm";
import {db} from "../../../../../database/dbclient.js";
import {rfidTags} from "../../../../../database/schema/rfidTags.js";
import type {TagData} from "../tagData.js";
import {createLog} from "../../../logger/logger.js";
type ReturnTag = {
success: boolean;
tag: any;
error: any;
};
export const tagStuff = async (tagData: TagData[]): Promise<any> => {
const tags = await db.select().from(rfidTags);
// look through each tag and either add it to the db or update the area and other relevent data.
for (let i = 0; i < tagData.length; i++) {
// check if the tag already exists
const tag = tags.filter((n) => n.tagHex === tagData[i].tagHex);
if (tag.length === 0) {
// add new tag
const newTag = {
tagHex: tagData[i].tagHex,
tag: tagData[i].tag,
lastRead: new Date(tagData[i].timeStamp),
counts: [{area: tagData[i].reader, timesHere: 1}], //jsonb("counts").notNull(), //.default([{area: 1, timesHere: 5}]).notNull(),
lastareaIn: tagData[i].reader,
antenna: tagData[i].antenna,
tagStrength: tagData[i].tagStrength,
};
try {
// insert the tag with the onConflict update the tag
const tag = await db.insert(rfidTags).values(newTag).returning({
tag: rfidTags.tag,
runningNumber: rfidTags.runningNumber,
counts: rfidTags.counts,
lastareaIn: rfidTags.lastareaIn,
});
createLog("info", "rfid", "rfid", `Tags were jusdt updated.`);
return {success: true, tag};
} catch (error) {
createLog("error", "rfid", "rfid", `${JSON.stringify(error)}`);
return {success: false, error};
}
} else {
// update tag
//console.log("Updating existing tag");
// make sure we actually have an array here
const countsArray = (tag[0]?.counts as {area: string; timesHere: number}[]) ?? [];
// check if the reader exists on the array
const areaExists = countsArray.some((t) => t.area === tagData[0].reader);
// run the update on the array
const updateCount = areaExists
? countsArray.map((t) => {
if (t.area === tagData[0].reader) {
return {...t, timesHere: t.timesHere + 1};
} else {
return {...t, area: tagData[i].reader, timesHere: 1};
}
})
: [...countsArray, {area: tagData[i].reader, timesHere: 1}];
const updateTag = {
lastRead: new Date(tagData[i].timeStamp),
counts: updateCount, //jsonb("counts").notNull(), //.default([{area: 1, timesHere: 5}]).notNull(),
lastareaIn: tagData[i].reader,
antenna: tagData[i].antenna,
tagStrength: tagData[i].tagStrength,
};
try {
await db.update(rfidTags).set(updateTag).where(eq(rfidTags.tagHex, tagData[0].tagHex)).returning({
tag: rfidTags.tag,
runningNumber: rfidTags.runningNumber,
counts: rfidTags.counts,
lastareaIn: rfidTags.lastareaIn,
});
createLog("info", "rfid", "rfid", `Tags were jusdt updated.`);
return {success: true, tag};
} catch (error) {
createLog("error", "rfid", "rfid", `${JSON.stringify(error)}`);
return {success: false, error};
}
}
}
};

View File

@@ -2,13 +2,12 @@ import {OpenAPIHono} from "@hono/zod-openapi";
import mgtEvents from "./route/mgtEvents.js";
import tagInfo from "./route/tagInfo.js";
import addReader from "./route/addReader.js";
import updateReader from "./route/updateReader.js";
import manualTrigger from "./route/manualTagRead.js";
const app = new OpenAPIHono();
const routes = [
mgtEvents,
tagInfo,
// settings
] as const;
const routes = [mgtEvents, tagInfo, addReader, updateReader, manualTrigger] as const;
// app.route("/server", modules);
const appRoutes = routes.forEach((route) => {

View File

@@ -0,0 +1,51 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {addReader} from "../controller/addReader.js";
import {authMiddleware} from "../../auth/middleware/authMiddleware.js";
import {responses} from "../../../globalUtils/routeDefs/responses.js";
import type {User} from "../../../types/users.js";
import {verify} from "hono/jwt";
const app = new OpenAPIHono();
export const ReaderBody = z.object({
reader: z.string().openapi({example: "wrapper1"}),
readerIP: z.string().openapi({example: "192.168.1.52"}),
});
app.openapi(
createRoute({
tags: ["rfid"],
summary: "Add new reader",
method: "post",
path: "/addreader",
middleware: authMiddleware,
description: "Adding in a new reader to add to the network.",
request: {
body: {content: {"application/json": {schema: ReaderBody}}},
},
responses: responses(),
}),
async (c) => {
const body = await c.req.json();
const authHeader = c.req.header("Authorization");
const token = authHeader?.split("Bearer ")[1] || "";
let user: User;
try {
const payload = await verify(token, process.env.JWT_SECRET!);
user = payload.user as User;
} catch (error) {
return c.json({message: "Unauthorized"}, 401);
}
try {
const addingReader = await addReader(body, user);
return c.json({success: addingReader.success, message: addingReader.message}, 200);
} catch (error) {
return c.json({success: false, message: `${body.name} encountered an error while trying to be added`}, 400);
}
}
);
export default app;

View File

@@ -0,0 +1,40 @@
//http://usday1vms006:4000/api/v1/zebra/wrapper1
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {responses} from "../../../globalUtils/routeDefs/responses.js";
import {readTags} from "../controller/readTags.js";
const app = new OpenAPIHono();
const ParamsSchema = z.object({
reader: z
.string()
.min(3)
.openapi({
param: {
name: "reader",
in: "path",
},
example: "1212121",
}),
});
app.openapi(
createRoute({
tags: ["rfid"],
summary: "Manual triggers the read function",
method: "post",
path: "/manualtrigger/{reader}",
request: {
params: ParamsSchema,
},
responses: responses(),
}),
async (c) => {
const {reader} = c.req.valid("param");
const manualTrigger = await readTags(reader);
return c.json({success: true, message: `A Manaul trigger was done on ${reader}`}, 200);
}
);
export default app;

View File

@@ -2,12 +2,8 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {readTags} from "../controller/readTags.js";
import {createLog} from "../../logger/logger.js";
// Define the response schema
const responseSchema = z.object({
success: z.boolean().openapi({example: true}),
message: z.string().optional(),
});
import {responses} from "../../../globalUtils/routeDefs/responses.js";
import {newHeartBeat} from "../controller/readerControl.js";
const app = new OpenAPIHono();
let lastGpiTimestamp = 0;
@@ -28,64 +24,37 @@ const ParamsSchema = z.object({
app.openapi(
createRoute({
tags: ["rfid"],
summary: "Adds a new module",
summary: "Post info from the reader",
method: "post",
path: "/mgtevents/{reader}",
request: {
params: ParamsSchema,
},
responses: {
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",
},
},
responses: responses(),
}),
async (c) => {
const {reader} = c.req.valid("param");
const body = await c.req.json();
if (body.type === "heartbeat") {
console.log("Heartbeat");
const heart = await newHeartBeat(reader);
return c.json({success: heart.success, message: heart.message}, 200);
}
if (body.type === "gpi" && body.data.state === "HIGH") {
const eventTimestamp = new Date(body.timestamp).getTime(); // Convert ISO timestamp to milliseconds
if (eventTimestamp - lastGpiTimestamp > 10) {
if (eventTimestamp - lastGpiTimestamp > 5 * 1000) {
// Check if it's been more than 2ms
lastGpiTimestamp = eventTimestamp; // Update last seen timestamp
createLog("info", "rfid", "rfid", `${reader} is reading a tag.`);
readTags(reader);
await readTags(reader);
} else {
console.log("Duplicate GPI event ignored.");
createLog("info", "rfid", "rfid", `A new trigger from ${reader} was to soon`);
lastGpiTimestamp = eventTimestamp;
}
//console.log(body);
}
return c.json({success: true, message: `New info from ${reader}`}, 200);

View File

@@ -2,12 +2,9 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {ConsoleLogWriter} from "drizzle-orm";
import {tagData} from "../controller/tagData.js";
// Define the response schema
const responseSchema = z.object({
success: z.boolean().openapi({example: true}),
message: z.string().optional(),
});
import {responses} from "../../../globalUtils/routeDefs/responses.js";
import {noRead} from "../controller/noRead.js";
import {badRead, goodRead} from "../controller/readerControl.js";
const app = new OpenAPIHono();
@@ -27,44 +24,13 @@ const ParamsSchema = z.object({
app.openapi(
createRoute({
tags: ["rfid"],
summary: "Adds a new module",
summary: "Tag info posted from the reader.",
method: "post",
path: "/taginfo/{reader}",
request: {
params: ParamsSchema,
},
responses: {
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",
},
},
responses: responses(),
}),
async (c) => {
const {reader} = c.req.valid("param");
@@ -74,17 +40,31 @@ app.openapi(
for (let i = 0; i < body.length; i++) {
const tag = Buffer.from(body[i].data.idHex, "hex").toString("utf-8");
if (tag.includes("ALPLA")) {
//console.log("Raw value:", body[i].data.peakRssi, "Parsed:", parseInt(body[i].data.peakRssi));
if (tag.includes("ALPLA") && parseInt(body[i].data.peakRssi) < -50) {
tagdata = [
...tagdata,
{tagHex: body[i].data.idHex, reader: reader, tag: tag, timeStamp: body[i].timestamp},
{
tagHex: body[i].data.idHex,
reader: reader,
tag: tag,
timeStamp: body[i].timestamp,
antenna: body[i].data.antenna,
tagStrength: body[i].data.peakRssi,
},
];
}
}
tagData(tagdata);
return c.json({success: true, message: `New info from ${reader}`}, 200);
if (tagdata.length === 0) {
noRead(reader);
badRead(reader);
return c.json({success: false, message: `There were no tags scanned.`}, 200);
} else {
tagData(tagdata);
goodRead(reader);
return c.json({success: true, message: `New info from ${reader}`}, 200);
}
}
);

View File

@@ -0,0 +1,52 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {addReader} from "../controller/addReader.js";
import {authMiddleware} from "../../auth/middleware/authMiddleware.js";
import {responses} from "../../../globalUtils/routeDefs/responses.js";
import type {User} from "../../../types/users.js";
import {verify} from "hono/jwt";
const app = new OpenAPIHono();
export const ReaderBody = z.object({
reader: z.string().openapi({example: "wrapper1"}),
readerIP: z.string().openapi({example: "192.168.1.52"}),
active: z.boolean().optional().openapi({example: true}),
});
app.openapi(
createRoute({
tags: ["rfid"],
summary: "Add new reader",
method: "patch",
path: "/updatereader",
middleware: authMiddleware,
description: "Updates the reader data..",
request: {
body: {content: {"application/json": {schema: ReaderBody}}},
},
responses: responses(),
}),
async (c) => {
const body = await c.req.json();
const authHeader = c.req.header("Authorization");
const token = authHeader?.split("Bearer ")[1] || "";
let user: User;
try {
const payload = await verify(token, process.env.JWT_SECRET!);
user = payload.user as User;
} catch (error) {
return c.json({message: "Unauthorized"}, 401);
}
try {
const addingReader = await addReader(body, user);
return c.json({success: addingReader.success, message: addingReader.message}, 200);
} catch (error) {
return c.json({success: false, message: `${body.name} encountered an error while trying to be added`}, 400);
}
}
);
export default app;