Compare commits

...

9 Commits

41 changed files with 1189 additions and 832 deletions

View File

@@ -1,5 +1,77 @@
# All Changes to LST can be found below. # All Changes to LST can be found below.
## [1.6.0](https://git.tuffraid.net/cowch/lst/compare/v1.5.0...v1.6.0) (2025-10-26)
### 📝 Testing Code
* **dock schedule fail:** failed attempt ad doing a dock schedule but leaving in here ([817a5c6](https://git.tuffraid.net/cowch/lst/commits/817a5c6876b338e4e0347eed94d0c2d9507e7ba3))
### 📈 Project changes
* **added biome linter in:** added in biom linter to assist ([2023c2f](https://git.tuffraid.net/cowch/lst/commits/2023c2fc35f8d57a6884d82b3710a03f3ccc57e7))
* **app:** config changes to bruno and incoming sqls ([f264c98](https://git.tuffraid.net/cowch/lst/commits/f264c98fbfccd4f1eb6dfdcb28b69903711a9e2b))
* **settings:** settings for biome ([dfff8fc](https://git.tuffraid.net/cowch/lst/commits/dfff8fc1667a5199a9f92bdbf7df1a1b19606b82))
### 📚 Documentation
* **api:** bruno endpoint saves ([705f29e](https://git.tuffraid.net/cowch/lst/commits/705f29e908b75e8ba8d09a9fc4a2b5745460babb))
* **bruno:** preprint in app added ([a2a8e0e](https://git.tuffraid.net/cowch/lst/commits/a2a8e0ef9f7086ba9d32bc4ec05a61e6904bfecc))
### 🛠️ Code Refactor
* **biome:** format changes ([dbe84d5](https://git.tuffraid.net/cowch/lst/commits/dbe84d5325291fb51f971426ec521c91eafa3537))
* **biome:** formats from biome ([27fa456](https://git.tuffraid.net/cowch/lst/commits/27fa45614e604a768c49dc4489fbf7a671364a7b))
* **biome:** more format changes ([255ceaa](https://git.tuffraid.net/cowch/lst/commits/255ceaab856e72435bbc3ebad37fb82c036f5208))
* **front end:** login fixes to account for the forced password change ([e99c409](https://git.tuffraid.net/cowch/lst/commits/e99c409cad049f781d4a52864f40264146a2bb49))
* **frontend:** tempt to stop the popup when redrected or coming to the page with no auth ([0fd777c](https://git.tuffraid.net/cowch/lst/commits/0fd777ccbdab2e8de8dcc02c134e01390bbc0d0a))
* **login:** added in a check for lastlogin and force reset password ([17e13d4](https://git.tuffraid.net/cowch/lst/commits/17e13d4604787d1473ae1e24ad4e9479087f6dce))
* **material checks:** added proper logging to capture it all for auditing later ([26b769f](https://git.tuffraid.net/cowch/lst/commits/26b769f4776df2833e3f27b02e5eedbc9f8693a6))
* **old app:** login migration to new app ([eb3fa4d](https://git.tuffraid.net/cowch/lst/commits/eb3fa4dd528427da49e2212bfa304ef9cdb06cc2))
* **plc connection zechetti:** added in more logging due to a weird issue with line 7 not sendin ([38edc62](https://git.tuffraid.net/cowch/lst/commits/38edc6214b353841a3414a66553446d4008ad54a))
* **printdelay:** added in a change to allow override the actualy time ([c59b6a1](https://git.tuffraid.net/cowch/lst/commits/c59b6a1ec27ecb8e5b6b08c8db7aee5bcb060801))
* **stats:** added in ram useage to the stats ([b9b0cd5](https://git.tuffraid.net/cowch/lst/commits/b9b0cd5c7010726532ef56ddab714308c8045b94))
* **wrapper:** changes to allow both controller and app to connect via wss:// ([da11270](https://git.tuffraid.net/cowch/lst/commits/da1127057cd766ec72316dee1ffcb11aed77904b))
### 🌟 Enhancements
* **admin:** users and roles added to the frontend to manage easier ([2142c06](https://git.tuffraid.net/cowch/lst/commits/2142c06ac3900aa70f1cf672b5a64102ed1c574f))
* **app:** order schdeuler ([94e1198](https://git.tuffraid.net/cowch/lst/commits/94e1198f6305751af7662a63e0ac21ac04f805d1))
* **frontend:** migrated old > new silo adjustments ([425f8f5](https://git.tuffraid.net/cowch/lst/commits/425f8f5f71d1dae1cf3a5d0307b3a2faeadb54b5))
* **labeling:** added printers and machine and other data for preprinting ([953af5e](https://git.tuffraid.net/cowch/lst/commits/953af5e0fea4cf0738a2bbfd3ee6ec46182b83dd))
* **labels:** added listener for old app to push all labels to the new app ([af079b8](https://git.tuffraid.net/cowch/lst/commits/af079b83069560f0a0d6f19c396a8238fba25e94))
* **migration:** dashboard migrated over ([2206a4d](https://git.tuffraid.net/cowch/lst/commits/2206a4d4baefdd770c83a03d68c9f5ac8f55a4c3))
* **migration:** dm moved ([ac9670d](https://git.tuffraid.net/cowch/lst/commits/ac9670d55340a3cc8e45d13ac1c09a056d06d1dd))
* **migration:** moved barcode generator ([fd9d774](https://git.tuffraid.net/cowch/lst/commits/fd9d774772aabb63fd69fe70302444fd2088d960))
* **migration:** moved changed log and properly added in the link to it ([0fe0a8f](https://git.tuffraid.net/cowch/lst/commits/0fe0a8f56a9833de1de4f8ba49f1d06b31e42ee8))
* **migration:** moved helper commands ([39c31aa](https://git.tuffraid.net/cowch/lst/commits/39c31aa1ec61231737cc4e7c3c33331ab562a808))
* **migration:** moved material helper over ([1da7b14](https://git.tuffraid.net/cowch/lst/commits/1da7b145a942dd64891511a8a63160748800de87))
* **migration:** moved ocp, ocme, wrapper stuff ([4ca20a0](https://git.tuffraid.net/cowch/lst/commits/4ca20a085efcb795bc312abff649a53132deac05))
* **migration:** moved rfid page ([f90a975](https://git.tuffraid.net/cowch/lst/commits/f90a975a5020a262109552019a13ead71271396c))
* **preprint:** added in preprint function to help with operations planning constraints ([282eab0](https://git.tuffraid.net/cowch/lst/commits/282eab01e15f81bcc407f45f1f3ffff056e0f27a))
* **settings:** added in settings ([a09ad87](https://git.tuffraid.net/cowch/lst/commits/a09ad8773c77b7b23ce98b3b3f6ce6122842f3ff))
* **v1 logger:** added in a logger to monitor the old app ([1d79195](https://git.tuffraid.net/cowch/lst/commits/1d79195d89cc31192c9998ef2b2f8ea501aff41e))
### 🐛 Bug fixes
* **admin:** corrections to sending over to test server for updates ([7964cda](https://git.tuffraid.net/cowch/lst/commits/7964cda197e42f2eadea3e636c2d910cb34c97bc))
* **barcodes:** moved to correct folder ([6a84da4](https://git.tuffraid.net/cowch/lst/commits/6a84da411770986f6f4c2088ebae169c688b28c1))
* **controller:** changed to actaully update both main and old app ([0d1f963](https://git.tuffraid.net/cowch/lst/commits/0d1f96333b11e6a2323e25552c10cc85d3c425af))
* **controller:** fix for updating iowa2 server ([358c41d](https://git.tuffraid.net/cowch/lst/commits/358c41deb41d418c5b1c6040269f2ed74e1782ff))
* **dmbuttons:** missed the uncomment here ([e620397](https://git.tuffraid.net/cowch/lst/commits/e62039793870a4d4b1f67adf256a9db2f6027e37))
* **loginform:** bug where the reset errror was not properly coming over ([414a21a](https://git.tuffraid.net/cowch/lst/commits/414a21a28719b50f61cc41056efc9b599491e048))
* **loginform:** error with password reset ([eae9436](https://git.tuffraid.net/cowch/lst/commits/eae9436f6d3aa8424043a426d5bbcc764967b3b6))
* **misc:** changes to several files for formatting ([b102112](https://git.tuffraid.net/cowch/lst/commits/b102112228bbf58b12bbca19cdf99483517b784d))
* **ocme:** corrected the urls for ocme ([f078cd6](https://git.tuffraid.net/cowch/lst/commits/f078cd6ceb9039e1f6e9b31f3e33ad446e65dd87))
* **old app:** correction for dev redirect if on localhost to be proper now ([70a2ff5](https://git.tuffraid.net/cowch/lst/commits/70a2ff5e845d9a8c460f4810f8de741fae32ad96))
* **printers:** missing logs for errors ([43abbd5](https://git.tuffraid.net/cowch/lst/commits/43abbd53f421988a13cbf8974a49d5ae5cbba1b9))
* **silos:** added only active machines, and ordered by location in asending ([d46ef92](https://git.tuffraid.net/cowch/lst/commits/d46ef922f3a6ddc36d5ddfcd94d424745f473a16))
## [1.5.0](https://git.tuffraid.net/cowch/lst/compare/v1.4.0...v1.5.0) (2025-10-07) ## [1.5.0](https://git.tuffraid.net/cowch/lst/compare/v1.4.0...v1.5.0) (2025-10-07)

View File

@@ -0,0 +1,25 @@
meta {
name: CreateSilo Adjustment
type: http
seq: 3
}
post {
url: {{url}}/lst/old/api/logistics/createsiloadjustment
body: json
auth: inherit
}
body:json {
{
"warehouseId": 51,
"quantity": 60575,
"laneId": 31717,
"username":"matthes01"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,8 @@
meta {
name: ocp
seq: 6
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,16 @@
meta {
name: Update Printers
type: http
seq: 1
}
get {
url: {{url}}/lst/old/api/ocp/updateprinters
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,8 @@
meta {
name: printers
seq: 1
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,26 @@
meta {
name: Change user password
type: http
seq: 5
}
patch {
url: {{url}}/lst/api/admin/users/changePassword/:userId
body: json
auth: inherit
}
params:path {
userId: 0hlO48C7Jw1J804FxrCnonKjQ2zh48R6
}
body:json {
{
"password":"nova0511"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,25 @@
meta {
name: Create user
type: http
seq: 4
}
post {
url: {{url}}/lst/api/admin/users
body: none
auth: inherit
}
body:json {
{
"username":"matthes01",
"name":"blake",
"email":"blake.matthes@alpla.com",
"password":"nova0511"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,26 @@
meta {
name: Delete User
type: http
seq: 6
}
delete {
url: {{url}}/lst/api/admin/users/delete/:userId
body: json
auth: inherit
}
params:path {
userId: 0hlO48C7Jw1J804FxrCnonKjQ2zh48R6
}
body:json {
{
"password":"nova0511"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -21,4 +21,5 @@ body:json {
settings { settings {
encodeUrl: true encodeUrl: true
timeout: 0
} }

View File

@@ -5,7 +5,7 @@ meta {
} }
patch { patch {
url: {{url}}/lst/api/admin/:userID/grant url: {{url}}/lst/api/admin/users/:userID/grant
body: json body: json
auth: inherit auth: inherit
} }

View File

@@ -5,7 +5,7 @@ meta {
} }
patch { patch {
url: {{url}}/lst/api/admin/:userID/revoke url: {{url}}/lst/api/admin/users/:userID/revoke
body: json body: json
auth: inherit auth: inherit
} }

View File

@@ -1,5 +1,5 @@
vars { vars {
url: https://usmcd1vms036.alpla.net url: http://localhost:4200
session_cookie: session_cookie:
urlv2: http://localhost:3000 urlv2: http://localhost:3000
jwtV2: jwtV2:

View File

@@ -86,7 +86,7 @@ const main = async () => {
app.use( app.use(
basePath + "/old", basePath + "/old",
createProxyMiddleware({ createProxyMiddleware({
target: `http://localhost:3000`, target: `http://localhost:${process.env.V1PORT || "3000"}`, // change this to pull from the correct port
changeOrigin: true, changeOrigin: true,
pathRewrite: (path, req) => { pathRewrite: (path, req) => {
// Remove the basePath + '/old' prefix from the path dynamically // Remove the basePath + '/old' prefix from the path dynamically
@@ -113,7 +113,9 @@ const main = async () => {
"http://localhost:4173", "http://localhost:4173",
"http://localhost:4200", "http://localhost:4200",
"http://localhost:3000", "http://localhost:3000",
"http://localhost:3001",
"http://localhost:4000", "http://localhost:4000",
"http://localhost:4001",
env.BETTER_AUTH_URL, // prod env.BETTER_AUTH_URL, // prod
]; ];

View File

@@ -0,0 +1,42 @@
import type { User } from "better-auth";
import { DrizzleQueryError } from "drizzle-orm";
import { auth } from "../../../../pkg/auth/auth.js";
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
export type NewUser = {
email: string;
password: string;
username: string;
statusCode: number;
message: string;
};
export const createNewUser = async (userData: NewUser) => {
const { data, error } = await tryCatch(
auth.api.createUser({
body: {
email: userData.email, // required
password: userData.password, // required
name: userData.username, // required
role: "user",
data: { username: userData.username },
},
}),
);
if (error) {
if (error instanceof DrizzleQueryError) {
// @ts-ignore
if (error?.cause.message.includes("unique constraint")) {
return {
statusCode: 400,
message: `${userData.username} already exists`,
};
}
}
return error;
}
return data;
};

View File

@@ -1,11 +1,9 @@
import type { Express, Request, Response } from "express"; import type { Express, Request, Response } from "express";
import { requireAuth } from "../../pkg/middleware/authMiddleware.js"; import { requireAuth } from "../../pkg/middleware/authMiddleware.js";
import { mainServerSync } from "./controller/servers/matchServers.js"; import { mainServerSync } from "./controller/servers/matchServers.js";
//admin routes
import users from "./routes/getUserRoles.js";
import grantRoles from "./routes/grantRole.js";
import revokeRoles from "./routes/revokeRole.js";
import servers from "./routes/servers/serverRoutes.js"; import servers from "./routes/servers/serverRoutes.js";
//admin routes
import users from "./routes/users/userRoutes.js";
export const setupAdminRoutes = (app: Express, basePath: string) => { export const setupAdminRoutes = (app: Express, basePath: string) => {
app.use( app.use(
@@ -15,22 +13,10 @@ export const setupAdminRoutes = (app: Express, basePath: string) => {
app.use( app.use(
basePath + "/api/admin/users", basePath + "/api/admin/users",
requireAuth("user", ["systemAdmin"]), // will pass bc system admin but this is just telling us we need this requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
users, users,
); );
app.use(
basePath + "/api/admin",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
grantRoles,
);
app.use(
basePath + "/api/admin",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
revokeRoles,
);
// run the sync only on startup // run the sync only on startup
setTimeout(() => { setTimeout(() => {
mainServerSync(); mainServerSync();

View File

@@ -1,52 +0,0 @@
import { Router } from "express";
import type { Request, Response } from "express";
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
import { db } from "../../../pkg/db/db.js";
import { user } from "../../../pkg/db/schema/auth-schema.js";
import { userRoles } from "../../../pkg/db/schema/user_roles.js";
const router = Router();
router.post("/", async (req: Request, res: Response) => {
// should get all users
const { data: users, error: userError } = await tryCatch(
db.select().from(user)
);
if (userError) {
return res.status(500).json({
success: false,
message: "Failed to get users",
error: userError,
});
}
// should get all roles
const { data: userRole, error: userRoleError } = await tryCatch(
db.select().from(userRoles)
);
if (userRoleError) {
return res.status(500).json({
success: false,
message: "Failed to get userRoless",
error: userRoleError,
});
}
// add the roles and return
const usersWithRoles = users.map((user) => {
const roles = userRole
.filter((ur) => ur.userId === user.id)
.map((ur) => ({ module: ur.module, role: ur.role }));
return { ...user, roles };
});
return res
.status(200)
.json({ success: true, message: "User data", data: usersWithRoles });
});
export default router;

View File

@@ -0,0 +1,25 @@
import { type Request, type Response, Router } from "express";
import { auth } from "../../../../pkg/auth/auth.js";
const router = Router();
router.patch("/:userId", async (req: Request, res: Response) => {
const userId = req.params.userId;
const cookieHeader = req.headers.cookie ?? "";
const authorization = req.headers.authorization ?? "";
const data = await auth.api.setUserPassword({
body: {
newPassword: req.body.password, // required
userId: userId, // required
},
// This endpoint requires session cookies.
headers: {
cookie: cookieHeader,
authorization,
},
});
return res.status(200).json({ message: "Password was just changed." });
});
export default router;

View File

@@ -0,0 +1,20 @@
import { type Request, type Response, Router } from "express";
import { createNewUser, type NewUser } from "../../controller/users/newUser.js";
const router = Router();
router.post("/", async (req: Request, res: Response) => {
const body = req.body;
const user = (await createNewUser(body)) as NewUser;
if (user?.statusCode === 400) {
return res.status(user?.statusCode).json({
message: user?.message,
});
}
return res
.status(200)
.json({ message: `${body.username}, was just created` });
});
export default router;

View File

@@ -0,0 +1,24 @@
import { type Request, type Response, Router } from "express";
import { auth } from "../../../../pkg/auth/auth.js";
const router = Router();
router.delete("/:userId", async (req: Request, res: Response) => {
const userId = req.params.userId;
const cookieHeader = req.headers.cookie ?? "";
const authorization = req.headers.authorization ?? "";
const data = await auth.api.removeUser({
body: {
userId: userId, // required
},
// This endpoint requires session cookies.
headers: {
cookie: cookieHeader,
authorization,
},
});
return res.status(200).json({ message: "User was just deleted." });
});
export default router;

View File

@@ -0,0 +1,25 @@
import { type Request, type Response, Router } from "express";
import { auth } from "../../../../pkg/auth/auth.js";
const router = Router();
router.patch("/:userId", async (req: Request, res: Response) => {
const userId = req.params.userId;
const cookieHeader = req.headers.cookie ?? "";
const authorization = req.headers.authorization ?? "";
const data = await auth.api.setUserPassword({
body: {
newPassword: req.body.password, // required
userId: userId, // required
},
// This endpoint requires session cookies.
headers: {
cookie: cookieHeader,
authorization,
},
});
return res.status(200).json({ message: "Password was just changed." });
});
export default router;

View File

@@ -0,0 +1,52 @@
import type { Request, Response } from "express";
import { Router } from "express";
import { db } from "../../../../pkg/db/db.js";
import { user } from "../../../../pkg/db/schema/auth-schema.js";
import { userRoles } from "../../../../pkg/db/schema/user_roles.js";
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
const router = Router();
router.post("/", async (req: Request, res: Response) => {
// should get all users
const { data: users, error: userError } = await tryCatch(
db.select().from(user),
);
if (userError) {
return res.status(500).json({
success: false,
message: "Failed to get users",
error: userError,
});
}
// should get all roles
const { data: userRole, error: userRoleError } = await tryCatch(
db.select().from(userRoles),
);
if (userRoleError) {
return res.status(500).json({
success: false,
message: "Failed to get userRoless",
error: userRoleError,
});
}
// add the roles and return
const usersWithRoles = users.map((user) => {
const roles = userRole
.filter((ur) => ur.userId === user.id)
.map((ur) => ({ module: ur.module, role: ur.role }));
return { ...user, roles };
});
return res
.status(200)
.json({ success: true, message: "User data", data: usersWithRoles });
});
export default router;

View File

@@ -1,10 +1,9 @@
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { Router } from "express"; import { Router } from "express";
import z from "zod"; import z from "zod";
import { db } from "../../../pkg/db/db.js"; import { db } from "../../../../pkg/db/db.js";
import { userRoles } from "../../../pkg/db/schema/user_roles.js"; import { userRoles } from "../../../../pkg/db/schema/user_roles.js";
import { createLogger } from "../../../pkg/logger/logger.js"; import { createLogger } from "../../../../pkg/logger/logger.js";
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
const roleSchema = z.object({ const roleSchema = z.object({
module: z.enum([ module: z.enum([

View File

@@ -2,10 +2,9 @@ import { and, eq } from "drizzle-orm";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { Router } from "express"; import { Router } from "express";
import z from "zod"; import z from "zod";
import { db } from "../../../pkg/db/db.js"; import { db } from "../../../../pkg/db/db.js";
import { userRoles } from "../../../pkg/db/schema/user_roles.js"; import { userRoles } from "../../../../pkg/db/schema/user_roles.js";
import { createLogger } from "../../../pkg/logger/logger.js"; import { createLogger } from "../../../../pkg/logger/logger.js";
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
const roleSchema = z.object({ const roleSchema = z.object({
module: z.enum([ module: z.enum([

View File

@@ -0,0 +1,51 @@
import { fromNodeHeaders } from "better-auth/node";
import type { Request, Response } from "express";
import { Router } from "express";
import { auth } from "../../../../pkg/auth/auth.js";
import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js";
import changePassword from "./changeUserPassword.js";
import createUser from "./createUser.js";
import deleteUser from "./deleteUser.js";
import users from "./getUserRoles.js";
import grantRoles from "./grantRole.js";
import revokeRoles from "./revokeRole.js";
const router = Router();
router.use(
"/",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
grantRoles,
);
router.use(
"/new",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
createUser,
);
router.use(
"/",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
revokeRoles,
);
router.use(
"/",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
users,
);
router.use(
"/changePassword",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
changePassword,
);
router.use(
"/delete",
requireAuth("user", ["systemAdmin", "admin"]), // will pass bc system admin but this is just telling us we need this
deleteUser,
);
export default router;

View File

@@ -1,89 +1,93 @@
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "../db/db.js";
import { username, admin, apiKey, jwt } from "better-auth/plugins";
import { betterAuth } from "better-auth"; import { betterAuth } from "better-auth";
import * as rawSchema from "../db/schema/auth-schema.js"; import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin, apiKey, jwt, username } from "better-auth/plugins";
import type { User } from "better-auth/types"; import type { User } from "better-auth/types";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "../db/db.js";
import * as rawSchema from "../db/schema/auth-schema.js";
import { sendEmail } from "../utils/mail/sendMail.js"; import { sendEmail } from "../utils/mail/sendMail.js";
export const schema = { export const schema = {
user: rawSchema.user, user: rawSchema.user,
session: rawSchema.session, session: rawSchema.session,
account: rawSchema.account, account: rawSchema.account,
verification: rawSchema.verification, verification: rawSchema.verification,
jwks: rawSchema.jwks, jwks: rawSchema.jwks,
apiKey: rawSchema.apikey, // 🔑 rename to apiKey apiKey: rawSchema.apikey, // 🔑 rename to apiKey
}; };
const RESET_EXPIRY_SECONDS = 3600; // 1 hour const RESET_EXPIRY_SECONDS = 3600; // 1 hour
export const auth = betterAuth({ export const auth = betterAuth({
database: drizzleAdapter(db, { database: drizzleAdapter(db, {
provider: "pg", provider: "pg",
schema, schema,
}), }),
trustedOrigins: [ trustedOrigins: [
"*.alpla.net", "*.alpla.net",
"http://localhost:5173", "http://localhost:5173",
"http://localhost:5500", "http://localhost:5500",
"http://localhost:4200", "http://localhost:4200",
"http://localhost:4000", "http://localhost:4000",
], ],
appName: "lst", appName: "lst",
emailAndPassword: { emailAndPassword: {
enabled: true, enabled: true,
minPasswordLength: 8, // optional config minPasswordLength: 8, // optional config
resetPasswordTokenExpirySeconds: RESET_EXPIRY_SECONDS, // time in seconds resetPasswordTokenExpirySeconds: RESET_EXPIRY_SECONDS, // time in seconds
sendResetPassword: async ({ user, token }) => { sendResetPassword: async ({ user, token }) => {
const frontendUrl = `${process.env.BETTER_AUTH_URL}/lst/app/user/resetpassword?token=${token}`; const frontendUrl = `${process.env.BETTER_AUTH_URL}/lst/app/user/resetpassword?token=${token}`;
const expiryMinutes = Math.floor(RESET_EXPIRY_SECONDS / 60); const expiryMinutes = Math.floor(RESET_EXPIRY_SECONDS / 60);
const expiryText = const expiryText =
expiryMinutes >= 60 expiryMinutes >= 60
? `${expiryMinutes / 60} hour${ ? `${expiryMinutes / 60} hour${expiryMinutes === 60 ? "" : "s"}`
expiryMinutes === 60 ? "" : "s" : `${expiryMinutes} minutes`;
}` const emailData = {
: `${expiryMinutes} minutes`; email: user.email,
const emailData = { subject: "LST- Forgot password request",
email: user.email, template: "forgotPassword",
subject: "LST- Forgot password request", context: {
template: "forgotPassword", username: user.name,
context: { email: user.email,
username: user.name, url: frontendUrl,
email: user.email, expiry: expiryText,
url: frontendUrl, },
expiry: expiryText, };
}, await sendEmail(emailData);
}; },
await sendEmail(emailData); // onPasswordReset: async ({ user }, request) => {
}, // // your logic here
// onPasswordReset: async ({ user }, request) => { // console.log(`Password for user ${user.email} has been reset.`);
// // your logic here // },
// console.log(`Password for user ${user.email} has been reset.`); },
// }, plugins: [
}, //jwt({ jwt: { expirationTime: "1h" } }),
plugins: [ apiKey(),
//jwt({ jwt: { expirationTime: "1h" } }), admin(),
apiKey(), username(),
admin(), ],
username(), session: {
], expiresIn: 60 * 60,
session: { updateAge: 60 * 5,
expiresIn: 60 * 60, freshAge: 60 * 2,
updateAge: 60 * 5, cookieCache: {
freshAge: 60 * 2, enabled: true,
cookieCache: { maxAge: 5 * 60,
enabled: true, },
maxAge: 5 * 60, // Cache duration in seconds },
}, cookie: {
}, path: "/lst/app",
events: { sameSite: "lax",
async onSignInSuccess({ user }: { user: User }) { secure: false,
await db httpOnly: true,
.update(schema.user) },
.set({ lastLogin: new Date() }) events: {
.where(eq(schema.user.id, user.id)); async onSignInSuccess({ user }: { user: User }) {
}, await db
}, .update(schema.user)
.set({ lastLogin: new Date() })
.where(eq(schema.user.id, user.id));
},
},
}); });
export type Auth = typeof auth; export type Auth = typeof auth;

View File

@@ -1,7 +1,7 @@
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { redirect, useNavigate, useRouter } from "@tanstack/react-router"; import { redirect, useNavigate, useRouter } from "@tanstack/react-router";
import { createAuthClient } from "better-auth/client"; import { createAuthClient } from "better-auth/client";
import { usernameClient } from "better-auth/client/plugins"; import { adminClient, usernameClient } from "better-auth/client/plugins";
import { useEffect } from "react"; import { useEffect } from "react";
import { create } from "zustand"; import { create } from "zustand";
import { api } from "./axiosAPI"; import { api } from "./axiosAPI";
@@ -112,7 +112,7 @@ export async function checkUserAccess({
// ---- BETTER AUTH CLIENT ---- // ---- BETTER AUTH CLIENT ----
export const authClient = createAuthClient({ export const authClient = createAuthClient({
baseURL: `${window.location.origin}/lst/api/auth`, baseURL: `${window.location.origin}/lst/api/auth`,
plugins: [usernameClient()], plugins: [usernameClient(), adminClient()],
options: { options: {
autoPopup: false, autoPopup: false,
requireAuth: false, requireAuth: false,

View File

@@ -59,7 +59,7 @@ export default function ExpandedRow({ row }: { row: any }) {
// user, // user,
// }); // });
try { try {
const result = await api.patch(`/api/admin/${user.id}/grant`, { const result = await api.patch(`/api/admin/users/${user.id}/grant`, {
module: module, module: module,
role: role, role: role,
}); });
@@ -83,7 +83,7 @@ export default function ExpandedRow({ row }: { row: any }) {
const onDeleteRole = async (module: string) => { const onDeleteRole = async (module: string) => {
try { try {
const result = await api.patch(`/api/admin/${user.id}/revoke`, { const result = await api.patch(`/api/admin/users/${user.id}/revoke`, {
module: module, module: module,
}); });

View File

@@ -3,6 +3,7 @@ import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
SidebarFooter, SidebarFooter,
SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
SidebarTrigger, SidebarTrigger,
} from "../../../../../components/ui/sidebar"; } from "../../../../../components/ui/sidebar";
@@ -51,7 +52,9 @@ export function AppSidebar() {
</SidebarContent> </SidebarContent>
<SidebarFooter> <SidebarFooter>
<SidebarMenuItem> <SidebarMenuItem>
<Link to={"/changelog"}>Changelog</Link> <SidebarMenuButton asChild>
<Link to={"/changelog"}>Changelog</Link>
</SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
<SidebarTrigger /> <SidebarTrigger />
</SidebarFooter> </SidebarFooter>

View File

@@ -0,0 +1,3 @@
export default function MaterialHelperPage() {
return <div>materialHelperPage</div>;
}

View File

@@ -7,10 +7,11 @@ import { Button } from "@/components/ui/button";
import { CardContent, CardFooter, CardHeader } from "@/components/ui/card"; import { CardContent, CardFooter, CardHeader } from "@/components/ui/card";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { useAuth } from "@/lib/authClient";
import { LstCard } from "../../extendedUi/LstCard"; import { LstCard } from "../../extendedUi/LstCard";
export default function Comment(data: any) { export default function Comment(data: any) {
const token = localStorage.getItem("auth_token"); const { session } = useAuth();
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter(); const router = useRouter();
@@ -27,8 +28,8 @@ export default function Comment(data: any) {
{ {
comment: value.comment, comment: value.comment,
key: data.id.split("&")[1].replace("amp;", ""), key: data.id.split("&")[1].replace("amp;", ""),
username: session?.user.username,
}, },
{ headers: { Authorization: `Bearer ${token}` } },
); );
if (res.data.success) { if (res.data.success) {

View File

@@ -43,6 +43,7 @@ export default function SiloCard(data: any) {
quantity: parseFloat(value.newLevel), quantity: parseFloat(value.newLevel),
warehouseId: silo.WarehouseID, warehouseId: silo.WarehouseID,
laneId: silo.LocationID, laneId: silo.LocationID,
username: session?.user.username,
}; };
try { try {

View File

@@ -1,153 +1,143 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useForm } from "@tanstack/react-form"; import { useForm } from "@tanstack/react-form";
import axios from "axios"; import axios from "axios";
import { format } from "date-fns"; import { format } from "date-fns";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default function ExportInventoryData() { export default function ExportInventoryData() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const form = useForm({ const form = useForm({
defaultValues: { defaultValues: {
age: "", age: "",
}, },
onSubmit: async ({ value }) => { onSubmit: async ({ value }) => {
setSaving(true); setSaving(true);
try { try {
const res = await axios.get( const res = await axios.get(
`/api/logistics/getcyclecount?age=${value.age}`, `/lst/old/api/logistics/getcyclecount?age=${value.age}`,
{ {
responseType: "blob", responseType: "blob",
} },
); );
const blob = new Blob([res.data], { const blob = new Blob([res.data], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}); });
const link = document.createElement("a"); const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob); link.href = window.URL.createObjectURL(blob);
link.download = `CycleCount-${format(new Date(Date.now()), "M-d-yyyy")}.xlsx`; // You can make this dynamic link.download = `CycleCount-${format(new Date(Date.now()), "M-d-yyyy")}.xlsx`; // You can make this dynamic
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
// Clean up // Clean up
document.body.removeChild(link); document.body.removeChild(link);
window.URL.revokeObjectURL(link.href); window.URL.revokeObjectURL(link.href);
toast.success(`File Downloaded`); toast.success(`File Downloaded`);
setSaving(false); setSaving(false);
setOpen(false); setOpen(false);
form.reset(); form.reset();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
console.log(`There was an error getting cycle counts.`); console.log(`There was an error getting cycle counts.`);
} }
}, },
}); });
return ( return (
<div> <div>
<Dialog <Dialog
open={open} open={open}
onOpenChange={(isOpen) => { onOpenChange={(isOpen) => {
if (!open) { if (!open) {
form.reset(); form.reset();
} }
setOpen(isOpen); setOpen(isOpen);
// toast.message("Model was something", { // toast.message("Model was something", {
// description: isOpen ? "Modal is open" : "Modal is closed", // description: isOpen ? "Modal is open" : "Modal is closed",
// }); // });
}} }}
> >
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline">Export Inventory Check</Button> <Button variant="outline">Export Inventory Check</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="sm:max-w-[425px]">
<DialogHeader> <DialogHeader>
<DialogTitle>Export Inventory lane check</DialogTitle> <DialogTitle>Export Inventory lane check</DialogTitle>
<DialogDescription> <DialogDescription>
Exports all lanes based on the age you enter, except Exports all lanes based on the age you enter, except empty lanes.
empty lanes. </DialogDescription>
</DialogDescription> </DialogHeader>
</DialogHeader> <form
<form onSubmit={(e) => {
onSubmit={(e) => { e.preventDefault();
e.preventDefault(); e.stopPropagation();
e.stopPropagation(); }}
}} >
> <div>
<div> <>
<> <form.Field
<form.Field name="age"
name="age" // validators={{
// validators={{ // // We can choose between form-wide and field-specific validators
// // We can choose between form-wide and field-specific validators // onChange: ({ value }) =>
// onChange: ({ value }) => // value.length > 3
// value.length > 3 // ? undefined
// ? undefined // : "Username must be longer than 3 letters",
// : "Username must be longer than 3 letters", // }}
// }} children={(field) => {
children={(field) => { return (
return ( <div className="m-2 min-w-48 max-w-96 p-2 flex flex-row">
<div className="m-2 min-w-48 max-w-96 p-2 flex flex-row"> <Label htmlFor="active">Age</Label>
<Label htmlFor="active"> <Input
Age className="ml-2"
</Label> name={field.name}
<Input onBlur={field.handleBlur}
className="ml-2" type="number"
name={field.name} onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur} />
type="number" </div>
onChange={(e) => );
field.handleChange( }}
e.target.value />
) </>
} </div>
/>
</div>
);
}}
/>
</>
</div>
<DialogFooter> <DialogFooter>
<div className="flex justify-end mt-2"> <div className="flex justify-end mt-2">
<Button onClick={() => setOpen(false)}> <Button onClick={() => setOpen(false)}>Close</Button>
Close <Button
</Button> type="submit"
<Button disabled={saving}
type="submit" onClick={form.handleSubmit}
disabled={saving} >
onClick={form.handleSubmit} {saving ? (
> <>
{saving ? ( <span>Saving....</span>
<> </>
<span>Saving....</span> ) : (
</> <span>Save setting</span>
) : ( )}
<span>Save setting</span> </Button>
)} </div>
</Button> </DialogFooter>
</div> </form>
</DialogFooter> </DialogContent>
</form> </Dialog>
</DialogContent> </div>
</Dialog> );
</div>
);
} }

View File

@@ -20,6 +20,7 @@ import { useAuth, useLogout } from "../../../lib/authClient";
import { AddCards } from "./-components/dashboard/AddCards"; import { AddCards } from "./-components/dashboard/AddCards";
import { AppSidebar } from "./-components/layout/lst-sidebar"; import { AppSidebar } from "./-components/layout/lst-sidebar";
import DMButtons from "./-components/logistics/dm/DMButtons"; import DMButtons from "./-components/logistics/dm/DMButtons";
import ExportInventoryData from "./-components/logistics/warehouse/ExportInventoryData";
export const Route = createFileRoute("/_old/old")({ export const Route = createFileRoute("/_old/old")({
component: RouteComponent, component: RouteComponent,
@@ -39,7 +40,7 @@ function RouteComponent() {
{location.pathname === "/lst/app/old" || {location.pathname === "/lst/app/old" ||
(location.pathname === "/lst/app/old/" && ( (location.pathname === "/lst/app/old/" && (
<div className="m-auto pr-2 flex flex-row gap-2"> <div className="m-auto pr-2 flex flex-row gap-2">
{/* <ExportInventoryData /> */} <ExportInventoryData />
<AddCards /> <AddCards />
</div> </div>
))} ))}

View File

@@ -6,17 +6,17 @@ const { sign, verify } = jwt;
export const authMiddleware: MiddlewareHandler = async (c, next) => { export const authMiddleware: MiddlewareHandler = async (c, next) => {
console.log("middleware checked"); console.log("middleware checked");
const cookieHeader = c.req.header("Cookie"); // const cookieHeader = c.req.header("Cookie");
if (!cookieHeader) return c.json({ error: "Unauthorized" }, 401); // if (!cookieHeader) return c.json({ error: "Unauthorized" }, 401);
const res = await axios.get(`${process.env.LST_BASE_URL}/api/user/me`, { // const res = await axios.get(`${process.env.LST_BASE_URL}/api/user/me`, {
headers: { Cookie: cookieHeader }, // headers: { Cookie: cookieHeader },
}); // });
if (res.status === 401) return c.json({ error: "Unauthorized" }, 401); // if (res.status === 401) return c.json({ error: "Unauthorized" }, 401);
//const user = await resp.json(); // //const user = await resp.json();
c.set("user", res.data.user); // c.set("user", res.data.user);
return next(); return next();
}; };

View File

@@ -84,26 +84,26 @@ interface UserRole {
const hasCorrectRole = (requiredRole: string[], module: string) => const hasCorrectRole = (requiredRole: string[], module: string) =>
createMiddleware(async (c, next) => { createMiddleware(async (c, next) => {
const cookieHeader = c.req.header("Cookie"); // const cookieHeader = c.req.header("Cookie");
if (!cookieHeader) return c.json({ error: "Unauthorized" }, 401); // if (!cookieHeader) return c.json({ error: "Unauthorized" }, 401);
const res = await axios.get(`${process.env.LST_BASE_URL}/api/user/roles`, { // const res = await axios.get(`${process.env.LST_BASE_URL}/api/user/roles`, {
headers: { Cookie: cookieHeader }, // headers: { Cookie: cookieHeader },
}); // });
const currentRoles: UserRole[] = res.data.data; // const currentRoles: UserRole[] = res.data.data;
const canAccess = currentRoles.some( // const canAccess = currentRoles.some(
(r) => r.module === module && requiredRole.includes(r.role), // (r) => r.module === module && requiredRole.includes(r.role),
); // );
if (!canAccess) { // if (!canAccess) {
return c.json( // return c.json(
{ // {
error: "Unauthorized", // error: "Unauthorized",
message: `You do not have access to ${module}`, // message: `You do not have access to ${module}`,
}, // },
400, // 400,
); // );
} // }
return next(); return next();
}); });

View File

@@ -44,7 +44,7 @@ app.openapi(
try { try {
try { try {
//return apiReturn(c, true, access?.message, access?.data, 200); //return apiReturn(c, true, access?.message, access?.data, 200);
const createSiloAdj = await createSiloAdjustment(data, c.get("user")); const createSiloAdj = await createSiloAdjustment(data, data.username);
return c.json( return c.json(
{ {

View File

@@ -55,7 +55,7 @@ app.openapi(
adjId, adjId,
data.comment, data.comment,
data.key, data.key,
c.get("user"), data.username,
); );
console.log(addComment); console.log(addComment);

View File

@@ -6,289 +6,273 @@ import { machineCheck } from "../../../sqlServer/querys/ocp/machineId.js";
import { mmQuery } from "../../../sqlServer/querys/ocp/mainMaterial.js"; import { mmQuery } from "../../../sqlServer/querys/ocp/mainMaterial.js";
export const isMainMatStaged = async (lot: any) => { export const isMainMatStaged = async (lot: any) => {
const set = serverSettings.length === 0 ? [] : serverSettings; const set = serverSettings.length === 0 ? [] : serverSettings;
// make staged false by deefault and error logged if theres an issue // make staged false by deefault and error logged if theres an issue
let isStaged = { message: "Material is staged", success: true }; let isStaged = { message: "Material is staged", success: true };
const { data, error } = (await tryCatch( const { data, error } = (await tryCatch(
query( query(
machineCheck.replace("where Active = 1 and [Location] = [loc]", ""), machineCheck.replace("where Active = 1 and [Location] = [loc]", ""),
"check machine needs mm" "check machine needs mm",
) ),
)) as any; )) as any;
const machine = data.data.filter( const machine = data.data.filter(
(m: any) => m.HumanReadableId === lot.machineID (m: any) => m.HumanReadableId === lot.machineID,
); );
// we have a check on ksc side to ignore the tetra machine for now as its not updating in 2.0 // we have a check on ksc side to ignore the tetra machine for now as its not updating in 2.0
if (!machine[0].StagingMainMaterialMandatory) { if (!machine[0].StagingMainMaterialMandatory) {
createLog( createLog(
"info", "info",
"mainMaterial", "mainMaterial",
"ocp", "ocp",
`The machine dose not require mm to print and book in.` `The machine dose not require mm to print and book in.`,
); );
return { return {
message: "Machine dose not require material to be staged", message: "Machine dose not require material to be staged",
success: true, success: true,
}; };
} }
// strangly the lot is not always sending over in slc so adding this in for now to see what line is cauing this issue // strangly the lot is not always sending over in slc so adding this in for now to see what line is cauing this issue
if (!lot) { if (!lot) {
createLog( createLog("info", "mainMaterial", "ocp", "No lot was passed correctly.");
"info", return isStaged;
"mainMaterial", }
"ocp",
"No lot was passed correctly."
);
return isStaged;
}
if (typeof lot !== "object" || lot === null || Array.isArray(lot)) { if (typeof lot !== "object" || lot === null || Array.isArray(lot)) {
createLog( createLog(
"info", "info",
"mainMaterial", "mainMaterial",
"ocp", "ocp",
`The lot sent over is not an object: ${JSON.stringify(lot)}` `The lot sent over is not an object: ${JSON.stringify(lot)}`,
); );
return isStaged; return isStaged;
} }
const updateQuery = mmQuery.replaceAll("[lotNumber]", lot.lot); const updateQuery = mmQuery.replaceAll("[lotNumber]", lot.lot);
try { try {
const r: any = await query(updateQuery, "Main Material Check"); const r: any = await query(updateQuery, "Main Material Check");
const res: any = r.data; const res: any = r.data;
// if (res[0].Staged >= 1) { // if (res[0].Staged >= 1) {
// isStaged = true; // isStaged = true;
// } // }
type CheckConditionArgs = { type CheckConditionArgs = {
results: any[]; results: any[];
filterFn: (n: any) => boolean; filterFn: (n: any) => boolean;
failCondition: (n: any) => boolean; failCondition: (n: any) => boolean;
failMessage: string; failMessage: string;
successMessage: string; successMessage: string;
lot: { lot: string | number }; lot: { lot: string | number };
}; };
const checkCondition = ({ const checkCondition = ({
results, results,
filterFn, filterFn,
failCondition, failCondition,
failMessage, failMessage,
successMessage, successMessage,
lot, lot,
}: CheckConditionArgs): { message: string; success: boolean } => { }: CheckConditionArgs): { message: string; success: boolean } => {
const subset = results.filter(filterFn); const subset = results.filter(filterFn);
if (subset.some(failCondition)) { if (subset.some(failCondition)) {
const failing = subset.filter(failCondition); const failing = subset.filter(failCondition);
createLog( createLog(
"info", "info",
"mainMaterial", "mainMaterial",
"ocp", "ocp",
`lot: ${lot.lot}, is missing: ${failing `lot: ${lot.lot}, is missing: ${failing
.map( .map(
(o: any) => (o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}` `${o.MaterialHumanReadableId} - ${o.MaterialDescription}`,
) )
.join(",\n ")} ${failMessage}` .join(",\n ")} ${failMessage}`,
); );
return { return {
message: `lot: ${lot.lot}, is missing: ${failing message: `lot: ${lot.lot}, is missing: ${failing
.map( .map(
(o: any) => (o: any) =>
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}` `${o.MaterialHumanReadableId} - ${o.MaterialDescription}`,
) )
.join(",\n ")} ${failMessage}`, .join(",\n ")} ${failMessage}`,
success: false, success: false,
}; };
} else { } else {
createLog( createLog(
"info", "info",
"mainMaterial", "mainMaterial",
"ocp", "ocp",
`lot: ${lot.lot}, ${JSON.stringify(results)}` `lot: ${lot.lot}, ${JSON.stringify(results)}`,
); );
return { message: successMessage, success: true }; return { message: successMessage, success: true };
} }
}; };
createLog("info", "mainMaterial", "ocp", `Maint material query ran.`); createLog("info", "mainMaterial", "ocp", `Maint material query ran.`);
const mainMaterial = res.find((n: any) => n.IsMainMaterial); const mainMaterial = res.find((n: any) => n.IsMainMaterial);
if (mainMaterial?.noMMShortage === "noMM") { if (mainMaterial?.Staged === 1) {
createLog( createLog(
"info", "info",
"mainMaterial", "mainMaterial",
"ocp", "ocp",
`Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}` `Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is staged for ${lot.lot}`,
); );
return { return {
message: `Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}`, message: `Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is staged for ${lot.lot}`,
success: false, success: true,
}; };
} }
if (mainMaterial?.noMMShortage === "noMM") {
createLog(
"info",
"mainMaterial",
"ocp",
`Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}`,
);
return {
message: `Main material: ${mainMaterial.MaterialHumanReadableId} - ${mainMaterial.MaterialDescription}: is not staged for ${lot.lot}`,
success: false,
};
}
// we need to filter the color stuff and then look for includes instead of a standard name. this way we can capture a everything and not a single type // we need to filter the color stuff and then look for includes instead of a standard name. this way we can capture a everything and not a single type
// for manual consume color if active to check colors // for manual consume color if active to check colors
const checkColorSetting = set.filter((n) => n.name === "checkColor"); const checkColorSetting = set.filter((n) => n.name === "checkColor");
// 2. Auto color // 2. Auto color
if (checkColorSetting[0].value === "1") { if (checkColorSetting[0].value === "1") {
// auto check // auto check
// 2. Auto color // 2. Auto color
const autoColor = checkCondition({ const autoColor = checkCondition({
results: res, results: res,
lot, lot,
filterFn: (n) => filterFn: (n) =>
n.isManual && n.isManual &&
!("noPKGAutoShortage" in n) && !("noPKGAutoShortage" in n) &&
!("noPKGManualShortage" in n), // pool = non-main, auto !("noPKGManualShortage" in n), // pool = non-main, auto
failCondition: (n) => n.autoConsumeCheck === "autoConsumeNOK", // column = autoConsumeCheck failCondition: (n) => n.autoConsumeCheck === "autoConsumeNOK", // column = autoConsumeCheck
failMessage: "for autoconsume", failMessage: "for autoconsume",
successMessage: "auto color is good", successMessage: "auto color is good",
}); });
if (!autoColor.success) return autoColor; if (!autoColor.success) return autoColor;
createLog( createLog(
"info", "info",
"mainMaterial", "mainMaterial",
"ocp", "ocp",
`Auto Color: ${JSON.stringify(autoColor)}` `Auto Color: ${JSON.stringify(autoColor)}`,
); );
// 3. Manual color // 3. Manual color
const manualColor = checkCondition({ const manualColor = checkCondition({
results: res, results: res,
lot, lot,
filterFn: (n) => filterFn: (n) =>
!n.IsMainMaterial && !n.IsMainMaterial &&
n.isManual && n.isManual &&
!("noPKGAutoShortage" in n) && !("noPKGAutoShortage" in n) &&
!("noPKGManualShortage" in n), // pool = non-main, manual !("noPKGManualShortage" in n), // pool = non-main, manual
failCondition: (n) => n.noManualShortage === "noOK", // column = noManualShortage failCondition: (n) => n.noManualShortage === "noOK", // column = noManualShortage
failMessage: "for manual material", failMessage: "for manual material",
successMessage: "manual color is good", successMessage: "manual color is good",
}); });
if (!manualColor.success) return manualColor; if (!manualColor.success) return manualColor;
createLog( createLog(
"info", "info",
"mainMaterial", "mainMaterial",
"ocp", "ocp",
`Manual Color: ${JSON.stringify(manualColor)}` `Manual Color: ${JSON.stringify(manualColor)}`,
); );
} else { } else {
createLog( createLog("info", "mainMaterial", "ocp", "Color check is not active.");
"info", }
"mainMaterial",
"ocp",
"Color check is not active."
);
}
// // if we want to check the packaging // // if we want to check the packaging
const checkPKGSetting = set.filter((n) => n.name === "checkPKG"); const checkPKGSetting = set.filter((n) => n.name === "checkPKG");
if (checkPKGSetting[0].value === "1") { if (checkPKGSetting[0].value === "1") {
const pkgAuto = checkCondition({ const pkgAuto = checkCondition({
results: res, results: res,
lot, lot,
filterFn: (n) => filterFn: (n) =>
!n.IsMainMaterial && !n.IsMainMaterial && !n.isManual && "noPKGAutoShortage" in n,
!n.isManual && failCondition: (n) => n.noPKGAutoShortage === "noAutoPkg",
"noPKGAutoShortage" in n, failMessage: "for pkg",
failCondition: (n) => n.noPKGAutoShortage === "noAutoPkg", successMessage: "auto PKG is good",
failMessage: "for pkg", });
successMessage: "auto PKG is good", if (!pkgAuto.success) return pkgAuto;
}); createLog(
if (!pkgAuto.success) return pkgAuto; "info",
createLog( "mainMaterial",
"info", "ocp",
"mainMaterial", `PKG Auto: ${JSON.stringify(pkgAuto)}`,
"ocp", );
`PKG Auto: ${JSON.stringify(pkgAuto)}` // 5. Packaging manual
); const pkgManual = checkCondition({
// 5. Packaging manual results: res,
const pkgManual = checkCondition({ lot,
results: res, filterFn: (n) =>
lot, !n.IsMainMaterial && n.isManual && "noPKGManualShortage" in n,
filterFn: (n) => failCondition: (n) => n.noPKGManualShortage === "noManPkg",
!n.IsMainMaterial && failMessage: "for pkg",
n.isManual && successMessage: "manual PKG is good",
"noPKGManualShortage" in n, });
failCondition: (n) => n.noPKGManualShortage === "noManPkg",
failMessage: "for pkg",
successMessage: "manual PKG is good",
});
if (!pkgManual.success) return pkgManual; if (!pkgManual.success) return pkgManual;
createLog( createLog(
"info", "info",
"mainMaterial", "mainMaterial",
"ocp", "ocp",
`PKG Manual: ${JSON.stringify(pkgManual)}` `PKG Manual: ${JSON.stringify(pkgManual)}`,
); );
} else { } else {
createLog( createLog("info", "mainMaterial", "ocp", "PKG check is not active.");
"info", }
"mainMaterial",
"ocp",
"PKG check is not active."
);
}
// manual pkg // manual pkg
if (checkPKGSetting[0].value === "1") { if (checkPKGSetting[0].value === "1") {
const packagingCheck = res.filter( const packagingCheck = res.filter(
(n: any) => (n: any) =>
!n.IsMainMaterial && !n.IsMainMaterial && n.isManual && "noPKGManualShortage" in n,
n.isManual && );
"noPKGManualShortage" in n if (
); packagingCheck.some((n: any) => n.noPKGManualShortage === "noManPkg")
if ( ) {
packagingCheck.some( createLog(
(n: any) => n.noPKGManualShortage === "noManPkg" "info",
) "mainMaterial",
) { "ocp",
createLog( `lot: ${lot.lot}, is missing: ${packagingCheck
"info", .map(
"mainMaterial", (o: any) =>
"ocp", `${o.MaterialHumanReadableId} - ${o.MaterialDescription}`,
`lot: ${lot.lot}, is missing: ${packagingCheck )
.map( .join(",\n ")} for pkg`,
(o: any) => );
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}` return (isStaged = {
) message: `lot: ${lot.lot}, is missing: ${packagingCheck
.join(",\n ")} for pkg` .map(
); (o: any) =>
return (isStaged = { `${o.MaterialHumanReadableId} - ${o.MaterialDescription}`,
message: `lot: ${lot.lot}, is missing: ${packagingCheck )
.map( .join(",\n ")} for pkg`,
(o: any) => success: false,
`${o.MaterialHumanReadableId} - ${o.MaterialDescription}` });
) }
.join(",\n ")} for pkg`, } else {
success: false, createLog("info", "mainMaterial", "ocp", "PKG check is not active.");
}); }
} } catch (err) {
} else { createLog(
createLog( "error",
"info", "mainMaterial",
"mainMaterial", "ocp",
"ocp", `Error from running the Main Material query: ${err}`,
"PKG check is not active." );
); }
} return isStaged;
} catch (err) {
createLog(
"error",
"mainMaterial",
"ocp",
`Error from running the Main Material query: ${err}`
);
}
return isStaged;
}; };

View File

@@ -1,285 +1,275 @@
import net from "net";
import { pausePrinter } from "../../utils/pausePrinter.js";
import { addHours, differenceInSeconds } from "date-fns"; import { addHours, differenceInSeconds } from "date-fns";
import { printerUpdate } from "./printerStatUpdate.js"; import net from "net";
import { timeZoneFix } from "../../../../globalUtils/timeZoneFix.js";
import { createLog } from "../../../logger/logger.js"; import { createLog } from "../../../logger/logger.js";
import { pausePrinter } from "../../utils/pausePrinter.js";
import { unPausePrinter } from "../../utils/unpausePrinter.js"; import { unPausePrinter } from "../../utils/unpausePrinter.js";
import { labelingProcess } from "../labeling/labelProcess.js"; import { labelingProcess } from "../labeling/labelProcess.js";
import { timeZoneFix } from "../../../../globalUtils/timeZoneFix.js";
import { autoLabelCreated } from "../labeling/labelRatio.js"; import { autoLabelCreated } from "../labeling/labelRatio.js";
import { printerUpdate } from "./printerStatUpdate.js";
let logLevel: string = process.env.LOG_LEVEL || "info"; let logLevel: string = process.env.LOG_LEVEL || "info";
let errorCheck = false; let errorCheck = false;
export const printerStatus = async (p: any) => { export const printerStatus = async (p: any) => {
/** /**
* Checks each printer to see what the current state is * Checks each printer to see what the current state is
*/ */
createLog("debug", "ocp", "ocp", `Printer cycling`); createLog("debug", "ocp", "ocp", `Printer cycling`);
const printer = new net.Socket(); const printer = new net.Socket();
return new Promise((resolve) => { return new Promise((resolve) => {
// connect to the printer, and check its status // connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => { printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer // write the message to the printer below gives us a feedback of the printer
printer.write("~HS"); printer.write("~HS");
}); });
// read the data from the printer // read the data from the printer
printer.on("data", async (data) => { printer.on("data", async (data) => {
const res = data.toString(); const res = data.toString();
// turn the data into an array to make it more easy to deal with // turn the data into an array to make it more easy to deal with
const tmp = res.split(","); const tmp = res.split(",");
//--------------- time stuff----------------------------------------------------------------- //--------------- time stuff-----------------------------------------------------------------
// get last time printed // get last time printed
const lastTime = new Date(p.lastTimePrinted).toISOString(); const lastTime = new Date(p.lastTimePrinted).toISOString();
// console.log(lastTime); // console.log(lastTime);
// current time? // current time?
/** /**
* *
* add the time zone to the settings db * add the time zone to the settings db
*/ */
// const currentTime = addHours( // const currentTime = addHours(
// new Date(Date.now()), // new Date(Date.now()),
// -6 // -6
// ).toISOString(); // ).toISOString();
const currentTime = timeZoneFix(); const currentTime = timeZoneFix();
let timeBetween = 0; let timeBetween = 0;
// if this is our first time printing pause the printer to start the timer, else we just update the time between timer // if this is our first time printing pause the printer to start the timer, else we just update the time between timer
if (lastTime === undefined) { if (lastTime === undefined) {
printer.end(); printer.end();
printerUpdate(p, 8); printerUpdate(p, 8);
pausePrinter(p); pausePrinter(p);
resolve({ success: true, message: "First Time printing" }); resolve({ success: true, message: "First Time printing" });
} else { } else {
timeBetween = differenceInSeconds(currentTime, lastTime); timeBetween = differenceInSeconds(currentTime, lastTime);
} }
// --- end time --- // --- end time ---
// --- printer logic --- // --- printer logic ---
createLog( createLog(
"debug", "debug",
"ocp", "ocp",
"ocp", "ocp",
`${p.name}: timeBetween: ${timeBetween}, delay ${parseInt( `${p.name}: timeBetween: ${timeBetween}, delay ${parseInt(
p.printDelay p.printDelay,
)}, ${currentTime}... ${lastTime}` )}, ${currentTime}... ${lastTime}`,
); );
if (tmp[2] === "0" && tmp[4] !== "000") { if (tmp[2] === "0" && tmp[4] !== "000") {
// unpaused and printing labels - reset timer // unpaused and printing labels - reset timer
createLog( createLog(
"debug", "info",
"ocp", "ocp",
"ocp", "ocp",
`Unpaused and printing labels, time remaing ${differenceInSeconds( `Unpaused and printing labels, time remaing ${differenceInSeconds(
parseInt(p.printDelay), parseInt(p.printDelay),
timeBetween timeBetween,
)}` )}`,
); );
// update last time printed in the array // update last time printed in the array
printerUpdate(p, 1); printerUpdate(p, 1);
} else if (tmp[2] === "1" && tmp[4] !== "000") { } else if (tmp[2] === "1" && tmp[4] !== "000") {
// was paused or label sent from somewhere else // was paused or label sent from somewhere else
createLog( createLog(
"info", "info",
"ocp", "ocp",
"ocp", "ocp",
`${ `${
p.name p.name
} paused to soon, unpausing, remaining time: ${differenceInSeconds( } paused to soon, unpausing, remaining time: ${differenceInSeconds(
parseInt(p.printDelay), parseInt(p.printDelay),
timeBetween timeBetween,
)}` )}`,
); );
// reset the timer for this printer as well other labels shouldnt be sent but if we send them ok // reset the timer for this printer as well other labels shouldnt be sent but if we send them ok
printerUpdate(p, 2); printerUpdate(p, 2);
unPausePrinter(p); unPausePrinter(p);
} else if (tmp[2] === "0" && timeBetween < parseInt(p.printDelay)) { } else if (tmp[2] === "0" && timeBetween < parseInt(p.printDelay)) {
// was unpaused to soon so repause it // was unpaused to soon so repause it
createLog( createLog(
"debug", "info",
"ocp", "ocp",
"ocp", "ocp",
`${p.name} Unpaused before the time allowed, time left ${ `${p.name} Unpaused before the time allowed, time left ${
differenceInSeconds(parseInt(p.printDelay), timeBetween) //seconds differenceInSeconds(parseInt(p.printDelay), timeBetween) //seconds
}` }`,
); );
printerUpdate(p, 3); printerUpdate(p, 3);
pausePrinter(p); pausePrinter(p);
} else if (tmp[2] === "0" && timeBetween > parseInt(p.printDelay)) { } else if (tmp[2] === "0" && timeBetween > parseInt(p.printDelay)) {
// its been long enough we can print a label // its been long enough we can print a label
createLog( createLog(
"debug", "info",
"ocp", "ocp",
"ocp", "ocp",
`${p.name} Allowed time passed and printing new label` `${p.name} Allowed time passed and printing new label`,
); );
// update last time printed in the array // update last time printed in the array
printerUpdate(p, 4); printerUpdate(p, 4);
// sending over for labeling. // sending over for labeling.
labelingProcess({ printer: p }); labelingProcess({ printer: p });
autoLabelCreated(); autoLabelCreated();
} else if (tmp[2] === "0") { } else if (tmp[2] === "0") {
// printer was unpaused for the first time or made it here // printer was unpaused for the first time or made it here
createLog( createLog("info", "ocp", "ocp", `${p.name} Frist time printing`);
"debug",
"ocp",
"ocp",
`${p.name} Frist time printing`
);
// add the time and printer // add the time and printer
printerUpdate(p, 4); printerUpdate(p, 4);
// sending over for labeling. // sending over for labeling.
labelingProcess({ printer: p }); labelingProcess({ printer: p });
autoLabelCreated(); autoLabelCreated();
} else if (tmp[2] === "1") { } else if (tmp[2] === "1") {
// printer is paused and waiting // printer is paused and waiting
createLog( createLog("debug", "ocp", "ocp", `${p.name} paused and waiting`);
"debug",
"ocp",
"ocp",
`${p.name} paused and waiting`
);
printerUpdate(p, 6); printerUpdate(p, 6);
} }
printer.end(); printer.end();
resolve({ success: true, message: "Print cycle completed." }); resolve({ success: true, message: "Print cycle completed." });
}); });
// as a safety destory it if its still there // as a safety destory it if its still there
printer.on("end", () => { printer.on("end", () => {
setTimeout(() => { setTimeout(() => {
if (!printer.destroyed) { if (!printer.destroyed) {
createLog( createLog(
"info", "info",
"printerState", "printerState",
"ocp", "ocp",
`${p.name}: was force closed, during normal cycle counting` `${p.name}: was force closed, during normal cycle counting`,
); );
printer.destroy(); printer.destroy();
} }
}, 1000); }, 1000);
}); });
printer.on("error", async (error: any) => { printer.on("error", async (error: any) => {
// just going to say theres an error with the printer // just going to say theres an error with the printer
//console.log(error.code); //console.log(error.code);
if (error.code.includes("ETIMEDOUT") && !errorCheck) { if (error.code.includes("ETIMEDOUT") && !errorCheck) {
createLog("error", "ocp", "ocp", `${p.name} is offline`); createLog("error", "ocp", "ocp", `${p.name} is offline`);
await printerUpdate(p, 9); await printerUpdate(p, 9);
errorCheck = true; errorCheck = true;
printer.end(); printer.end();
resolve({ resolve({
success: false, success: false,
message: "The printer is offline.", message: "The printer is offline.",
}); });
} }
if (!error.code.includes("ETIMEDOUT") && !errorCheck) { if (!error.code.includes("ETIMEDOUT") && !errorCheck) {
createLog( createLog(
"error", "error",
"ocp", "ocp",
"ocp", "ocp",
`${p.name} encountered an error: ${error}` `${p.name} encountered an error: ${error}`,
); );
await printerUpdate(p, 7); await printerUpdate(p, 7);
errorCheck = true; errorCheck = true;
// send log data // send log data
// fake line // fake line
printer.end(); printer.end();
resolve({ resolve({
success: false, success: false,
message: "There was an error with the printer.", message: "There was an error with the printer.",
}); });
} }
}); });
}); });
}; };
export const autoLabelingStats = async (p: any) => { export const autoLabelingStats = async (p: any) => {
/** /**
* Checks autolabeling printers just to see what they are doing. * Checks autolabeling printers just to see what they are doing.
*/ */
createLog("debug", "ocp", "ocp", `Printer cycling`); createLog("debug", "ocp", "ocp", `Printer cycling`);
const printer = new net.Socket(); const printer = new net.Socket();
return new Promise((resolve) => { return new Promise((resolve) => {
// connect to the printer, and check its status // connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => { printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer // write the message to the printer below gives us a feedback of the printer
printer.write("~HS"); printer.write("~HS");
}); });
// read the data from the printer // read the data from the printer
printer.on("data", async (data) => { printer.on("data", async (data) => {
const res = data.toString(); const res = data.toString();
// turn the data into an array to make it more easy to deal with // turn the data into an array to make it more easy to deal with
const tmp = res.split(","); const tmp = res.split(",");
if (tmp[4] !== "000") { if (tmp[4] !== "000") {
// unpaused and printing labels - reset timer // unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`); createLog("debug", "ocp", "ocp", `Printing Labels`);
// update last time printed in the array // update last time printed in the array
printerUpdate(p, 1); printerUpdate(p, 1);
} }
if (tmp[4] === "000") { if (tmp[4] === "000") {
// unpaused and printing labels - reset timer // unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`); createLog("debug", "ocp", "ocp", `Printing Labels`);
// update last time printed in the array // update last time printed in the array
printerUpdate(p, 5); printerUpdate(p, 5);
} }
}); });
printer.on("error", async (error) => { printer.on("error", async (error) => {
// just going to say theres an error with the printer // just going to say theres an error with the printer
console.log(error); console.log(error);
if (!errorCheck) { if (!errorCheck) {
createLog( createLog(
"error", "error",
"ocp", "ocp",
"ocp", "ocp",
`${p.name}, encountered an error: ${error}` `${p.name}, encountered an error: ${error}`,
); );
} }
await printerUpdate(p, 7); await printerUpdate(p, 7);
errorCheck = true; errorCheck = true;
// send log data // send log data
// fake line // fake line
printer.end(); printer.end();
resolve({ resolve({
success: false, success: false,
message: "There was an error with the printer.", message: "There was an error with the printer.",
}); });
}); });
}); });
}; };

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "lst", "name": "lst",
"version": "1.5.0", "version": "1.6.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "lst", "name": "lst",
"version": "1.5.0", "version": "1.6.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@dotenvx/dotenvx": "^1.51.0", "@dotenvx/dotenvx": "^1.51.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "lst", "name": "lst",
"version": "1.5.0", "version": "1.6.0",
"description": "Logistics support tool - the place where the support happens.", "description": "Logistics support tool - the place where the support happens.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {