Compare commits
28 Commits
5469a0dc5c
...
v0.0.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 82eaa23da7 | |||
| b18d1ced6d | |||
| 69c5cf87fd | |||
| 1fadf0ad25 | |||
| beae6eb648 | |||
| 82ab735982 | |||
| dbd56c1b50 | |||
| 037a473ab7 | |||
| 32998d417f | |||
| ddcb7e76a3 | |||
| 191cb2b698 | |||
| 2021141967 | |||
| 751c8f21ab | |||
| 85073c19d2 | |||
| 6b8d7b53d0 | |||
| e025d0f5cc | |||
| e67e9e6d72 | |||
| 2846b9cb0d | |||
| 5db2a7fe75 | |||
| 81dc575b4f | |||
| bf7d765989 | |||
| 4f24fe4660 | |||
| 68d13b03d3 | |||
| c3379919b9 | |||
| 326c2e125c | |||
| 880902c478 | |||
| 100c9ff9be | |||
| a8af021621 |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
|
||||
"$schema": "https://unpkg.com/@changesets/config/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
|
||||
5
.changeset/neat-years-unite.md
Normal file
5
.changeset/neat-years-unite.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"lst_v3": patch
|
||||
---
|
||||
|
||||
build stuff
|
||||
11
.changeset/pre.json
Normal file
11
.changeset/pre.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"tag": "alpha",
|
||||
"initialVersions": {
|
||||
"lst_v3": "1.0.1"
|
||||
},
|
||||
"changesets": [
|
||||
"neat-years-unite",
|
||||
"soft-onions-appear"
|
||||
]
|
||||
}
|
||||
5
.changeset/soft-onions-appear.md
Normal file
5
.changeset/soft-onions-appear.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"lst_v3": patch
|
||||
---
|
||||
|
||||
external url added for docker
|
||||
@@ -6,4 +6,7 @@ Dockerfile
|
||||
docker-compose.yml
|
||||
npm-debug.log
|
||||
builds
|
||||
testFiles
|
||||
testFiles
|
||||
nssm.exe
|
||||
postgresql-17.9-2-windows-x64.exe
|
||||
VSCodeUserSetup-x64-1.112.0.msi
|
||||
31
.gitea/workflows/docker-build.yml
Normal file
31
.gitea/workflows/docker-build.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Build and Push LST Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout (local)
|
||||
run: |
|
||||
git clone https://git.tuffraid.net/cowch/lst_v3.git .
|
||||
git checkout ${{ gitea.sha }}
|
||||
|
||||
- name: Login to registry
|
||||
run: echo "${{ secrets.PASSWORD }}" | docker login git.tuffraid.net -u "cowch" --password-stdin
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
docker build \
|
||||
-t git.tuffraid.net/cowch/lst_v3:latest \
|
||||
-t git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }} \
|
||||
.
|
||||
|
||||
- name: Push
|
||||
run: |
|
||||
docker push git.tuffraid.net/cowch/lst_v3:latest
|
||||
docker push git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }}
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,6 +1,15 @@
|
||||
# ---> Node
|
||||
testFiles
|
||||
builds
|
||||
.includes
|
||||
.buildNumber
|
||||
temp
|
||||
.scriptCreds
|
||||
node-v24.14.0-x64.msi
|
||||
postgresql-17.9-2-windows-x64.exe
|
||||
VSCodeUserSetup-x64-1.112.0.exe
|
||||
nssm.exe
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
1
.vscode/lst.code-snippets
vendored
1
.vscode/lst.code-snippets
vendored
@@ -10,6 +10,7 @@
|
||||
"\tmessage: \"${5:Failed to connect to the prod sql server.}\",",
|
||||
"\tdata: ${6:[]},",
|
||||
"\tnotify: ${7:false},",
|
||||
"\troom: ${8:''},",
|
||||
"});"
|
||||
],
|
||||
"description": "Insert a returnFunc template"
|
||||
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -3,6 +3,8 @@
|
||||
"workbench.colorTheme": "Default Dark+",
|
||||
"terminal.integrated.env.windows": {},
|
||||
"editor.formatOnSave": true,
|
||||
"typescript.preferences.importModuleSpecifier": "relative",
|
||||
"javascript.preferences.importModuleSpecifier": "relative",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
@@ -52,14 +54,21 @@
|
||||
"alpla",
|
||||
"alplamart",
|
||||
"alplaprod",
|
||||
"bookin",
|
||||
"Datamart",
|
||||
"dyco",
|
||||
"intiallally",
|
||||
"manadatory",
|
||||
"OCME",
|
||||
"onnotice",
|
||||
"opendock",
|
||||
"opendocks",
|
||||
"ppoo",
|
||||
"prodlabels"
|
||||
"preseed",
|
||||
"prodlabels",
|
||||
"prolink",
|
||||
"Skelly",
|
||||
"trycatch"
|
||||
],
|
||||
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",
|
||||
"gitea.instanceURL": "https://git.tuffraid.net",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# lst_v3
|
||||
|
||||
## 1.0.2-alpha.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- build stuff
|
||||
- external url added for docker
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -9,10 +9,13 @@ WORKDIR /app
|
||||
# Copy package files
|
||||
COPY . .
|
||||
|
||||
# Install production dependencies only
|
||||
# build backend
|
||||
RUN npm ci
|
||||
RUN npm run build:docker
|
||||
|
||||
RUN npm run build
|
||||
# build frontend
|
||||
RUN npm --prefix frontend ci
|
||||
RUN npm --prefix frontend run build
|
||||
|
||||
###########
|
||||
# Stage 2 #
|
||||
@@ -33,6 +36,9 @@ RUN npm ci --omit=dev
|
||||
|
||||
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY --from=build /app/frontend/dist ./frontend/dist
|
||||
|
||||
# TODO add in drizzle migrates
|
||||
|
||||
ENV RUNNING_IN_DOCKER=true
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { toNodeHandler } from "better-auth/node";
|
||||
import express from "express";
|
||||
import morgan from "morgan";
|
||||
@@ -20,15 +22,34 @@ const createApp = async () => {
|
||||
baseUrl = "/lst";
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// well leave this active so we can monitor it to validate
|
||||
app.use(morgan("tiny"));
|
||||
app.set("trust proxy", true);
|
||||
app.all(`${baseUrl}api/auth/*splat`, toNodeHandler(auth));
|
||||
app.use(express.json());
|
||||
app.use(lstCors());
|
||||
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
||||
app.use(express.json());
|
||||
setupRoutes(baseUrl, app);
|
||||
|
||||
log.info("Express app created");
|
||||
app.use(
|
||||
baseUrl + "/app",
|
||||
express.static(join(__dirname, "../frontend/dist")),
|
||||
);
|
||||
|
||||
app.get(baseUrl + "/app/*splat", (_, res) => {
|
||||
res.sendFile(join(__dirname, "../frontend/dist/index.html"));
|
||||
});
|
||||
|
||||
app.all("*foo", (_, res) => {
|
||||
res.status(400).json({
|
||||
message:
|
||||
"You have encountered a route that dose not exist, please check the url and try again",
|
||||
});
|
||||
});
|
||||
|
||||
log.info("Lst app created");
|
||||
return { app, baseUrl };
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ const signin = z.union([
|
||||
const r = Router();
|
||||
|
||||
r.post("/", async (req, res) => {
|
||||
let login: unknown;
|
||||
let login: unknown | any;
|
||||
|
||||
try {
|
||||
const validated = signin.parse(req.body);
|
||||
if ("email" in validated) {
|
||||
@@ -92,6 +93,27 @@ r.post("/", async (req, res) => {
|
||||
password: validated.password,
|
||||
},
|
||||
headers: fromNodeHeaders(req.headers),
|
||||
asResponse: true,
|
||||
});
|
||||
|
||||
if (login.status === 401) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "routes",
|
||||
subModule: "auth",
|
||||
message: `Incorrect username or password please try again`,
|
||||
data: [],
|
||||
status: 401, //connect.success ? 200 : 400,
|
||||
});
|
||||
}
|
||||
|
||||
login.headers.forEach((value: string, key: string) => {
|
||||
if (key.toLowerCase() === "set-cookie") {
|
||||
res.append("set-cookie", value);
|
||||
} else {
|
||||
res.setHeader(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { APIError } from "better-auth";
|
||||
import { count, sql } from "drizzle-orm";
|
||||
import { count, eq, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
@@ -58,7 +58,10 @@ r.post("/", async (req, res) => {
|
||||
// if we have no users yet lets make this new one the admin
|
||||
if (userCount === 0) {
|
||||
// make this user an admin
|
||||
await db.update(user).set({ role: "admin", updatedAt: sql`NOW()` });
|
||||
await db
|
||||
.update(user)
|
||||
.set({ role: "admin", updatedAt: sql`NOW()` })
|
||||
.where(eq(user.id, newUser.user.id));
|
||||
}
|
||||
|
||||
apiReturn(res, {
|
||||
@@ -78,7 +81,7 @@ r.post("/", async (req, res) => {
|
||||
// details: flattened,
|
||||
// });
|
||||
|
||||
apiReturn(res, {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "routes",
|
||||
@@ -90,7 +93,7 @@ r.post("/", async (req, res) => {
|
||||
}
|
||||
|
||||
if (err instanceof APIError) {
|
||||
apiReturn(res, {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "routes",
|
||||
@@ -101,7 +104,7 @@ r.post("/", async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
apiReturn(res, {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "routes",
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { OpenAPIV3_1 } from "openapi-types";
|
||||
import { cronerActiveJobs } from "../scaler/cronerActiveJobs.spec.js";
|
||||
import { cronerStatusChange } from "../scaler/cronerStatusChange.spec.js";
|
||||
import { prodLoginSpec } from "../scaler/login.spec.js";
|
||||
import { openDockApt } from "../scaler/opendockGetRelease.spec.js";
|
||||
import { prodRestartSpec } from "../scaler/prodSqlRestart.spec.js";
|
||||
import { prodStartSpec } from "../scaler/prodSqlStart.spec.js";
|
||||
import { prodStopSpec } from "../scaler/prodSqlStop.spec.js";
|
||||
@@ -33,6 +34,7 @@ export const openApiBase: OpenAPIV3_1.Document = {
|
||||
description: "Development server",
|
||||
},
|
||||
],
|
||||
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
@@ -87,6 +89,10 @@ export const openApiBase: OpenAPIV3_1.Document = {
|
||||
name: "Utils",
|
||||
description: "All routes related to the utilities on the server",
|
||||
},
|
||||
{
|
||||
name: "Open Dock",
|
||||
description: "All routes related to the opendock on the server",
|
||||
},
|
||||
// { name: "TMS", description: "TMS integration" },
|
||||
],
|
||||
paths: {}, // Will be populated
|
||||
@@ -121,6 +127,7 @@ export const setupApiDocsRoutes = (baseUrl: string, app: Express) => {
|
||||
//...mergedDatamart,
|
||||
...cronerActiveJobs,
|
||||
...cronerStatusChange,
|
||||
...openDockApt,
|
||||
|
||||
// Add more specs here as you build features
|
||||
},
|
||||
@@ -134,7 +141,9 @@ export const setupApiDocsRoutes = (baseUrl: string, app: Express) => {
|
||||
apiReference({
|
||||
url: `${baseUrl}/api/docs.json`,
|
||||
theme: "purple",
|
||||
|
||||
darkMode: true,
|
||||
persistAuth: true,
|
||||
authentication: {
|
||||
securitySchemes: {
|
||||
httpBasic: {
|
||||
|
||||
29
backend/db/schema/notifications.schema.ts
Normal file
29
backend/db/schema/notifications.schema.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
boolean,
|
||||
jsonb,
|
||||
pgTable,
|
||||
text,
|
||||
uniqueIndex,
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import type z from "zod";
|
||||
|
||||
export const notifications = pgTable(
|
||||
"notifications",
|
||||
{
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description").notNull(),
|
||||
active: boolean("active").default(false),
|
||||
interval: text("interval").default("5"),
|
||||
options: jsonb("options").default([]),
|
||||
},
|
||||
(table) => [uniqueIndex("notify_name").on(table.name)],
|
||||
);
|
||||
|
||||
export const notificationSchema = createSelectSchema(notifications);
|
||||
export const newNotificationSchema = createInsertSchema(notifications);
|
||||
|
||||
export type Notification = z.infer<typeof notificationSchema>;
|
||||
export type NewNotification = z.infer<typeof newNotificationSchema>;
|
||||
30
backend/db/schema/notifications.sub.schema.ts
Normal file
30
backend/db/schema/notifications.sub.schema.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { pgTable, text, unique, uuid } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import type z from "zod";
|
||||
import { user } from "./auth.schema.js";
|
||||
import { notifications } from "./notifications.schema.js";
|
||||
|
||||
export const notificationSub = pgTable(
|
||||
"notification_sub",
|
||||
{
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
notificationId: uuid("notification_id")
|
||||
.notNull()
|
||||
.references(() => notifications.id, { onDelete: "cascade" }),
|
||||
emails: text("emails").array().default([]),
|
||||
},
|
||||
(table) => ({
|
||||
userNotificationUnique: unique(
|
||||
"notification_sub_user_notification_unique",
|
||||
).on(table.userId, table.notificationId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const notificationSubSchema = createSelectSchema(notificationSub);
|
||||
export const newNotificationSubSchema = createInsertSchema(notificationSub);
|
||||
|
||||
export type NotificationSub = z.infer<typeof notificationSubSchema>;
|
||||
export type NewNotificationSub = z.infer<typeof newNotificationSubSchema>;
|
||||
6
backend/db/schema/printerLogs.schema.ts
Normal file
6
backend/db/schema/printerLogs.schema.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
|
||||
export const opendockApt = pgTable("printer_log", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
name: text("name").notNull(),
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
@@ -13,9 +14,9 @@ import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
export const settingType = pgEnum("setting_type", [
|
||||
"feature",
|
||||
"system",
|
||||
"standard",
|
||||
"feature", // when changed deals with triggering the croner related to this
|
||||
"system", // when changed fires a system restart but this should be rare and all these settings should be in the env
|
||||
"standard", // will be effected by the next process, either croner or manual trigger
|
||||
]);
|
||||
|
||||
export const settings = pgTable(
|
||||
@@ -27,8 +28,9 @@ export const settings = pgTable(
|
||||
description: text("description"),
|
||||
moduleName: text("moduleName"), // what part of lst dose it belong to this is used to split the settings out later
|
||||
active: boolean("active").default(true),
|
||||
roles: jsonb("roles").notNull().default(["systemAdmin"]), // role or roles to see this goes along with the moduleName, need to have a x role in module to see this setting.
|
||||
roles: jsonb("roles").$type<string[]>().notNull().default(["systemAdmin"]), // role or roles to see this goes along with the moduleName, need to have a x role in module to see this setting.
|
||||
settingType: settingType(),
|
||||
seedVersion: integer("seed_version").default(1), // this is intended for if we want to update the settings.
|
||||
add_User: text("add_User").default("LST_System").notNull(),
|
||||
add_Date: timestamp("add_Date").defaultNow(),
|
||||
upd_user: text("upd_User").default("LST_System").notNull(),
|
||||
|
||||
10
backend/db/schema/stats.schema.ts
Normal file
10
backend/db/schema/stats.schema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
|
||||
export const serverStats = pgTable("stats", {
|
||||
id: text("id").primaryKey().default("serverStats"),
|
||||
build: integer("build").notNull().default(1),
|
||||
lastUpdate: timestamp("last_update").defaultNow(),
|
||||
});
|
||||
|
||||
export type ServerStats = InferSelectModel<typeof serverStats>;
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Writable } from "node:stream";
|
||||
|
||||
import pino, { type Logger } from "pino";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
//import build from "pino-abstract-transport";
|
||||
|
||||
@@ -26,20 +28,27 @@ const dbStream = new Writable({
|
||||
const levelName = pinoLogLevels[obj.level] || "unknown";
|
||||
|
||||
const res = await tryCatch(
|
||||
db.insert(logs).values({
|
||||
level: levelName,
|
||||
module: obj?.module?.toLowerCase(),
|
||||
subModule: obj?.subModule?.toLowerCase(),
|
||||
hostname: obj?.hostname?.toLowerCase(),
|
||||
message: obj.msg,
|
||||
stack: obj?.stack,
|
||||
}),
|
||||
db
|
||||
.insert(logs)
|
||||
.values({
|
||||
level: levelName,
|
||||
module: obj?.module?.toLowerCase(),
|
||||
subModule: obj?.subModule?.toLowerCase(),
|
||||
hostname: obj?.hostname?.toLowerCase(),
|
||||
message: obj.msg,
|
||||
stack: obj?.stack,
|
||||
})
|
||||
.returning(),
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
console.error(res.error);
|
||||
}
|
||||
|
||||
if (obj.room) {
|
||||
emitToRoom(obj.room, res.data ? res.data[0] : obj);
|
||||
}
|
||||
emitToRoom("logs", res.data ? res.data[0] : obj);
|
||||
callback();
|
||||
} catch (err) {
|
||||
console.error("DB log insert error:", err);
|
||||
@@ -48,31 +57,34 @@ const dbStream = new Writable({
|
||||
},
|
||||
});
|
||||
|
||||
// ✅ Multistream setup
|
||||
const streams = [
|
||||
{
|
||||
stream: pino.transport({
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
singleLine: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
level: "info",
|
||||
stream: dbStream,
|
||||
},
|
||||
];
|
||||
|
||||
const rootLogger: Logger = pino(
|
||||
{
|
||||
level: logLevel,
|
||||
redact: { paths: ["email", "password"], remove: true },
|
||||
},
|
||||
pino.multistream(streams),
|
||||
pino.multistream([
|
||||
{
|
||||
level: logLevel,
|
||||
stream: pino.transport({
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
singleLine: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
level: logLevel,
|
||||
stream: dbStream,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* example data to put in as a reference
|
||||
* rooms logs | labels | etc
|
||||
*/
|
||||
export const createLogger = (bindings: Record<string, unknown>): Logger => {
|
||||
return rootLogger.child(bindings);
|
||||
};
|
||||
|
||||
@@ -2,27 +2,55 @@ import { fromNodeHeaders } from "better-auth/node";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: {
|
||||
id: string;
|
||||
email?: string;
|
||||
roles?: string | null | undefined; //Record<string, string[]>;
|
||||
username?: string | null | undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// function toWebHeaders(nodeHeaders: Request["headers"]): Headers {
|
||||
// const h = new Headers();
|
||||
// for (const [key, value] of Object.entries(nodeHeaders)) {
|
||||
// if (Array.isArray(value)) {
|
||||
// value.forEach((v) => h.append(key, v));
|
||||
// } else if (value !== undefined) {
|
||||
// h.set(key, value);
|
||||
// }
|
||||
// }
|
||||
// return h;
|
||||
// }
|
||||
|
||||
export const requireAuth = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
// TODO: add the real auth stuff in later.
|
||||
try {
|
||||
const session = await auth.api.getSession({
|
||||
headers: fromNodeHeaders(req.headers),
|
||||
//query: { disableCookieCache: true },
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
//return res.status(401).json({ error: "Unauthorized" });
|
||||
console.info("not auth of course");
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
// attach session to request for later use
|
||||
(req as any).session = session;
|
||||
console.info(
|
||||
"Just passing the middleware and reminder that we need to add the real stuff in.",
|
||||
);
|
||||
//console.log(session);
|
||||
|
||||
req.user = {
|
||||
id: session.user.id,
|
||||
email: session.user.email,
|
||||
roles: session.user.role,
|
||||
username: session.user.username,
|
||||
};
|
||||
|
||||
next();
|
||||
} catch {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
|
||||
52
backend/middleware/auth.requiredPerms.middleware.ts
Normal file
52
backend/middleware/auth.requiredPerms.middleware.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
|
||||
type PermissionMap = Record<string, string[]>;
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
authz?: {
|
||||
success: boolean;
|
||||
permissions: PermissionMap;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRoles(roles: unknown): string {
|
||||
if (Array.isArray(roles)) return roles.join(",");
|
||||
if (typeof roles === "string") return roles;
|
||||
return "";
|
||||
}
|
||||
|
||||
export function requirePermission(permissions: PermissionMap) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const role = normalizeRoles(req.user?.roles) as any;
|
||||
|
||||
const result = await auth.api.userHasPermission({
|
||||
body: {
|
||||
role,
|
||||
permissions,
|
||||
},
|
||||
});
|
||||
|
||||
req.authz = {
|
||||
success: !!result?.success,
|
||||
permissions,
|
||||
};
|
||||
|
||||
if (!result?.success) {
|
||||
return res.status(403).json({
|
||||
ok: false,
|
||||
message: "You do not have permission to perform this action.",
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
37
backend/middleware/featureActive.middleware.ts
Normal file
37
backend/middleware/featureActive.middleware.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { settings } from "../db/schema/settings.schema.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param moduleName name of the module we are checking if is enabled or not.
|
||||
*/
|
||||
export const featureCheck = (moduleName: string) => {
|
||||
// get the features from the settings
|
||||
|
||||
return async (_req: Request, res: Response, next: NextFunction) => {
|
||||
const { data: sData, error: sError } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(
|
||||
and(
|
||||
eq(settings.settingType, "feature"),
|
||||
eq(settings.name, moduleName),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (sError) {
|
||||
return res.status(403).json({ error: "Internal Error" });
|
||||
}
|
||||
|
||||
if (!sData?.length || !sData[0]?.active) {
|
||||
return res.status(403).json({ error: "Feature disabled" });
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
153
backend/notification/notification.controller.ts
Normal file
153
backend/notification/notification.controller.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { notifications } from "../db/schema/notifications.schema.js";
|
||||
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { minutesToCron } from "../utils/croner.minConvert.js";
|
||||
import { createCronJob, stopCronJob } from "../utils/croner.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const log = createLogger({ module: "notifications", subModule: "start" });
|
||||
|
||||
export const startNotifications = async () => {
|
||||
// get active notification
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db.select().from(notifications).where(eq(notifications.active, true)),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
log.error(
|
||||
{ error: error },
|
||||
"There was an error when getting notifications.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
if (data.length === 0) {
|
||||
log.info(
|
||||
{},
|
||||
"There are know currently active notifications to start up.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// get the subs and see if we have any subs currently so we can fire up the notification
|
||||
const { data: sub, error: subError } = await tryCatch(
|
||||
db.select().from(notificationSub),
|
||||
);
|
||||
|
||||
if (subError) {
|
||||
log.error(
|
||||
{ error: error },
|
||||
"There was an error when getting subscriptions.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.length === 0) {
|
||||
log.info({}, "There are know currently active subscriptions.");
|
||||
return;
|
||||
}
|
||||
|
||||
const emailString = [
|
||||
...new Set(
|
||||
sub.flatMap((e) =>
|
||||
e.emails?.map((email) => email.trim().toLowerCase()),
|
||||
),
|
||||
),
|
||||
].join(";");
|
||||
|
||||
for (const n of data) {
|
||||
createCronJob(
|
||||
n.name,
|
||||
minutesToCron(parseInt(n.interval ?? "15", 10)),
|
||||
async () => {
|
||||
try {
|
||||
const { default: runFun } = await import(
|
||||
`./notification.${n.name.trim()}.js`
|
||||
);
|
||||
await runFun(n, emailString);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
{ error: error },
|
||||
"There was an error starting the notification",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const modifiedNotification = async (id: string) => {
|
||||
// when a notifications subscribed to, updated, deleted we want to get the info and rerun the startup on the single notification.
|
||||
const { data, error } = await tryCatch(
|
||||
db.select().from(notifications).where(eq(notifications.id, id)),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
log.error(
|
||||
{ error: error },
|
||||
"There was an error when getting notifications.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
if (!data[0]?.active) {
|
||||
stopCronJob(data[0]?.name ?? "");
|
||||
return;
|
||||
}
|
||||
|
||||
// get the subs for the specific id as we only want to up the modified one
|
||||
const { data: sub, error: subError } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(notificationSub)
|
||||
.where(eq(notificationSub.notificationId, id)),
|
||||
);
|
||||
|
||||
if (subError) {
|
||||
log.error(
|
||||
{ error: error },
|
||||
"There was an error when getting subscriptions.",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub.length === 0) {
|
||||
log.info({}, "There are know currently active subscriptions.");
|
||||
stopCronJob(data[0]?.name ?? "");
|
||||
return;
|
||||
}
|
||||
|
||||
const emailString = [
|
||||
...new Set(
|
||||
sub.flatMap((e) =>
|
||||
e.emails?.map((email) => email.trim().toLowerCase()),
|
||||
),
|
||||
),
|
||||
].join(";");
|
||||
|
||||
createCronJob(
|
||||
data[0].name,
|
||||
minutesToCron(parseInt(data[0].interval ?? "15", 10)),
|
||||
async () => {
|
||||
try {
|
||||
const { default: runFun } = await import(
|
||||
`./notification.${data[0]?.name.trim()}.js`
|
||||
);
|
||||
await runFun(data[0], emailString);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
{ error: error },
|
||||
"There was an error starting the notification",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
10
backend/notification/notification.reprintLabels.ts
Normal file
10
backend/notification/notification.reprintLabels.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
const reprint = (data: any, emails: string) => {
|
||||
// TODO: do the actual logic for the notification.
|
||||
console.log(data);
|
||||
console.log(emails);
|
||||
|
||||
// TODO send the error to systemAdmin users so they do not always need to be on the notifications.
|
||||
// these errors are defined per notification.
|
||||
};
|
||||
|
||||
export default reprint;
|
||||
55
backend/notification/notification.route.ts
Normal file
55
backend/notification/notification.route.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { type Response, Router } from "express";
|
||||
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { notifications } from "../db/schema/notifications.schema.js";
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res: Response) => {
|
||||
const hasPermissions = await auth.api.userHasPermission({
|
||||
body: {
|
||||
//userId: req?.user?.id,
|
||||
role: req.user?.roles as any,
|
||||
permissions: {
|
||||
notifications: ["readAll"], // This must match the structure in your access control
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { data: nName, error: nError } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(notifications)
|
||||
.where(
|
||||
!hasPermissions.success ? eq(notifications.active, true) : undefined,
|
||||
)
|
||||
.orderBy(notifications.name),
|
||||
);
|
||||
|
||||
if (nError) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "get",
|
||||
message: `There was an error getting the notifications `,
|
||||
data: [nError],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "notification",
|
||||
subModule: "get",
|
||||
message: `All current notifications`,
|
||||
data: nName ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
export default r;
|
||||
20
backend/notification/notification.routes.ts
Normal file
20
backend/notification/notification.routes.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { Express } from "express";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import getNotifications from "./notification.route.js";
|
||||
import updateNote from "./notification.update.route.js";
|
||||
import deleteSub from "./notificationSub.delete.route.js";
|
||||
import subs from "./notificationSub.get.route.js";
|
||||
import newSub from "./notificationSub.post.route.js";
|
||||
import updateSub from "./notificationSub.update.route.js";
|
||||
|
||||
export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
|
||||
//stats will be like this as we dont need to change this
|
||||
app.use(`${baseUrl}/api/notification`, requireAuth, getNotifications);
|
||||
app.use(`${baseUrl}/api/notification`, requireAuth, updateNote);
|
||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, subs);
|
||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, newSub);
|
||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, updateSub);
|
||||
app.use(`${baseUrl}/api/notification/sub`, requireAuth, deleteSub);
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
81
backend/notification/notification.update.route.ts
Normal file
81
backend/notification/notification.update.route.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { type Response, Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { notifications } from "../db/schema/notifications.schema.js";
|
||||
import { requirePermission } from "../middleware/auth.requiredPerms.middleware.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { modifiedNotification } from "./notification.controller.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
const updateNote = z.object({
|
||||
description: z.string().optional(),
|
||||
active: z.boolean().optional(),
|
||||
interval: z.string().optional(),
|
||||
options: z.array(z.record(z.string(), z.unknown())).optional(),
|
||||
});
|
||||
|
||||
r.patch(
|
||||
"/:id",
|
||||
requirePermission({ notifications: ["update"] }),
|
||||
async (req, res: Response) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const validated = updateNote.parse(req.body);
|
||||
|
||||
const { data: nName, error: nError } = await tryCatch(
|
||||
db
|
||||
.update(notifications)
|
||||
.set(validated)
|
||||
.where(eq(notifications.id, id as string))
|
||||
.returning(),
|
||||
);
|
||||
|
||||
await modifiedNotification(id as string);
|
||||
|
||||
if (nError) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "update",
|
||||
message: `There was an error getting the notifications `,
|
||||
data: [nError],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "notification",
|
||||
subModule: "update",
|
||||
message: `Notification was updated`,
|
||||
data: nName ?? [],
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof z.ZodError) {
|
||||
const flattened = z.flattenError(err);
|
||||
// return res.status(400).json({
|
||||
// error: "Validation failed",
|
||||
// details: flattened,
|
||||
// });
|
||||
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "routes",
|
||||
subModule: "notification",
|
||||
message: "Validation failed",
|
||||
data: [flattened.fieldErrors],
|
||||
status: 400, //connect.success ? 200 : 400,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
export default r;
|
||||
76
backend/notification/notificationSub.delete.route.ts
Normal file
76
backend/notification/notificationSub.delete.route.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { type Response, Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { modifiedNotification } from "./notification.controller.js";
|
||||
|
||||
const newSubscribe = z.object({
|
||||
emails: z.email().array().describe("An array of emails"),
|
||||
userId: z.string().describe("User id."),
|
||||
notificationId: z.string().describe("Notification id"),
|
||||
});
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.delete("/", async (req, res: Response) => {
|
||||
try {
|
||||
const validated = newSubscribe.parse(req.body);
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.delete(notificationSub)
|
||||
.where(
|
||||
and(
|
||||
eq(notificationSub.userId, validated.userId),
|
||||
eq(notificationSub.notificationId, validated.notificationId),
|
||||
),
|
||||
)
|
||||
.returning(),
|
||||
);
|
||||
|
||||
await modifiedNotification(validated.notificationId);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "post",
|
||||
message: `There was an error deleting the subscription `,
|
||||
data: [error],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "notification",
|
||||
subModule: "post",
|
||||
message: `Subscription deleted`,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof z.ZodError) {
|
||||
const flattened = z.flattenError(err);
|
||||
// return res.status(400).json({
|
||||
// error: "Validation failed",
|
||||
// details: flattened,
|
||||
// });
|
||||
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "routes",
|
||||
subModule: "notification",
|
||||
message: "Validation failed",
|
||||
data: [flattened.fieldErrors],
|
||||
status: 400, //connect.success ? 200 : 400,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
export default r;
|
||||
57
backend/notification/notificationSub.get.route.ts
Normal file
57
backend/notification/notificationSub.get.route.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { type Response, Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (req, res: Response) => {
|
||||
const { userId } = req.query;
|
||||
|
||||
const hasPermissions = await auth.api.userHasPermission({
|
||||
body: {
|
||||
//userId: req?.user?.id,
|
||||
role: req.user?.roles as any,
|
||||
permissions: {
|
||||
notifications: ["readAll"], // This must match the structure in your access control
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(notificationSub)
|
||||
.where(
|
||||
userId || !hasPermissions.success
|
||||
? eq(notificationSub.userId, `${req?.user?.id ?? ""}`)
|
||||
: undefined,
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "post",
|
||||
message: `There was an error getting subscriptions `,
|
||||
data: [error],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "notification",
|
||||
subModule: "post",
|
||||
message: `Subscriptions`,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
export default r;
|
||||
75
backend/notification/notificationSub.post.route.ts
Normal file
75
backend/notification/notificationSub.post.route.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { type Response, Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { modifiedNotification } from "./notification.controller.js";
|
||||
|
||||
const newSubscribe = z.object({
|
||||
emails: z
|
||||
.email()
|
||||
.array()
|
||||
|
||||
.describe("An array of emails"),
|
||||
userId: z.string().describe("User id."),
|
||||
notificationId: z
|
||||
.string()
|
||||
|
||||
.describe("Notification id"),
|
||||
});
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/", async (req, res: Response) => {
|
||||
try {
|
||||
const validated = newSubscribe.parse(req.body);
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db.insert(notificationSub).values(validated).returning(),
|
||||
);
|
||||
|
||||
await modifiedNotification(validated.notificationId);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "post",
|
||||
message: `There was an error getting the notifications `,
|
||||
data: [error],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "notification",
|
||||
subModule: "post",
|
||||
message: `Subscribed to notification`,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof z.ZodError) {
|
||||
const flattened = z.flattenError(err);
|
||||
// return res.status(400).json({
|
||||
// error: "Validation failed",
|
||||
// details: flattened,
|
||||
// });
|
||||
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "routes",
|
||||
subModule: "notification",
|
||||
message: "Validation failed",
|
||||
data: [flattened.fieldErrors],
|
||||
status: 400, //connect.success ? 200 : 400,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
export default r;
|
||||
84
backend/notification/notificationSub.update.route.ts
Normal file
84
backend/notification/notificationSub.update.route.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { type Response, Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { modifiedNotification } from "./notification.controller.js";
|
||||
|
||||
const newSubscribe = z.object({
|
||||
emails: z.email().array().describe("An array of emails"),
|
||||
userId: z.string().describe("User id."),
|
||||
notificationId: z.string().describe("Notification id"),
|
||||
});
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.patch("/", async (req, res: Response) => {
|
||||
try {
|
||||
const validated = newSubscribe.parse(req.body);
|
||||
|
||||
const emails = validated.emails
|
||||
.map((e) => e.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
|
||||
const uniqueEmails = [...new Set(emails)];
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.update(notificationSub)
|
||||
.set({ emails: uniqueEmails })
|
||||
.where(
|
||||
and(
|
||||
eq(notificationSub.userId, validated.userId),
|
||||
eq(notificationSub.notificationId, validated.notificationId),
|
||||
),
|
||||
)
|
||||
.returning(),
|
||||
);
|
||||
|
||||
await modifiedNotification(validated.notificationId);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "notification",
|
||||
subModule: "update",
|
||||
message: `There was an error updating the notifications `,
|
||||
data: [error],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "notification",
|
||||
subModule: "update",
|
||||
message: `Subscription updated`,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof z.ZodError) {
|
||||
const flattened = z.flattenError(err);
|
||||
// return res.status(400).json({
|
||||
// error: "Validation failed",
|
||||
// details: flattened,
|
||||
// });
|
||||
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "routes",
|
||||
subModule: "notification",
|
||||
message: "Validation failed",
|
||||
data: [flattened.fieldErrors],
|
||||
status: 400, //connect.success ? 200 : 400,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
export default r;
|
||||
50
backend/notification/notifications.master.ts
Normal file
50
backend/notification/notifications.master.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import {
|
||||
type NewNotification,
|
||||
notifications,
|
||||
} from "../db/schema/notifications.schema.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const note: NewNotification[] = [
|
||||
{
|
||||
name: "reprintLabels",
|
||||
description:
|
||||
"Monitors the labels that are printed and returns a there data, if one falls withing the time frame.",
|
||||
active: false,
|
||||
interval: "10",
|
||||
options: [{ prodID: 1 }],
|
||||
},
|
||||
];
|
||||
|
||||
export const createNotifications = async () => {
|
||||
const log = createLogger({ module: "notifications", subModule: "create" });
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.insert(notifications)
|
||||
.values(note)
|
||||
.onConflictDoUpdate({
|
||||
target: notifications.name,
|
||||
set: {
|
||||
description: sql`excluded.description`,
|
||||
},
|
||||
// where: sql`
|
||||
// settings.seed_version IS NULL
|
||||
// OR settings.seed_version < excluded.seed_version
|
||||
// `,
|
||||
})
|
||||
.returning(),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
log.error(
|
||||
{ error: error },
|
||||
"There was an error when adding or updating the notifications.",
|
||||
);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
log.info({}, "All notifications were added/updated");
|
||||
}
|
||||
};
|
||||
36
backend/ocp/ocp.printer.listener.ts
Normal file
36
backend/ocp/ocp.printer.listener.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* the route that listens for the printers post.
|
||||
*
|
||||
* and http-post alert should be setup on each printer pointing to at min you will want to make the alert for
|
||||
* pause printer, you can have all on here as it will also monitor and do things on all messages
|
||||
*
|
||||
* http://{serverIP}:2222/lst/api/ocp/printer/listener/{printerName}
|
||||
*
|
||||
* the messages will be sent over to the db for logging as well as specific ones will do something
|
||||
*
|
||||
* pause will validate if can print
|
||||
* close head will repause the printer so it wont print a label
|
||||
* power up will just repause the printer so it wont print a label
|
||||
*/
|
||||
|
||||
import { Router } from "express";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/printer/listener/:printer", async (req, res) => {
|
||||
const { printer: printerName } = req.params;
|
||||
console.log(req.body);
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "ocp",
|
||||
subModule: "printing",
|
||||
message: `${printerName} just passed over a message`,
|
||||
data: req.body ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
19
backend/ocp/ocp.printer.manage.ts
Normal file
19
backend/ocp/ocp.printer.manage.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* this will do a prod sync, update or add alerts to the printer, validate the next pm intervale as well as head replacement.
|
||||
*
|
||||
* if a printer is upcoming on a pm or head replacement send to the plant to address.
|
||||
*
|
||||
* a trigger on the printer table will have the ability to run this as well
|
||||
*
|
||||
* heat beats on all assigned printers
|
||||
*
|
||||
* printer status will live here this will be how we manage all the levels of status like 3 paused, 1 printing, 8 error, 10 power up, etc...
|
||||
*/
|
||||
|
||||
export const printerManager = async () => {};
|
||||
|
||||
export const printerHeartBeat = async () => {
|
||||
// heat heats will be defaulted to 60 seconds no reason to allow anything else
|
||||
};
|
||||
|
||||
//export const printerStatus = async (statusNr: number, printerId: number) => {};
|
||||
22
backend/ocp/ocp.routes.ts
Normal file
22
backend/ocp/ocp.routes.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { type Express, Router } from "express";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
import listener from "./ocp.printer.listener.js";
|
||||
|
||||
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
|
||||
//setup all the routes
|
||||
const router = Router();
|
||||
|
||||
// is the feature even on?
|
||||
router.use(featureCheck("ocp"));
|
||||
|
||||
// non auth routes up here
|
||||
router.use(listener);
|
||||
|
||||
// auth routes below here
|
||||
router.use(requireAuth);
|
||||
|
||||
//router.use("");
|
||||
|
||||
app.use(`${baseUrl}/api/ocp`, router);
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
// NOTE: we assume "accessToken" was already obtained earlier via a call to '/auth/login'.
|
||||
|
||||
// get the
|
||||
const accessToken =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3MTkyNTQyZS01NzQ5LTRlZTgtYjdjZS0zNTQ4ZjA0NGQwOWIiLCJqdGkiOiI1NzE5ZmQ2OS02NTVkLTQ1MjctYTJjOC1hZWNhMjU0MTQ2MDEiLCJpZCI6IjcxOTI1NDJlLTU3NDktNGVlOC1iN2NlLTM1NDhmMDQ0ZDA5YiIsImVtYWlsIjoiYmxha2UubWF0dGhlc0BhbHBsYS5jb20iLCJvcmdJZCI6IjI2YTE4NjlmLTYwNDktNDM3Mi04ZWMzLTVkZDZlNDIzZjJmNiIsImNvbXBhbnlJZCI6bnVsbCwicm9sZSI6InJvbGVfb3duZXIiLCJpc0VtYWlsVmVyaWZpZWQiOnRydWUsImludmFsaWRMb2dpbkF0dGVtcHRzIjpudWxsLCJpYXQiOjE3NzA4MTE5MTEsImV4cCI6MTc3MTA3MTExMX0.jLHOSIF5RHUGjwq8WvycYxD9HK8_677O6sgRUZeYdUQ";
|
||||
|
||||
const baseSubspaceUrl = "wss://subspace.opendock.com";
|
||||
const url = `${baseSubspaceUrl}?token=${accessToken}`;
|
||||
const socket = io(url, { transports: ["websocket"] }); // Enforce 'websocket' transport only.
|
||||
|
||||
// socket.on("heartbeat", (data) => {
|
||||
// console.log(data);
|
||||
// });
|
||||
|
||||
socket.on("connection", () => {
|
||||
console.log("Connected");
|
||||
});
|
||||
|
||||
socket.on("create-Appointment", (data) => {
|
||||
console.log("appt create:", data);
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
import axios from "axios";
|
||||
import { addHours } from "date-fns";
|
||||
import { formatInTimeZone } from "date-fns-tz";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { opendockApt } from "../db/schema/opendock.schema.js";
|
||||
import { settings } from "../db/schema/settings.schema.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||
import {
|
||||
@@ -14,6 +15,7 @@ import { createCronJob } from "../utils/croner.utils.js";
|
||||
import { delay } from "../utils/delay.utils.js";
|
||||
import { returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { getToken, odToken } from "./opendock.utils.js";
|
||||
|
||||
let lastCheck = formatInTimeZone(
|
||||
new Date().toISOString(),
|
||||
@@ -36,21 +38,12 @@ type Releases = {
|
||||
CustomerReleaseNumber: string;
|
||||
};
|
||||
|
||||
type ODToken = {
|
||||
odToken: string | null;
|
||||
tokenDate: Date | null;
|
||||
};
|
||||
|
||||
let odToken: ODToken = {
|
||||
odToken: null,
|
||||
tokenDate: new Date(),
|
||||
};
|
||||
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
||||
const log = createLogger({ module: "opendock", subModule: "releaseMonitor" });
|
||||
|
||||
const postRelease = async (release: Releases) => {
|
||||
if (!odToken.odToken) {
|
||||
log.info("Getting Auth Token");
|
||||
log.info({}, "Getting Auth Token");
|
||||
await getToken();
|
||||
}
|
||||
|
||||
@@ -58,7 +51,7 @@ const postRelease = async (release: Releases) => {
|
||||
new Date(odToken.tokenDate || Date.now()).getTime() <
|
||||
Date.now() - TWENTY_FOUR_HOURS
|
||||
) {
|
||||
log.info("Refreshing Auth Token");
|
||||
log.info({}, "Refreshing Auth Token");
|
||||
await getToken();
|
||||
}
|
||||
/**
|
||||
@@ -87,11 +80,11 @@ const postRelease = async (release: Releases) => {
|
||||
: release.DeliveryState === 3 // this will consider finished and if a correction needs made to the bol we need to cancel and reactivate the order
|
||||
? "Completed"
|
||||
: release.DeliveryState === 4 && "Completed",
|
||||
userId: "2629b4f6-0003-472d-8b26-66a69ce5ac50", // this should be the carrierid
|
||||
loadTypeId: "0aa7988e-b17b-4f10-acdd-3d029b44a773", // well get this and make it a default one
|
||||
dockId: "00ba4386-ce5a-4dd1-9356-6e6d10a24609", // this the warehouse we want it in to start out
|
||||
userId: process.env.DEFAULT_CARRIER, // this should be the carrierid
|
||||
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
|
||||
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
|
||||
refNumbers: [release.ReleaseNumber],
|
||||
refNumber: release.ReleaseNumber,
|
||||
//refNumber: release.ReleaseNumber,
|
||||
start: release.DeliveryDate,
|
||||
end: addHours(release.DeliveryDate, 1),
|
||||
notes: "",
|
||||
@@ -120,7 +113,7 @@ const postRelease = async (release: Releases) => {
|
||||
name: "intPallet Count",
|
||||
type: "int",
|
||||
label: "Pallet Count",
|
||||
value: parseInt(release.LoadingUnits, 10),
|
||||
value: parseInt(release.LoadingUnits, 10), // do we really want to update this if its partial load as it should have been the full amount?
|
||||
description: "How many pallets",
|
||||
placeholder: "22",
|
||||
dropDownValues: [],
|
||||
@@ -207,13 +200,20 @@ const postRelease = async (release: Releases) => {
|
||||
})
|
||||
.returning();
|
||||
|
||||
log.info(`${release.ReleaseNumber} was updated`);
|
||||
log.info({}, `${release.ReleaseNumber} was updated`);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
log.error(
|
||||
{ error: e },
|
||||
`Error updating the release: ${release.ReleaseNumber}`,
|
||||
);
|
||||
}
|
||||
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||
} catch (e: any) {
|
||||
//console.info(newDockApt);
|
||||
log.error(e.response.data);
|
||||
log.error(
|
||||
{ error: e.response.data },
|
||||
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -225,7 +225,7 @@ const postRelease = async (release: Releases) => {
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
Authorization: `Bearer ${odToken}`,
|
||||
Authorization: `Bearer ${odToken.odToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -234,7 +234,7 @@ const postRelease = async (release: Releases) => {
|
||||
// this will be utilized when we are listening for the changes to the apts. that way we can update the state to arrived. we will run our own checks on this guy during the incoming messages.
|
||||
|
||||
if (response.status === 400) {
|
||||
log.error(response.data.data.message);
|
||||
log.error({}, response.data.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -255,12 +255,16 @@ const postRelease = async (release: Releases) => {
|
||||
})
|
||||
.returning();
|
||||
|
||||
log.info(`${release.ReleaseNumber} was created`);
|
||||
log.info({}, `${release.ReleaseNumber} was created`);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
log.error({ error: e }, "Error creating new release");
|
||||
}
|
||||
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||
} catch (e: any) {
|
||||
log.error(e.response.data);
|
||||
log.error(
|
||||
{ error: e?.response?.data },
|
||||
"Error posting new release to opendock",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -273,7 +277,10 @@ export const monitorReleaseChanges = async () => {
|
||||
// TODO: validate if the setting for opendocks is active and start / stop the system based on this
|
||||
// if it changes we set to false and the next loop will stop.
|
||||
|
||||
const openDockMonitor = true;
|
||||
const openDockMonitor = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.name, "opendock_sync"));
|
||||
// console.info("Starting release monitor", lastCheck);
|
||||
|
||||
const sqlQuery = sqlQuerySelector(`releaseChecks`) as SqlQuery;
|
||||
@@ -290,8 +297,8 @@ export const monitorReleaseChanges = async () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (openDockMonitor) {
|
||||
createCronJob("open-dock-monitor", "*/15 * * * * *", async () => {
|
||||
if (openDockMonitor[0]?.active) {
|
||||
createCronJob("opendock_sync", "*/15 * * * * *", async () => {
|
||||
try {
|
||||
const result = await prodQuery(
|
||||
sqlQuery.query.replace("[dateCheck]", `'${lastCheck}'`),
|
||||
@@ -312,8 +319,11 @@ export const monitorReleaseChanges = async () => {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
log.error({ error: e }, "Monitor error");
|
||||
console.error(
|
||||
{ error: e },
|
||||
"Error occurred while running the monitor job",
|
||||
);
|
||||
log.error({ error: e }, "Error occurred while running the monitor job");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -349,28 +359,6 @@ export const monitorReleaseChanges = async () => {
|
||||
// }
|
||||
};
|
||||
|
||||
const getToken = async () => {
|
||||
try {
|
||||
const { status, data } = await axios.post(
|
||||
`${process.env.OPENDOCK_URL}/auth/login`,
|
||||
{
|
||||
email: "blake.matthes@alpla.com",
|
||||
password: process.env.OPENDOCK_PASSWORD,
|
||||
},
|
||||
);
|
||||
|
||||
if (status === 400) {
|
||||
log.error(data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
odToken = { odToken: data.access_token, tokenDate: new Date() };
|
||||
log.info("Token added");
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
// export const monitorReleaseChanges = async () => {
|
||||
// console.log("Starting release monitor", lastCheck);
|
||||
// setInterval(async () => {
|
||||
19
backend/opendock/opendock.routes.ts
Normal file
19
backend/opendock/opendock.routes.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { type Express, Router } from "express";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
import getApt from "./opendockGetRelease.route.js";
|
||||
|
||||
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
||||
//setup all the routes
|
||||
// Apply auth to entire router
|
||||
const router = Router();
|
||||
|
||||
// is the feature even on?
|
||||
router.use(featureCheck("opendock_sync"));
|
||||
|
||||
// we need to make sure we are authenticated to see the releases
|
||||
router.use(requireAuth);
|
||||
|
||||
router.use(getApt);
|
||||
app.use(`${baseUrl}/api/opendock`, router);
|
||||
};
|
||||
35
backend/opendock/opendock.utils.ts
Normal file
35
backend/opendock/opendock.utils.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import axios from "axios";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
|
||||
type ODToken = {
|
||||
odToken: string | null;
|
||||
tokenDate: Date | null;
|
||||
};
|
||||
|
||||
export let odToken: ODToken = {
|
||||
odToken: null,
|
||||
tokenDate: new Date(),
|
||||
};
|
||||
|
||||
export const getToken = async () => {
|
||||
const log = createLogger({ module: "opendock", subModule: "releaseMonitor" });
|
||||
try {
|
||||
const { status, data } = await axios.post(
|
||||
`${process.env.OPENDOCK_URL}/auth/login`,
|
||||
{
|
||||
email: "blake.matthes@alpla.com",
|
||||
password: process.env.OPENDOCK_PASSWORD,
|
||||
},
|
||||
);
|
||||
|
||||
if (status === 400) {
|
||||
log.error(data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
odToken = { odToken: data.access_token, tokenDate: new Date() };
|
||||
log.info({}, "Token added");
|
||||
} catch (e) {
|
||||
log.error({ error: e }, "Error getting/refreshing token");
|
||||
}
|
||||
};
|
||||
40
backend/opendock/opendockGetRelease.route.ts
Normal file
40
backend/opendock/opendockGetRelease.route.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { desc, gte, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { opendockApt } from "../db/schema/opendock.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (_, res) => {
|
||||
//const limit
|
||||
|
||||
const daysCreated = 30;
|
||||
|
||||
const { data } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(opendockApt)
|
||||
.where(
|
||||
gte(
|
||||
opendockApt.createdAt,
|
||||
sql.raw(`NOW() - INTERVAL '${daysCreated} days'`),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(opendockApt.createdAt))
|
||||
.limit(500),
|
||||
);
|
||||
|
||||
apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "opendock",
|
||||
subModule: "apt",
|
||||
message: `The first ${data?.length} Apt(s) that were created in the last ${daysCreated} `,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
69
backend/opendock/opendockSocketMonitor.utils.ts
Normal file
69
backend/opendock/opendockSocketMonitor.utils.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { io, type Socket } from "socket.io-client";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { systemSettings } from "../server.js";
|
||||
import { getToken, odToken } from "./opendock.utils.js";
|
||||
|
||||
const log = createLogger({ module: "opendock", subModule: "releaseMonitor" });
|
||||
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
||||
let socket: Socket | null = null;
|
||||
export const opendockSocketMonitor = async () => {
|
||||
// checking if we actaully want to run this
|
||||
if (!systemSettings.filter((n) => n.name === "opendock_sync")[0]?.active) {
|
||||
log.info({}, "Opendock is not active");
|
||||
}
|
||||
|
||||
if (!odToken.odToken) {
|
||||
log.info({}, "Getting Auth Token");
|
||||
await getToken();
|
||||
}
|
||||
|
||||
if (
|
||||
new Date(odToken.tokenDate || Date.now()).getTime() <
|
||||
Date.now() - TWENTY_FOUR_HOURS
|
||||
) {
|
||||
log.info({}, "Refreshing Auth Token");
|
||||
await getToken();
|
||||
}
|
||||
const baseSubspaceUrl = "wss://subspace.staging.opendock.com";
|
||||
const url = `${baseSubspaceUrl}?token=${odToken.odToken}`;
|
||||
socket = io(url, { transports: ["websocket"] }); // Enforce 'websocket' transport only.
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log("Connected");
|
||||
});
|
||||
|
||||
// socket.on("heartbeat", (data) => {
|
||||
// console.log(data);
|
||||
// });
|
||||
|
||||
socket.on("create-Appointment", (data) => {
|
||||
console.log("appt create:", data);
|
||||
});
|
||||
|
||||
socket.on("update-Appointment", (data) => {
|
||||
console.log("appt update:", data);
|
||||
});
|
||||
|
||||
socket.on("error", (data) => {
|
||||
console.log("Error:", data);
|
||||
});
|
||||
|
||||
// socket.onAny((event, ...args) => {
|
||||
// console.log("Received event:", event, args);
|
||||
// });
|
||||
};
|
||||
|
||||
export const killOpendockSocket = () => {
|
||||
if (!socket) {
|
||||
console.log("No active socket to kill");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🛑 Killing socket connection...");
|
||||
|
||||
socket.removeAllListeners(); // optional but clean
|
||||
socket.disconnect();
|
||||
socket = null;
|
||||
|
||||
console.log("✅ Socket killed");
|
||||
};
|
||||
@@ -58,7 +58,7 @@ export const prodQuery = async (queryToRun: string, name: string) => {
|
||||
return {
|
||||
success: true,
|
||||
message: `Query results for: ${name}`,
|
||||
data: result.recordset,
|
||||
data: result.recordset ?? [],
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const err = error as SqlError;
|
||||
|
||||
188
backend/rfid/daytonConfig copy.json
Normal file
188
backend/rfid/daytonConfig copy.json
Normal file
@@ -0,0 +1,188 @@
|
||||
{
|
||||
"GPIO-LED": {
|
||||
"GPODefaults": {
|
||||
"1": "HIGH",
|
||||
"2": "HIGH",
|
||||
"3": "HIGH",
|
||||
"4": "HIGH"
|
||||
},
|
||||
"LEDDefaults": {
|
||||
"3": "GREEN"
|
||||
},
|
||||
"TAG_READ": [
|
||||
{
|
||||
"pin": 1,
|
||||
"state": "HIGH",
|
||||
"type": "GPO"
|
||||
}
|
||||
]
|
||||
},
|
||||
"READER-GATEWAY": {
|
||||
"batching": [
|
||||
{
|
||||
"maxPayloadSizePerReport": 256000,
|
||||
"reportingInterval": 2000
|
||||
}
|
||||
],
|
||||
"endpointConfig": {
|
||||
"data": {
|
||||
"event": {
|
||||
"connections": [
|
||||
{
|
||||
"additionalOptions": {
|
||||
"batching": {
|
||||
"maxPayloadSizePerReport": 256000,
|
||||
"reportingInterval": 2000
|
||||
},
|
||||
"retention": {
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
}
|
||||
},
|
||||
"description": "",
|
||||
"name": "LST",
|
||||
"options": {
|
||||
"URL": "https://usday1prod.alpla.net/lst/old/api/rfid/taginfo/line3.4",
|
||||
"security": {
|
||||
"CACertificateFileLocation": "",
|
||||
"authenticationOptions": {
|
||||
"privateKeyFileLocation": "/readerconfig/ssl/server.key",
|
||||
"publicKeyFileLocation": "/readerconfig/ssl/server.crt"
|
||||
},
|
||||
"authenticationType": "NONE",
|
||||
"verifyHost": false,
|
||||
"verifyPeer": false
|
||||
}
|
||||
},
|
||||
"type": "httpPost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"managementEventConfig": {
|
||||
"errors": {
|
||||
"antenna": false,
|
||||
"cpu": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"database": true,
|
||||
"flash": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"ntp": true,
|
||||
"radio": true,
|
||||
"radio_control": true,
|
||||
"ram": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"reader_gateway": true,
|
||||
"userApp": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 120
|
||||
}
|
||||
},
|
||||
"gpiEvents": true,
|
||||
"gpoEvents": true,
|
||||
"heartbeat": {
|
||||
"fields": {
|
||||
"radio_control": [
|
||||
"ANTENNAS",
|
||||
"RADIO_ACTIVITY",
|
||||
"RADIO_CONNECTION",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_ERRORS",
|
||||
"NUM_WARNINGS",
|
||||
"NUM_TAG_READS",
|
||||
"NUM_TAG_READS_PER_ANTENNA",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"NUM_RADIO_PACKETS_RXED"
|
||||
],
|
||||
"reader_gateway": [
|
||||
"NUM_DATA_MESSAGES_RXED",
|
||||
"NUM_MANAGEMENT_EVENTS_TXED",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"NUM_DATA_MESSAGES_RETAINED",
|
||||
"NUM_DATA_MESSAGES_DROPPED",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_ERRORS",
|
||||
"NUM_WARNINGS",
|
||||
"INTERFACE_CONNECTION_STATUS",
|
||||
"NOLOCKQ_DEPTH"
|
||||
],
|
||||
"system": [
|
||||
"CPU",
|
||||
"FLASH",
|
||||
"NTP",
|
||||
"RAM",
|
||||
"SYSTEMTIME",
|
||||
"TEMPERATURE",
|
||||
"UPTIME",
|
||||
"GPO",
|
||||
"GPI",
|
||||
"POWER_NEGOTIATION",
|
||||
"POWER_SOURCE",
|
||||
"MAC_ADDRESS",
|
||||
"HOSTNAME"
|
||||
],
|
||||
"userDefined": null,
|
||||
"userapps": [
|
||||
"STATUS",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_DATA_MESSAGES_RXED",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"INCOMING_DATA_BUFFER_PERCENTAGE_REMAINING",
|
||||
"OUTGOING_DATA_BUFFER_PERCENTAGE_REMAINING"
|
||||
]
|
||||
},
|
||||
"interval": 60
|
||||
},
|
||||
"userappEvents": true,
|
||||
"warnings": {
|
||||
"cpu": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"database": true,
|
||||
"flash": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"ntp": true,
|
||||
"radio_api": true,
|
||||
"radio_control": true,
|
||||
"ram": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"reader_gateway": true,
|
||||
"temperature": {
|
||||
"ambient": 75,
|
||||
"pa": 105
|
||||
},
|
||||
"userApp": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 60
|
||||
}
|
||||
}
|
||||
},
|
||||
"retention": [
|
||||
{
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
"xml": "<?xml version='1.0'?>\n<Motorola xmlns:Falcon='http://www.motorola.com/RFID/Readers/Config/Falcon' xmlns='http://www.motorola.com/RFID/Readers/Config/Falcon'>\n<Config>\n<AppVersion major='3' minor='28' build='1' maintenance='0'/>\n<CommConfig EnabledStacks='IPV4' DisableRAPktProcessing='1' EnableDHCPv6='1' IPv6StaticIPAddr='fe80::1' IPv6SubnetMask='64' IPv6StaticGateway='::' IPv6DNSIP='fe80::20' DHCP='1' IPAddr='10.44.14.39' Mask='255.255.255.0' Gateway='10.44.14.252' DNS='10.44.9.250' DomainSearch='example.com' HttpRunning='2' TelnetActive='2' FtpActive='2' usbMode='0' WatchdogEnabled='1' AvahiEnabled='1' NetBIOSEnabled='0' RDMPAgentEnabled='1' SerialConTimeout='0' SNTP='0.0.0.0' SNTPHostName='pool.ntp.org' sntpHostDisplayMode='0' llrpClientMode='0' llrpSecureMode='0' llrpSecureModeValidatePeer='0' llrpPort='5084' llrpHostIP='192.168.127.2' allowllrpConnOverride='0' shouldReconnect='1'/>\n<Bluetooth discoverable='0' pairable='0' PincodeEnabled='0' passkey='165CB22DA5BE7BBEFB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03' startIP='192.168.0.2' endIP='192.168.0.3'/>\n<WirelessConfig essid='' autoconnect='0'/>\n<RegionConfig RFCountry='United States/Canada' RFRegulatory='US FCC 15' RFScanMode='0' LBTEnable='0' ChannelData='FFFFFFFFFFFFFFFF'/>\n<SnmpConfig snmpVersion='1' heartbeat='1'/>\n<SyslogConfig RemoteIp='0.0.0.0' RemotePort='514' LogMinSeverity='7' ApplyFilter='0' MinimumSeverity='7' ProcessFilter='rmserver.elf,llrpserver.elf,snmpextagent.elf,RDMPAgent'/>\n<UserList>\n<User name='admin' PSWD='$6$weLpDwlv$utr0AwgPIae2O4Gln4cQ2IJJblXye412Xqni0V.ahIFKUOCEDGjzZ4ttthhrw7rmmQYsCXKwA9znyqPkAT.IL/'/>\n<User name='rfidadm' PSWD='15491'/>\n</UserList>\n<IPReader name='FX96007AF832 FX9600 RFID Reader' desc='FX96007AF832 Advanced Reader' flags='0' MonoStatic='0' CheckAntenna='1' gpiDebounceTime='0' gpioMapping='0' idleModeTimeOut='0' diagMode='0' extDiagMode='0' contact='Zebra Technologies Corporation' PowerNegotiation='0' PowerNegotiationProtocol='0' allowGuestLogin='1' configureHostName='0'>\n<ReadPoint name='Read Point 1' flags='0' CableLossPerHundredFt='10' CableLength='10'/>\n<ReadPoint name='Read Point 2' flags='0' CableLossPerHundredFt='10' CableLength='10'/>\n<ReadPoint name='Read Point 3' flags='1' CableLossPerHundredFt='10' CableLength='10'/>\n<ReadPoint name='Read Point 4' flags='1' CableLossPerHundredFt='10' CableLength='10'/>\n</IPReader>\n<SerialPortConf Mode='0' Baudrate='115200' Databits='8' Parity='none' Stopbits='1' Flowcontrol='hardware' TagMetaData='0' InventoryControl='0' IsAutostart='0'/>\n<FXConnectConfig FXConnectMode='0' TagMetaData='0' InventoryControl='None' HeartBeatPeriod='0' IsAutostart='0' PreFilterMode='0' PreFilters='None'/>\n<ProfinetConfig virtualDAP='1'/>\n<NodeJSPortConf Portnumber='8001'/>\n</Config>\n<MOTOROLA_LLRP_CONFIG><LLRP_READER_CONFIG />\n</MOTOROLA_LLRP_CONFIG>\n<IOT_CONNECT_CONFIG><OPERATING_MODE />\n</IOT_CONNECT_CONFIG>\n<RadioProfileData><RadioRegisterData Address='0' Data='00'/>\n</RadioProfileData>\n<CustomProfileData ForceEAPMode='0' FIPS_MODE_ENABLED='0' MaxNumberOfTagsBuffered='512'/>\n</Motorola >\n"
|
||||
}
|
||||
206
backend/rfid/daytonConfig.json
Normal file
206
backend/rfid/daytonConfig.json
Normal file
@@ -0,0 +1,206 @@
|
||||
{
|
||||
"GPIO-LED": {
|
||||
"GPODefaults": {
|
||||
"1": "HIGH",
|
||||
"2": "HIGH",
|
||||
"3": "HIGH",
|
||||
"4": "HIGH"
|
||||
},
|
||||
"LEDDefaults": {
|
||||
"3": "GREEN"
|
||||
},
|
||||
"TAG_READ": [
|
||||
{
|
||||
"pin": 1,
|
||||
"state": "HIGH",
|
||||
"type": "GPO"
|
||||
}
|
||||
]
|
||||
},
|
||||
"READER-GATEWAY": {
|
||||
"batching": [
|
||||
{
|
||||
"maxPayloadSizePerReport": 256000,
|
||||
"reportingInterval": 2000
|
||||
}
|
||||
],
|
||||
"endpointConfig": {
|
||||
"data": {
|
||||
"event": {
|
||||
"connections": [
|
||||
{
|
||||
"additionalOptions": {
|
||||
"retention": {
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
}
|
||||
},
|
||||
"description": "",
|
||||
"name": "lst",
|
||||
"options": {
|
||||
"URL": "http://usday1vms006:3100/api/rfid/taginfo/wrapper1",
|
||||
"security": {
|
||||
"CACertificateFileLocation": "",
|
||||
"authenticationOptions": {},
|
||||
"authenticationType": "NONE",
|
||||
"verifyHost": false,
|
||||
"verifyPeer": false
|
||||
}
|
||||
},
|
||||
"type": "httpPost"
|
||||
},
|
||||
{
|
||||
"additionalOptions": {
|
||||
"retention": {
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
}
|
||||
},
|
||||
"description": "",
|
||||
"name": "mgt",
|
||||
"options": {
|
||||
"URL": "http://usday1vms006:3100/api/rfid/mgtevents/wrapper1",
|
||||
"security": {
|
||||
"CACertificateFileLocation": "",
|
||||
"authenticationOptions": {},
|
||||
"authenticationType": "NONE",
|
||||
"verifyHost": false,
|
||||
"verifyPeer": false
|
||||
}
|
||||
},
|
||||
"type": "httpPost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"tagDataInterface1": "lst",
|
||||
"managementEventsInterface": "mgt"
|
||||
},
|
||||
"managementEventConfig": {
|
||||
"errors": {
|
||||
"antenna": false,
|
||||
"cpu": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"database": true,
|
||||
"flash": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"ntp": true,
|
||||
"radio": true,
|
||||
"radio_control": true,
|
||||
"ram": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"reader_gateway": true,
|
||||
"userApp": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 120
|
||||
}
|
||||
},
|
||||
"gpiEvents": true,
|
||||
"gpoEvents": true,
|
||||
"heartbeat": {
|
||||
"fields": {
|
||||
"radio_control": [
|
||||
"ANTENNAS",
|
||||
"RADIO_ACTIVITY",
|
||||
"RADIO_CONNECTION",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_ERRORS",
|
||||
"NUM_WARNINGS",
|
||||
"NUM_TAG_READS",
|
||||
"NUM_TAG_READS_PER_ANTENNA",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"NUM_RADIO_PACKETS_RXED"
|
||||
],
|
||||
"reader_gateway": [
|
||||
"NUM_DATA_MESSAGES_RXED",
|
||||
"NUM_MANAGEMENT_EVENTS_TXED",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"NUM_DATA_MESSAGES_RETAINED",
|
||||
"NUM_DATA_MESSAGES_DROPPED",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_ERRORS",
|
||||
"NUM_WARNINGS",
|
||||
"INTERFACE_CONNECTION_STATUS",
|
||||
"NOLOCKQ_DEPTH"
|
||||
],
|
||||
"system": [
|
||||
"CPU",
|
||||
"FLASH",
|
||||
"NTP",
|
||||
"RAM",
|
||||
"SYSTEMTIME",
|
||||
"TEMPERATURE",
|
||||
"UPTIME",
|
||||
"GPO",
|
||||
"GPI",
|
||||
"POWER_NEGOTIATION",
|
||||
"POWER_SOURCE",
|
||||
"MAC_ADDRESS",
|
||||
"HOSTNAME"
|
||||
],
|
||||
"userDefined": null,
|
||||
"userapps": [
|
||||
"STATUS",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_DATA_MESSAGES_RXED",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"INCOMING_DATA_BUFFER_PERCENTAGE_REMAINING",
|
||||
"OUTGOING_DATA_BUFFER_PERCENTAGE_REMAINING"
|
||||
]
|
||||
},
|
||||
"interval": 60
|
||||
},
|
||||
"userappEvents": true,
|
||||
"warnings": {
|
||||
"cpu": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"database": true,
|
||||
"flash": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"ntp": true,
|
||||
"radio_api": true,
|
||||
"radio_control": true,
|
||||
"ram": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"reader_gateway": true,
|
||||
"temperature": {
|
||||
"ambient": 75,
|
||||
"pa": 105
|
||||
},
|
||||
"userApp": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 60
|
||||
}
|
||||
}
|
||||
},
|
||||
"retention": [
|
||||
{
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
import type { Express } from "express";
|
||||
|
||||
import { setupAuthRoutes } from "./auth/auth.routes.js";
|
||||
// import the routes and route setups
|
||||
import { setupApiDocsRoutes } from "./configs/scaler.config.js";
|
||||
import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
|
||||
import { setupNotificationRoutes } from "./notification/notification.routes.js";
|
||||
import { setupOCPRoutes } from "./ocp/ocp.routes.js";
|
||||
import { setupOpendockRoutes } from "./opendock/opendock.routes.js";
|
||||
import { setupProdSqlRoutes } from "./prodSql/prodSql.routes.js";
|
||||
import { setupSystemRoutes } from "./system/system.routes.js";
|
||||
import { setupUtilsRoutes } from "./utils/utils.routes.js";
|
||||
@@ -15,13 +19,7 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
|
||||
setupDatamartRoutes(baseUrl, app);
|
||||
setupAuthRoutes(baseUrl, app);
|
||||
setupUtilsRoutes(baseUrl, app);
|
||||
|
||||
// routes that get activated if the module is set to activated.
|
||||
|
||||
app.all("*foo", (_, res) => {
|
||||
res.status(400).json({
|
||||
message:
|
||||
"You have encountered a route that dose not exist, please check the url and try again",
|
||||
});
|
||||
});
|
||||
setupOpendockRoutes(baseUrl, app);
|
||||
setupNotificationRoutes(baseUrl, app);
|
||||
setupOCPRoutes(baseUrl, app);
|
||||
};
|
||||
|
||||
36
backend/scaler/opendockGetRelease.spec.ts
Normal file
36
backend/scaler/opendockGetRelease.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { OpenAPIV3_1 } from "openapi-types";
|
||||
|
||||
export const openDockApt: OpenAPIV3_1.PathsObject = {
|
||||
"/api/opendock": {
|
||||
get: {
|
||||
summary: "Open Dock apt",
|
||||
description: "Returns the last 30 days of apt(s).",
|
||||
tags: ["Open Dock"],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Jobs returned",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: {
|
||||
type: "boolean",
|
||||
format: "boolean",
|
||||
example: true,
|
||||
},
|
||||
message: {
|
||||
type: "string",
|
||||
format: "string",
|
||||
example:
|
||||
"The first 5 Apt(s) that were created in the last 30 days",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,39 +1,60 @@
|
||||
import { createServer } from "node:http";
|
||||
import os from "node:os";
|
||||
import createApp from "./app.js";
|
||||
import { db } from "./db/db.controller.js";
|
||||
import { dbCleanup } from "./db/dbCleanup.controller.js";
|
||||
import { type Setting, settings } from "./db/schema/settings.schema.js";
|
||||
import { createLogger } from "./logger/logger.controller.js";
|
||||
import { monitorReleaseChanges } from "./opendock/releaseMonitor.utils.js";
|
||||
import { startNotifications } from "./notification/notification.controller.js";
|
||||
import { createNotifications } from "./notification/notifications.master.js";
|
||||
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
|
||||
import { opendockSocketMonitor } from "./opendock/opendockSocketMonitor.utils.js";
|
||||
import { connectProdSql } from "./prodSql/prodSqlConnection.controller.js";
|
||||
import { setupSocketIORoutes } from "./socket.io/serverSetup.js";
|
||||
import { baseSettingValidationCheck } from "./system/settingsBase.controller.js";
|
||||
import { createCronJob } from "./utils/croner.utils.js";
|
||||
|
||||
const port = Number(process.env.PORT) || 3000;
|
||||
|
||||
export let systemSettings: Setting[] = [];
|
||||
const start = async () => {
|
||||
const log = createLogger({ module: "system", subModule: "main start" });
|
||||
|
||||
// triggering long lived processes
|
||||
connectProdSql();
|
||||
|
||||
// start long live processes
|
||||
setTimeout(() => {
|
||||
monitorReleaseChanges(); // this is od monitoring the db for all new releases
|
||||
createCronJob("JobAuditLogCleanUp", "* 0 5 * * * *", () =>
|
||||
dbCleanup("jobs", 30),
|
||||
);
|
||||
createCronJob("logsCleanup", "* 15 5 * * * *", () => dbCleanup("logs", 30));
|
||||
createCronJob("opendockAptCleanup", "* 30 5 * * * *", () =>
|
||||
dbCleanup("opendockApt", 90),
|
||||
);
|
||||
}, 5 * 1000);
|
||||
|
||||
const { app, baseUrl } = await createApp();
|
||||
|
||||
const server = createServer(app);
|
||||
|
||||
setupSocketIORoutes(baseUrl, server);
|
||||
|
||||
const log = createLogger({ module: "system", subModule: "main start" });
|
||||
|
||||
// triggering long lived processes
|
||||
connectProdSql();
|
||||
|
||||
// trigger startup processes these must run before anything else can run
|
||||
await baseSettingValidationCheck();
|
||||
systemSettings = await db.select().from(settings);
|
||||
|
||||
//when starting up long lived features the name must match the setting name.
|
||||
// also we always want to have long lived processes inside a setting check.
|
||||
setTimeout(() => {
|
||||
if (systemSettings.filter((n) => n.name === "opendock_sync")[0]?.active) {
|
||||
log.info({}, "Opendock is not active");
|
||||
monitorReleaseChanges(); // this is od monitoring the db for all new releases
|
||||
opendockSocketMonitor();
|
||||
createCronJob("opendockAptCleanup", "0 30 5 * * *", () =>
|
||||
dbCleanup("opendockApt", 90),
|
||||
);
|
||||
}
|
||||
|
||||
// these jobs below are system jobs and should run no matter what.
|
||||
createCronJob("JobAuditLogCleanUp", "0 0 5 * * *", () =>
|
||||
dbCleanup("jobs", 30),
|
||||
);
|
||||
createCronJob("logsCleanup", "0 15 5 * * *", () => dbCleanup("logs", 120));
|
||||
|
||||
// one shots only needed to run on server startups
|
||||
createNotifications();
|
||||
startNotifications();
|
||||
}, 5 * 1000);
|
||||
|
||||
server.listen(port, async () => {
|
||||
log.info(
|
||||
`Listening on http://${os.hostname()}:${port}${baseUrl}, logging in ${process.env.LOG_LEVEL}, current ENV ${process.env.NODE_ENV ? process.env.NODE_ENV : "development"}`,
|
||||
|
||||
8
backend/socket.io/roomCache.socket.ts
Normal file
8
backend/socket.io/roomCache.socket.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { RoomId } from "./types.socket.js";
|
||||
|
||||
export const MAX_HISTORY = 50;
|
||||
export const FLUSH_INTERVAL = 100; // 50ms change higher if needed
|
||||
|
||||
export const roomHistory = new Map<RoomId, unknown[]>();
|
||||
export const roomBuffers = new Map<RoomId, any[]>();
|
||||
export const roomFlushTimers = new Map<RoomId, NodeJS.Timeout>();
|
||||
39
backend/socket.io/roomDefinitions.socket.ts
Normal file
39
backend/socket.io/roomDefinitions.socket.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { desc } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { logs } from "../db/schema/logs.schema.js";
|
||||
import type { RoomId } from "./types.socket.js";
|
||||
|
||||
type RoomDefinition<T = unknown> = {
|
||||
seed: (limit: number) => Promise<T[]>;
|
||||
};
|
||||
|
||||
export const protectedRooms: any = {
|
||||
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||
admin: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||
};
|
||||
|
||||
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||
logs: {
|
||||
seed: async (limit) => {
|
||||
try {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(logs)
|
||||
.orderBy(desc(logs.createdAt))
|
||||
.limit(limit);
|
||||
|
||||
return rows; //.reverse();
|
||||
} catch (e) {
|
||||
console.error("Failed to seed logs:", e);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
labels: {
|
||||
seed: async (limit) => {
|
||||
console.info(limit);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
};
|
||||
27
backend/socket.io/roomEmitter.socket.ts
Normal file
27
backend/socket.io/roomEmitter.socket.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// the emitter setup
|
||||
|
||||
import type { RoomId } from "./types.socket.js";
|
||||
|
||||
let addDataToRoom: ((roomId: RoomId, payload: unknown[]) => void) | null = null;
|
||||
|
||||
export const registerEmitter = (
|
||||
fn: (roomId: RoomId, payload: unknown[]) => void,
|
||||
) => {
|
||||
addDataToRoom = fn;
|
||||
};
|
||||
|
||||
export const emitToRoom = (roomId: RoomId, payload: unknown[]) => {
|
||||
if (!addDataToRoom) {
|
||||
console.error("Socket emitter not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
addDataToRoom(roomId, payload);
|
||||
};
|
||||
|
||||
/*
|
||||
import { emitToRoom } from "../socket/socketEmitter.js";
|
||||
// room name
|
||||
// its payload
|
||||
emitToRoom("logs", newLogRow);
|
||||
*/
|
||||
73
backend/socket.io/roomService.socket.ts
Normal file
73
backend/socket.io/roomService.socket.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { Server } from "socket.io";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import {
|
||||
FLUSH_INTERVAL,
|
||||
MAX_HISTORY,
|
||||
roomBuffers,
|
||||
roomFlushTimers,
|
||||
roomHistory,
|
||||
} from "./roomCache.socket.js";
|
||||
import { roomDefinition } from "./roomDefinitions.socket.js";
|
||||
import type { RoomId } from "./types.socket.js";
|
||||
|
||||
// get the db data if not exiting already
|
||||
const log = createLogger({ module: "socket.io", subModule: "roomService" });
|
||||
|
||||
export const preseedRoom = async (roomId: RoomId) => {
|
||||
if (roomHistory.has(roomId)) {
|
||||
return roomHistory.get(roomId);
|
||||
}
|
||||
|
||||
const roomDef = roomDefinition[roomId];
|
||||
|
||||
if (!roomDef) {
|
||||
log.error({}, `Room ${roomId} is not defined`);
|
||||
}
|
||||
|
||||
const latestData = await roomDef.seed(MAX_HISTORY);
|
||||
|
||||
roomHistory.set(roomId, latestData);
|
||||
|
||||
return latestData;
|
||||
};
|
||||
|
||||
export const createRoomEmitter = (io: Server) => {
|
||||
const addDataToRoom = <T>(roomId: RoomId, payload: T) => {
|
||||
if (!roomHistory.has(roomId)) {
|
||||
roomHistory.set(roomId, []);
|
||||
}
|
||||
|
||||
const history = roomHistory.get(roomId)!;
|
||||
history?.push(payload);
|
||||
|
||||
if (history?.length > MAX_HISTORY) {
|
||||
history?.shift();
|
||||
}
|
||||
|
||||
if (!roomBuffers.has(roomId)) {
|
||||
roomBuffers.set(roomId, []);
|
||||
}
|
||||
|
||||
roomBuffers.get(roomId)!.push(payload);
|
||||
|
||||
if (!roomFlushTimers.has(roomId)) {
|
||||
const timer = setTimeout(() => {
|
||||
const buffered = roomBuffers.get(roomId) || [];
|
||||
|
||||
if (buffered.length > 0) {
|
||||
io.to(roomId).emit("room-update", {
|
||||
roomId,
|
||||
payloads: buffered, // ✅ array now
|
||||
});
|
||||
}
|
||||
|
||||
roomBuffers.set(roomId, []);
|
||||
roomFlushTimers.delete(roomId);
|
||||
}, FLUSH_INTERVAL);
|
||||
|
||||
roomFlushTimers.set(roomId, timer);
|
||||
}
|
||||
};
|
||||
|
||||
return { addDataToRoom };
|
||||
};
|
||||
@@ -4,48 +4,137 @@ import type { Server as HttpServer } from "node:http";
|
||||
import { instrument } from "@socket.io/admin-ui";
|
||||
import { Server } from "socket.io";
|
||||
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { allowedOrigins } from "../utils/cors.utils.js";
|
||||
import { registerEmitter } from "./roomEmitter.socket.js";
|
||||
import { createRoomEmitter, preseedRoom } from "./roomService.socket.js";
|
||||
|
||||
//const __filename = fileURLToPath(import.meta.url);
|
||||
//const __dirname = dirname(__filename);
|
||||
const log = createLogger({ module: "socket.io", subModule: "setup" });
|
||||
|
||||
import { auth } from "../utils/auth.utils.js";
|
||||
//import type { Session, User } from "better-auth"; // adjust if needed
|
||||
import { protectedRooms } from "./roomDefinitions.socket.js";
|
||||
|
||||
// declare module "socket.io" {
|
||||
// interface Socket {
|
||||
// user?: User | any;
|
||||
// session?: Session;
|
||||
// }
|
||||
// }
|
||||
|
||||
export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
||||
const io = new Server(server, {
|
||||
path: `${baseUrl}/api/socket.io`,
|
||||
cors: {
|
||||
origin: ["http://localhost:3000", "https://admin.socket.io"],
|
||||
origin: allowedOrigins,
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
|
||||
// ✅ Create emitter instance
|
||||
const { addDataToRoom } = createRoomEmitter(io);
|
||||
registerEmitter(addDataToRoom);
|
||||
|
||||
io.use(async (socket, next) => {
|
||||
try {
|
||||
//const cookieHeader = socket.handshake.headers.cookie;
|
||||
const headers = new Headers();
|
||||
|
||||
for (const [key, value] of Object.entries(socket.request.headers)) {
|
||||
if (typeof value === "string") {
|
||||
headers.set(key, value);
|
||||
} else if (Array.isArray(value)) {
|
||||
headers.set(key, value.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
const session = await auth.api.getSession({
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return next(); // allow connection, but no auth
|
||||
}
|
||||
|
||||
if (session) {
|
||||
socket.user = session.user;
|
||||
socket.session = session as any;
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
io.on("connection", (s) => {
|
||||
console.info(s.id);
|
||||
log.info({}, `User connected: ${s.id}`);
|
||||
|
||||
s.emit("welcome", {
|
||||
serverTime: Date.now(),
|
||||
availableRooms: ["logs", "labels"],
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
s.on("join-room", async (rn) => {
|
||||
const config = protectedRooms[rn];
|
||||
|
||||
if (config?.requiresAuth && !s.user) {
|
||||
return s.emit("room-error", {
|
||||
room: rn,
|
||||
message: "Authentication required",
|
||||
});
|
||||
}
|
||||
|
||||
const roles = Array.isArray(config.role) ? config.role : [config.role];
|
||||
|
||||
console.log(roles, s.user.role);
|
||||
|
||||
//if (config?.role && s.user?.role !== config.role) {
|
||||
if (config?.role && !roles.includes(s.user?.role)) {
|
||||
return s.emit("room-error", {
|
||||
room: rn,
|
||||
message: `Not authorized to be in room: ${rn}`,
|
||||
});
|
||||
}
|
||||
s.join(rn);
|
||||
|
||||
// get room seeded
|
||||
const history = await preseedRoom(rn);
|
||||
log.info({}, `User joined ${rn}: ${s.id}`);
|
||||
// send the intial data
|
||||
s.emit("room-update", {
|
||||
roomId: rn,
|
||||
payloads: history,
|
||||
initial: true,
|
||||
});
|
||||
});
|
||||
|
||||
s.on("leave-room", (room) => {
|
||||
s.leave(room);
|
||||
log.info({}, `${s.id} left room: ${room}`);
|
||||
});
|
||||
});
|
||||
|
||||
// admin stuff for socket io
|
||||
// app.use(
|
||||
// express.static(
|
||||
// join(__dirname, "../../../node_modules/@socket.io/admin-ui/dist"),
|
||||
// ),
|
||||
// );
|
||||
io.on("disconnect", (s) => {
|
||||
log.info({}, "User disconnected:", s.id);
|
||||
});
|
||||
|
||||
// admin stuff
|
||||
|
||||
// app.get(baseUrl + "/admindashboard", (_, res) => {
|
||||
// res.sendFile(
|
||||
// join(
|
||||
// __dirname,
|
||||
// "../../../node_modules/@socket.io/admin-ui/dist/index.js",
|
||||
// ),
|
||||
// );
|
||||
// });
|
||||
const admin = io.of("/admin");
|
||||
admin.on("connection", () => {
|
||||
console.info("Connected to admin userspace");
|
||||
admin.on("connection", (s) => {
|
||||
log.info({}, `User connected: ${s.id}`);
|
||||
});
|
||||
|
||||
admin.on("disconnect", (s) => {
|
||||
log.info({}, "User disconnected:", s.id);
|
||||
});
|
||||
|
||||
instrument(io, {
|
||||
auth: false,
|
||||
//namespaceName: "/admin",
|
||||
});
|
||||
//setup all the routes
|
||||
// app.use(`${baseUrl}/api/datamart`, runQuery);
|
||||
// app.use(`${baseUrl}/api/datamart`, addQuery);
|
||||
// app.use(`${baseUrl}/api/datamart`, updateQuery);
|
||||
// just sending a get on datamart will return all the queries that we can call.
|
||||
};
|
||||
|
||||
1
backend/socket.io/types.socket.ts
Normal file
1
backend/socket.io/types.socket.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type RoomId = "logs" | "labels"; //| "alerts" | "metrics";
|
||||
44
backend/system/settings.route.ts
Normal file
44
backend/system/settings.route.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { type Response, Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { settings } from "../db/schema/settings.schema.js";
|
||||
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
// export const updateSetting = async (setting: Setting) => {
|
||||
// // TODO: when the setting is a feature setting we will need to have it run each kill switch on the crons well just stop them and during a reset it just wont start them
|
||||
// // TODO: when the setting is a system we will need to force an app restart
|
||||
// // TODO: when the setting is standard we don't do anything.
|
||||
// };
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/", async (_, res: Response) => {
|
||||
const { data: sName, error: sError } = await tryCatch(
|
||||
db.select().from(settings).orderBy(settings.name),
|
||||
);
|
||||
|
||||
if (sError) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "settings",
|
||||
message: `There was an error getting the settings `,
|
||||
data: [sError],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "system",
|
||||
subModule: "settings",
|
||||
message: `All current settings`,
|
||||
data: sName ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
334
backend/system/settingsBase.controller.ts
Normal file
334
backend/system/settingsBase.controller.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { type NewSetting, settings } from "../db/schema/settings.schema.js";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const newSettings: NewSetting[] = [
|
||||
// feature settings
|
||||
{
|
||||
name: "opendock_sync",
|
||||
value: "0",
|
||||
active: false,
|
||||
description: "Dock Scheduling system",
|
||||
moduleName: "opendock",
|
||||
settingType: "feature",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "ocp",
|
||||
value: "1",
|
||||
active: false,
|
||||
description: "One click print",
|
||||
moduleName: "ocp",
|
||||
settingType: "feature",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "ocme",
|
||||
value: "0",
|
||||
active: false,
|
||||
description: "Dayton Agv system",
|
||||
moduleName: "ocme",
|
||||
settingType: "feature",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "demandManagement",
|
||||
value: "1",
|
||||
active: false,
|
||||
description: "Fake EDI System",
|
||||
moduleName: "demandManagement",
|
||||
settingType: "feature",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "qualityRequest",
|
||||
value: "0",
|
||||
active: false,
|
||||
description: "Quality System",
|
||||
moduleName: "qualityRequest",
|
||||
settingType: "feature",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "tms",
|
||||
value: "0",
|
||||
active: false,
|
||||
description: "Transport system integration",
|
||||
moduleName: "tms",
|
||||
settingType: "feature",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
|
||||
// standard settings
|
||||
{
|
||||
name: "prolinkCheck",
|
||||
value: "1",
|
||||
active: true,
|
||||
description:
|
||||
"Will prolink be considered to check if matches, mainly used in plants that do not fully utilize prolink + ocp",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "bookin",
|
||||
value: "1",
|
||||
active: true,
|
||||
description: "Will we book in the labels after they are printed.",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin", "supervisor"],
|
||||
seedVersion: 2,
|
||||
},
|
||||
{
|
||||
name: "printDelay",
|
||||
value: "90",
|
||||
active: true,
|
||||
description: "The default time between label printing",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "dualPrinting",
|
||||
value: "0",
|
||||
active: true,
|
||||
description: "Are we producing on 2 lines that pack into the 1 packer",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "fifoCheck",
|
||||
value: "45",
|
||||
active: true,
|
||||
description:
|
||||
"This check is used to do a more near fifo check when pulling pallets for the agv's",
|
||||
moduleName: "ocme",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "dayCheck",
|
||||
value: "3",
|
||||
active: true,
|
||||
description: "how many days +/- to check for shipments in alplaprod",
|
||||
moduleName: "ocme",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "maxLotsPerTruck",
|
||||
value: "3",
|
||||
active: true,
|
||||
description:
|
||||
"What is the maximum amount of lots that can be pulled for a truck this",
|
||||
moduleName: "ocme",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "monitorAddress",
|
||||
value: "8",
|
||||
active: true,
|
||||
description:
|
||||
"What address(2) are we monitoring for lot restrictions. multiple addresses can be used but should be separated by a comma ,",
|
||||
moduleName: "ocme",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "ocmeCycleCount",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"Are you enabling the system count page? meaning that we will allow a 'manual cycle count' vs full auto",
|
||||
moduleName: "ocme",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "inhouseDelivery",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"This is for in-house plants that deliver direct to the customer, note if book-in is off this will be ignored ",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "dycoConnect",
|
||||
value: "0",
|
||||
active: true,
|
||||
description: "Connection to the dyco in dayton for the labeling process",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "dycoPrint",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"This tells us we want to use the dyco system to print the labels",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "strapperCheck",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"Strapper alarm check this is more used in tandem with dycoPrint setting",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "rfid_ocp",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"Enable the rfid pallet mgt system this replaces dycoPrint but can be overridden by dycoPrint",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "ocpCycleDelay",
|
||||
value: "10",
|
||||
active: true,
|
||||
description:
|
||||
"How long is the delay to fire off the printer check status, Defaulting to 10 seconds",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "pNgAddress",
|
||||
value: "139",
|
||||
active: true,
|
||||
description:
|
||||
"This is the P&G address that is used when uploading the new forecast from P&G",
|
||||
moduleName: "demandManagement",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "zechetti_1",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"Active the Zechetti 1 process, to print from and no longer utilize the pc",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "zechetti_2",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"Active the Zechetti 2 process, to print from and no longer utilize the pc",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "checkColor",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"During the material check are we going to check the color has enough provided to create the next pallet",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "checkPKG",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"During the material check are we going to check the packaging has enough provided to create the next pallet",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
{
|
||||
name: "lotPrintDelay",
|
||||
value: "0",
|
||||
active: true,
|
||||
description:
|
||||
"Override ride the printer printer delay setting, by default if this is on all printers are controlled by this.",
|
||||
moduleName: "ocp",
|
||||
settingType: "standard",
|
||||
roles: ["admin"],
|
||||
seedVersion: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export const baseSettingValidationCheck = async () => {
|
||||
const log = createLogger({ module: "system", subModule: "settings" });
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.insert(settings)
|
||||
.values(newSettings)
|
||||
.onConflictDoUpdate({
|
||||
target: settings.name,
|
||||
set: {
|
||||
roles: sql`excluded.roles`,
|
||||
description: sql`excluded.description`,
|
||||
moduleName: sql`excluded."moduleName"`,
|
||||
settingType: sql`excluded."settingType"`,
|
||||
seedVersion: sql`excluded.seed_version`,
|
||||
upd_user: "LST_System",
|
||||
upd_date: sql`now()`,
|
||||
},
|
||||
where: sql`
|
||||
settings.seed_version IS NULL
|
||||
OR settings.seed_version < excluded.seed_version
|
||||
`,
|
||||
})
|
||||
.returning(),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
log.error(
|
||||
{ error: error },
|
||||
"There was an error when adding or updating the settings.",
|
||||
);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
log.info({}, "All Settings were added/updated");
|
||||
}
|
||||
};
|
||||
38
backend/system/settingsFeatures.controller.ts
Normal file
38
backend/system/settingsFeatures.controller.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* When a feature setting gets updated we will handle it here.
|
||||
* we will stop jobs, stop cycles
|
||||
*/
|
||||
|
||||
import { dbCleanup } from "../db/dbCleanup.controller.js";
|
||||
import type { Setting } from "../db/schema/settings.schema.js";
|
||||
import { monitorReleaseChanges } from "../opendock/openDockRreleaseMonitor.utils.js";
|
||||
import {
|
||||
killOpendockSocket,
|
||||
opendockSocketMonitor,
|
||||
} from "../opendock/opendockSocketMonitor.utils.js";
|
||||
import {
|
||||
createCronJob,
|
||||
resumeCronJob,
|
||||
stopCronJob,
|
||||
} from "../utils/croner.utils.js";
|
||||
|
||||
export const featureControl = async (data: Setting) => {
|
||||
// when a feature is changed to active or deactivated we will update the cron.
|
||||
if (data.active) {
|
||||
resumeCronJob(data.name);
|
||||
} else {
|
||||
stopCronJob(data.name);
|
||||
}
|
||||
|
||||
// specific setting stuff should have handled like below. what needs turned back on or off.
|
||||
if (data.name === "opendock_sync" && data.active) {
|
||||
opendockSocketMonitor();
|
||||
monitorReleaseChanges();
|
||||
createCronJob("opendockAptCleanup", "0 30 5 * * *", () =>
|
||||
dbCleanup("opendockApt", 90),
|
||||
);
|
||||
} else {
|
||||
killOpendockSocket();
|
||||
stopCronJob("opendockAptCleanup");
|
||||
}
|
||||
};
|
||||
110
backend/system/settingsUpdate.route.ts
Normal file
110
backend/system/settingsUpdate.route.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { type Request, type Response, Router } from "express";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { settings } from "../db/schema/settings.schema.js";
|
||||
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
import { featureControl } from "./settingsFeatures.controller.js";
|
||||
|
||||
// // TODO: when the setting is a system we will need to force an app restart
|
||||
// // TODO: when the setting is standard we don't do anything.
|
||||
// };
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.patch("/:name", async (req: Request, res: Response) => {
|
||||
const { name } = req.params;
|
||||
const updates: Record<string, unknown | null> = {};
|
||||
// lets see if we even have a setting name
|
||||
|
||||
const { data: sName, error: sError } = await tryCatch(
|
||||
db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.name, name ?? "")),
|
||||
);
|
||||
|
||||
if (sError) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "settings",
|
||||
message: `There was an error checking the name of the setting`,
|
||||
data: [sError],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (sName?.length === 0) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "system",
|
||||
subModule: "settings",
|
||||
message: `The setting "${name}" dose not appear to be a valid setting please check the name and try again. `,
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// manage the actual setting. we will still do an upsert just in case we strangely get past everything
|
||||
|
||||
if (req.body?.value !== undefined) {
|
||||
updates.value = req.body.value;
|
||||
}
|
||||
|
||||
if (req.body?.description !== undefined) {
|
||||
updates.description = req.body.description;
|
||||
}
|
||||
|
||||
if (req.body?.moduleName !== undefined) {
|
||||
updates.moduleName = req.body.moduleName;
|
||||
}
|
||||
|
||||
if (req.body?.active !== undefined) {
|
||||
updates.active = req.body.active === "true";
|
||||
}
|
||||
|
||||
if (req.body?.roles !== undefined) {
|
||||
updates.roles = req.body.roles;
|
||||
}
|
||||
|
||||
if (req.body?.settingType !== undefined) {
|
||||
updates.settingType = req.body.settingType;
|
||||
}
|
||||
|
||||
updates.upd_user = req.user?.username || "lst_user";
|
||||
updates.upd_date = sql`NOW()`;
|
||||
|
||||
const updatedSetting = await db
|
||||
.update(settings)
|
||||
.set(updates)
|
||||
.where(eq(settings.name, name ?? ""))
|
||||
.returning();
|
||||
|
||||
// the switch statment will only run when the setting actually updates
|
||||
switch (updatedSetting[0]?.settingType) {
|
||||
case "feature":
|
||||
await featureControl(updatedSetting[0]);
|
||||
break;
|
||||
case "system":
|
||||
// TODO: add the system control logic in to restart the app if not in dev mode
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "system",
|
||||
subModule: "settings",
|
||||
message: `Setting "${name}" Was just updated. `,
|
||||
data: updatedSetting,
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
@@ -16,6 +16,7 @@ router.get("/", async (_, res) => {
|
||||
res.status(200).json({
|
||||
status: "ok",
|
||||
uptime: process.uptime(),
|
||||
nodeVersion: process.version,
|
||||
memoryUsage: `Heap: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB / RSS: ${(
|
||||
used.rss / 1024 / 1024
|
||||
).toFixed(2)} MB`,
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import type { Express } from "express";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import getSettings from "./settings.route.js";
|
||||
import updSetting from "./settingsUpdate.route.js";
|
||||
import stats from "./stats.route.js";
|
||||
|
||||
export const setupSystemRoutes = (baseUrl: string, app: Express) => {
|
||||
//stats will be like this as we dont need to change this
|
||||
app.use(`${baseUrl}/api/stats`, stats);
|
||||
app.use(`${baseUrl}/api/settings`, getSettings);
|
||||
app.use(`${baseUrl}/api/settings`, requireAuth, updSetting);
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
|
||||
9
backend/types/socket.d.ts
vendored
Normal file
9
backend/types/socket.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import "socket.io";
|
||||
import type { Session, User } from "better-auth"; // adjust if needed
|
||||
|
||||
declare module "socket.io" {
|
||||
interface Socket {
|
||||
user?: User | any;
|
||||
session?: Session;
|
||||
}
|
||||
}
|
||||
26
backend/utils/auth.permissions.ts
Normal file
26
backend/utils/auth.permissions.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createAccessControl } from "better-auth/plugins/access";
|
||||
|
||||
export const statement = {
|
||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
user: ["ban"],
|
||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
} as const;
|
||||
|
||||
export const ac = createAccessControl(statement);
|
||||
|
||||
export const user = ac.newRole({
|
||||
app: ["read", "create"],
|
||||
notifications: ["read", "create"],
|
||||
});
|
||||
|
||||
export const admin = ac.newRole({
|
||||
app: ["read", "create", "update"],
|
||||
});
|
||||
|
||||
export const systemAdmin = ac.newRole({
|
||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
user: ["ban"],
|
||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
});
|
||||
@@ -1,17 +1,18 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import {
|
||||
admin,
|
||||
apiKey,
|
||||
createAuthMiddleware,
|
||||
customSession,
|
||||
admin as adminPlugin,
|
||||
// apiKey,
|
||||
// createAuthMiddleware,
|
||||
//customSession,
|
||||
jwt,
|
||||
lastLoginMethod,
|
||||
username,
|
||||
} from "better-auth/plugins";
|
||||
import { eq } from "drizzle-orm";
|
||||
//import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import * as rawSchema from "../db/schema/auth.schema.js";
|
||||
import { ac, admin, systemAdmin, user } from "./auth.permissions.js";
|
||||
import { allowedOrigins } from "./cors.utils.js";
|
||||
import { sendEmail } from "./sendEmail.utils.js";
|
||||
|
||||
@@ -31,57 +32,60 @@ export const auth = betterAuth({
|
||||
provider: "pg",
|
||||
schema,
|
||||
}),
|
||||
trustedOrigins: allowedOrigins,
|
||||
user: {
|
||||
additionalFields: {
|
||||
role: {
|
||||
type: "string",
|
||||
required: false,
|
||||
input: false,
|
||||
},
|
||||
lastLogin: {
|
||||
type: "date",
|
||||
required: true,
|
||||
//required: false,
|
||||
input: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
jwt({ jwt: { expirationTime: "1h" } }),
|
||||
apiKey(),
|
||||
admin(),
|
||||
//apiKey(),
|
||||
adminPlugin({
|
||||
ac,
|
||||
roles: {
|
||||
admin,
|
||||
user,
|
||||
systemAdmin,
|
||||
},
|
||||
}),
|
||||
lastLoginMethod(),
|
||||
username({
|
||||
minUsernameLength: 5,
|
||||
usernameValidator: (username) => {
|
||||
if (username === "admin") {
|
||||
if (username === "admin" || username === "root") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
customSession(async ({ user, session }) => {
|
||||
const roles = await db
|
||||
.select({ roles: rawSchema.user.role })
|
||||
.from(rawSchema.user)
|
||||
.where(eq(rawSchema.user.id, session.id));
|
||||
return {
|
||||
roles,
|
||||
user: {
|
||||
...user,
|
||||
//newField: "newField",
|
||||
},
|
||||
session,
|
||||
};
|
||||
}),
|
||||
|
||||
// customSession(async ({ user, session }) => {
|
||||
// const roles = await db
|
||||
// .select({ roles: rawSchema.user.role })
|
||||
// .from(rawSchema.user)
|
||||
// .where(eq(rawSchema.user.id, session.id));
|
||||
// return {
|
||||
// roles,
|
||||
// user: {
|
||||
// ...user,
|
||||
// //newField: "newField",
|
||||
// },
|
||||
// session,
|
||||
// };
|
||||
// }),
|
||||
],
|
||||
trustedOrigins: allowedOrigins,
|
||||
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
minPasswordLength: 8, // optional config
|
||||
resetPasswordTokenExpirySeconds: process.env.RESET_EXPIRY_SECONDS, // time in seconds
|
||||
sendResetPassword: async ({ user, token }) => {
|
||||
const frontendUrl = `${process.env.BETTER_AUTH_URL}/lst/app/user/resetpassword?token=${token}`;
|
||||
const frontendUrl = `${process.env.URL}/lst/app/user/resetpassword?token=${token}`;
|
||||
const expiryMinutes = Math.floor(
|
||||
parseInt(process.env.RESET_EXPIRY_SECONDS ?? "3600", 10) / 60,
|
||||
);
|
||||
@@ -122,16 +126,16 @@ export const auth = betterAuth({
|
||||
secure: false,
|
||||
httpOnly: true,
|
||||
},
|
||||
hooks: {
|
||||
after: createAuthMiddleware(async (ctx) => {
|
||||
if (ctx.path.startsWith("/login")) {
|
||||
const newSession = ctx.context.newSession;
|
||||
if (newSession) {
|
||||
// something here later
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
// hooks: {
|
||||
// after: createAuthMiddleware(async (ctx) => {
|
||||
// if (ctx.path.startsWith("/login")) {
|
||||
// const newSession = ctx.context.newSession;
|
||||
// if (newSession) {
|
||||
// // something here later
|
||||
// }
|
||||
// }
|
||||
// }),
|
||||
// },
|
||||
events: {
|
||||
// async onSignInSuccess({ user }: { user: User }) {
|
||||
// await db
|
||||
|
||||
@@ -12,6 +12,10 @@ export const allowedOrigins = [
|
||||
"https://admin.socket.io",
|
||||
"https://electron-socket-io-playground.vercel.app",
|
||||
`${process.env.URL}`,
|
||||
`http://${process.env.PROD_SERVER}:3000`,
|
||||
`http://${process.env.PROD_SERVER}:3100`, // temp
|
||||
`http://usmcd1olp082:3000`,
|
||||
`${process.env.EXTERNAL_URL}`, // internal docker
|
||||
];
|
||||
export const lstCors = () => {
|
||||
return cors({
|
||||
|
||||
38
backend/utils/croner.minConvert.ts
Normal file
38
backend/utils/croner.minConvert.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
|
||||
const minTime = 5;
|
||||
export const minutesToCron = (minutes: number): string => {
|
||||
const log = createLogger({ module: "system", subModule: "croner" });
|
||||
if (minutes < minTime) {
|
||||
log.error(
|
||||
{},
|
||||
`Conversion of less then ${minTime} min and is not allowed on this server`,
|
||||
);
|
||||
}
|
||||
|
||||
// Every X minutes (under 60)
|
||||
if (minutes < 60) {
|
||||
return `*/${minutes} * * * *`;
|
||||
}
|
||||
|
||||
// Every X hours (clean division)
|
||||
if (minutes % 60 === 0 && minutes < 1440) {
|
||||
const hours = minutes / 60;
|
||||
return `0 0 */${hours} * * *`;
|
||||
}
|
||||
|
||||
// Every day
|
||||
if (minutes === 1440) {
|
||||
return `0 0 8 * * *`; // 8am
|
||||
}
|
||||
|
||||
// Every X days (clean division)
|
||||
if (minutes % 1440 === 0) {
|
||||
const days = minutes / 1440;
|
||||
return `0 0 0 */${days} * *`;
|
||||
}
|
||||
|
||||
// Fallback (not cleanly divisible)
|
||||
// Cron can't represent this perfectly → run every X minutes as approximation
|
||||
return `0 */${minutes} * * * *`;
|
||||
};
|
||||
@@ -18,9 +18,10 @@ export interface JobInfo {
|
||||
|
||||
// Store running cronjobs
|
||||
export const runningCrons: Record<string, Cron> = {};
|
||||
const log = createLogger({ module: "system", subModule: "croner" });
|
||||
|
||||
// how to se the times
|
||||
// * ┌──────────────── (optional) second (0 - 59) \n
|
||||
// * ┌──────────────── (optional) second (0 - 59)
|
||||
// * │ ┌────────────── minute (0 - 59)
|
||||
// * │ │ ┌──────────── hour (0 - 23)
|
||||
// * │ │ │ ┌────────── day of month (1 - 31)
|
||||
@@ -30,6 +31,8 @@ export const runningCrons: Record<string, Cron> = {};
|
||||
// * │ │ │ │ │ │ ┌──── (optional) year (1 - 9999)
|
||||
// * │ │ │ │ │ │ │
|
||||
// * * 05 * * * * *
|
||||
// note that when leaving * anywhere means ever part of that. IE * in the seconds part will run every second inside that min
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name Name of the job we want to run
|
||||
@@ -43,7 +46,6 @@ export const createCronJob = async (
|
||||
) => {
|
||||
// get the timezone based on the os timezone set
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const log = createLogger({ module: "system", subModule: "croner" });
|
||||
|
||||
// Destroy existing job if it exist
|
||||
if (runningCrons[name]) {
|
||||
@@ -124,11 +126,19 @@ export const removeCronJob = (name: string) => {
|
||||
export const stopCronJob = (name: string) => {
|
||||
if (runningCrons[name]) {
|
||||
runningCrons[name].pause();
|
||||
log.info(
|
||||
{},
|
||||
`${name} was just stopped either manually or due to a setting change.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const resumeCronJob = (name: string) => {
|
||||
if (runningCrons[name]) {
|
||||
runningCrons[name].resume();
|
||||
log.info(
|
||||
{},
|
||||
`${name} was just restarted either manually or due to a setting change.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import type { Response } from "express";
|
||||
import { createLogger } from "../logger/logger.controller.js";
|
||||
|
||||
interface Data {
|
||||
interface Data<T = unknown[]> {
|
||||
success: boolean;
|
||||
module: "system" | "ocp" | "routes" | "datamart" | "utils";
|
||||
module:
|
||||
| "system"
|
||||
| "ocp"
|
||||
| "routes"
|
||||
| "datamart"
|
||||
| "utils"
|
||||
| "opendock"
|
||||
| "notification";
|
||||
subModule:
|
||||
| "db"
|
||||
| "labeling"
|
||||
@@ -13,10 +20,20 @@ interface Data {
|
||||
| "sendmail"
|
||||
| "auth"
|
||||
| "datamart"
|
||||
| "jobs";
|
||||
| "jobs"
|
||||
| "apt"
|
||||
| "settings"
|
||||
| "get"
|
||||
| "update"
|
||||
| "delete"
|
||||
| "post"
|
||||
| "notification"
|
||||
| "delete"
|
||||
| "printing";
|
||||
level: "info" | "error" | "debug" | "fatal";
|
||||
message: string;
|
||||
data?: unknown[];
|
||||
room?: string;
|
||||
data?: T;
|
||||
notify?: boolean;
|
||||
}
|
||||
|
||||
@@ -36,20 +53,21 @@ interface Data {
|
||||
*/
|
||||
export const returnFunc = (data: Data) => {
|
||||
const notify = data.notify ? data.notify : false;
|
||||
const room = data.room ?? data.room;
|
||||
const log = createLogger({ module: data.module, subModule: data.subModule });
|
||||
// handle the logging part
|
||||
switch (data.level) {
|
||||
case "info":
|
||||
log.info({ notify: notify }, data.message);
|
||||
log.info({ notify: notify, room }, data.message);
|
||||
break;
|
||||
case "error":
|
||||
log.error({ notify: notify, error: data.data }, data.message);
|
||||
log.error({ notify: notify, error: data.data, room }, data.message);
|
||||
break;
|
||||
case "debug":
|
||||
log.debug({ notify: notify }, data.message);
|
||||
log.debug({ notify: notify, room }, data.message);
|
||||
break;
|
||||
case "fatal":
|
||||
log.fatal({ notify: notify }, data.message);
|
||||
log.fatal({ notify: notify, room }, data.message);
|
||||
}
|
||||
|
||||
// api section to return
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import nodemailer from "nodemailer";
|
||||
import type Mail from "nodemailer/lib/mailer/index.js";
|
||||
import type { Address } from "nodemailer/lib/mailer/index.js";
|
||||
import type SMTPTransport from "nodemailer/lib/smtp-transport/index.js";
|
||||
import hbs from "nodemailer-express-handlebars";
|
||||
import { returnFunc } from "./returnHelper.utils.js";
|
||||
import { tryCatch } from "./trycatch.utils.js";
|
||||
@@ -24,62 +21,36 @@ interface EmailData {
|
||||
}
|
||||
|
||||
export const sendEmail = async (data: EmailData) => {
|
||||
let transporter: Transporter;
|
||||
let fromEmail: string | Address;
|
||||
const fromEmail: string = `DoNotReply@mail.alpla.com`;
|
||||
const transporter: Transporter = nodemailer.createTransport({
|
||||
host: "smtp.azurecomm.net",
|
||||
port: 587,
|
||||
//rejectUnauthorized: false,
|
||||
tls: {
|
||||
minVersion: "TLSv1.2",
|
||||
},
|
||||
auth: {
|
||||
user: "donotreply@mail.alpla.com",
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
},
|
||||
debug: true,
|
||||
});
|
||||
|
||||
if (os.hostname().includes("OLP")) {
|
||||
transporter = nodemailer.createTransport({
|
||||
service: "gmail",
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASSWORD, // The 16-character App Password
|
||||
},
|
||||
//debug: true,
|
||||
});
|
||||
|
||||
// update the from email
|
||||
fromEmail = process.env.EMAIL_USER as string;
|
||||
|
||||
// update the from email
|
||||
fromEmail = `donotreply@alpla.com`;
|
||||
} else {
|
||||
//create the servers smtp config
|
||||
let host = `${os.hostname().replace("VMS006", "")}-smtp.alpla.net`;
|
||||
|
||||
if (os.hostname().includes("VMS036")) {
|
||||
host = "USMCD1-smtp.alpla.net";
|
||||
}
|
||||
|
||||
transporter = nodemailer.createTransport({
|
||||
host: host,
|
||||
port: 25,
|
||||
rejectUnauthorized: false,
|
||||
//secure: false,
|
||||
// auth: {
|
||||
// user: "alplaprod",
|
||||
// pass: "obelix",
|
||||
// },
|
||||
debug: true,
|
||||
} as SMTPTransport.Options);
|
||||
|
||||
// update the from email
|
||||
fromEmail = `donotreply@alpla.com`;
|
||||
}
|
||||
|
||||
// create the handlebars view
|
||||
// creating the handlbar options
|
||||
const viewPath = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"./mailViews",
|
||||
"../utils/mailViews/",
|
||||
);
|
||||
|
||||
const handlebarOptions = {
|
||||
viewEngine: {
|
||||
extname: ".hbs",
|
||||
defaultLayout: "",
|
||||
//layoutsDir: path.resolve(viewPath, "layouts"), // Path to layouts directory
|
||||
defaultLayout: "", // Specify the default layout
|
||||
partialsDir: viewPath,
|
||||
},
|
||||
viewPath: viewPath,
|
||||
extName: ".hbs",
|
||||
extName: ".hbs", // File extension for Handlebars templates
|
||||
};
|
||||
|
||||
transporter.use("compile", hbs(handlebarOptions));
|
||||
@@ -89,7 +60,7 @@ export const sendEmail = async (data: EmailData) => {
|
||||
to: data.email,
|
||||
subject: data.subject,
|
||||
//text: "You will have a reset token here and only have 30min to click the link before it expires.",
|
||||
//html: emailTemplate("Blake's Test", "This is an example with css"),
|
||||
//html: emailTemplate("BlakesTest", "This is an example with css"),
|
||||
template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs')
|
||||
context: data.context,
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": ["**", "!!**/dist","!!**/frontend", "!!**/lst_docs"]
|
||||
"includes": ["**", "!!**/dist", "!!**/lst_docs"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
|
||||
37
brunoApi/auth/Login.bru
Normal file
37
brunoApi/auth/Login.bru
Normal file
@@ -0,0 +1,37 @@
|
||||
meta {
|
||||
name: Login
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/api/auth/sign-in/email
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
headers {
|
||||
Origin: http://localhost:3000
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"email": "blake.matthes@alpla.com",
|
||||
"password": "nova0511"
|
||||
}
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
// // grab the raw Set-Cookie header
|
||||
// const cookies = res.headers["set-cookie"];
|
||||
|
||||
// const sessionCookie = cookies[0].split(";")[0];
|
||||
|
||||
// // Save it as an environment variable
|
||||
// bru.setEnvVar("session_cookie", sessionCookie);
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
35
brunoApi/auth/Register.bru
Normal file
35
brunoApi/auth/Register.bru
Normal file
@@ -0,0 +1,35 @@
|
||||
meta {
|
||||
name: Register
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/api/authentication/register
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"name":"Blake", // option when in the frontend as we will pass over as username if not added
|
||||
"username": "matthes01",
|
||||
"email": "blake.matthes@alpla.com",
|
||||
"password": "nova0511"
|
||||
}
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
// // grab the raw Set-Cookie header
|
||||
// const cookies = res.headers["set-cookie"];
|
||||
|
||||
// const sessionCookie = cookies[0].split(";")[0];
|
||||
|
||||
// // Save it as an environment variable
|
||||
// bru.setEnvVar("session_cookie", sessionCookie);
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
brunoApi/auth/folder.bru
Normal file
8
brunoApi/auth/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: auth
|
||||
seq: 5
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
16
brunoApi/auth/getSession.bru
Normal file
16
brunoApi/auth/getSession.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: getSession
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/api/auth/get-session
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
9
brunoApi/bruno.json
Normal file
9
brunoApi/bruno.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "lst_v3",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
3
brunoApi/collection.bru
Normal file
3
brunoApi/collection.bru
Normal file
@@ -0,0 +1,3 @@
|
||||
docs {
|
||||
All Api endpoints to the logistics support tool
|
||||
}
|
||||
16
brunoApi/datamart/Get queries.bru
Normal file
16
brunoApi/datamart/Get queries.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Get queries
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/api/datamart
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
20
brunoApi/datamart/Run Query.bru
Normal file
20
brunoApi/datamart/Run Query.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Run Query
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/api/datamart/:name
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:path {
|
||||
name: activeArticles
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
brunoApi/datamart/folder.bru
Normal file
8
brunoApi/datamart/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: datamart
|
||||
seq: 2
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
7
brunoApi/environments/lstv3.bru
Normal file
7
brunoApi/environments/lstv3.bru
Normal file
@@ -0,0 +1,7 @@
|
||||
vars {
|
||||
url: http://localhost:3000/lst
|
||||
readerIp: 10.44.14.215
|
||||
}
|
||||
vars:secret [
|
||||
token
|
||||
]
|
||||
20
brunoApi/notifications/Get All notifications.bru
Normal file
20
brunoApi/notifications/Get All notifications.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Get All notifications.
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/api/notification
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
|
||||
docs {
|
||||
Passing all as a query param will return all queries active and none active
|
||||
}
|
||||
24
brunoApi/notifications/Subscribe to notification.bru
Normal file
24
brunoApi/notifications/Subscribe to notification.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: Subscribe to notification
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/api/notification/sub
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"userId":"m6AbQXFwOXoX3YKLfwWgq2LIdDqS5jqv",
|
||||
"notificationId": "0399eb2a-39df-48b7-9f1c-d233cec94d2e",
|
||||
"emails": ["blake.mattes@alpla.com","cowchmonkey@gmail.com"]
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
brunoApi/notifications/folder.bru
Normal file
8
brunoApi/notifications/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: notifications
|
||||
seq: 7
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
24
brunoApi/notifications/remove sub notification.bru
Normal file
24
brunoApi/notifications/remove sub notification.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: remove sub notification
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
delete {
|
||||
url: {{url}}/api/notification/sub
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"userId":"0kHd6Kkdub4GW6rK1qa1yjWwqXtvykqT",
|
||||
"notificationId": "0399eb2a-39df-48b7-9f1c-d233cec94d2e",
|
||||
"emails": ["blake.mattes@alpla.com"]
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
brunoApi/notifications/subscriptions.bru
Normal file
16
brunoApi/notifications/subscriptions.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: subscriptions
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/api/notification/sub
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
31
brunoApi/notifications/update notification.bru
Normal file
31
brunoApi/notifications/update notification.bru
Normal file
@@ -0,0 +1,31 @@
|
||||
meta {
|
||||
name: update notification
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
patch {
|
||||
url: {{url}}/api/notification/:id
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:path {
|
||||
id: 0399eb2a-39df-48b7-9f1c-d233cec94d2e
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"active" : true,
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
|
||||
docs {
|
||||
Passing all as a query param will return all queries active and none active
|
||||
}
|
||||
24
brunoApi/notifications/update sub notification.bru
Normal file
24
brunoApi/notifications/update sub notification.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: update sub notification
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
patch {
|
||||
url: {{url}}/api/notification/sub
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"userId":"m6AbQXFwOXoX3YKLfwWgq2LIdDqS5jqv",
|
||||
"notificationId": "0399eb2a-39df-48b7-9f1c-d233cec94d2e",
|
||||
"emails": ["cowchmonkey@gmail.com"]
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
22
brunoApi/ocp/Printer Listenter.bru
Normal file
22
brunoApi/ocp/Printer Listenter.bru
Normal file
@@ -0,0 +1,22 @@
|
||||
meta {
|
||||
name: Printer Listenter
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/api/ocp/printer/listener/line_1
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"message":"xnvjdhhgsdfr"
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
brunoApi/ocp/folder.bru
Normal file
8
brunoApi/ocp/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: ocp
|
||||
seq: 9
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
16
brunoApi/opendock/GetApt.bru
Normal file
16
brunoApi/opendock/GetApt.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: GetApt
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/api/opendock
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
brunoApi/prodSql/Sql Start.bru
Normal file
16
brunoApi/prodSql/Sql Start.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Sql Start
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/api/system/prodsql/start
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
brunoApi/prodSql/Sql restart.bru
Normal file
16
brunoApi/prodSql/Sql restart.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Sql restart
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/api/system/prodsql/restart
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
brunoApi/prodSql/Sql stop.bru
Normal file
16
brunoApi/prodSql/Sql stop.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Sql stop
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/api/system/prodsql/stop
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
brunoApi/prodSql/folder.bru
Normal file
8
brunoApi/prodSql/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: prodSql
|
||||
seq: 6
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
8
brunoApi/rfidReaders/folder.bru
Normal file
8
brunoApi/rfidReaders/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: rfidReaders
|
||||
seq: 8
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
20
brunoApi/rfidReaders/reader.bru
Normal file
20
brunoApi/rfidReaders/reader.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: reader
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://usday1prod.alpla.net/lst/old/api/rfid/mgtevents/line3.1
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
20
brunoApi/rfidReaders/readerSpecific/Config.bru
Normal file
20
brunoApi/rfidReaders/readerSpecific/Config.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Config
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://{{readerIp}}/cloud/config
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{token}}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
32
brunoApi/rfidReaders/readerSpecific/Login.bru
Normal file
32
brunoApi/rfidReaders/readerSpecific/Login.bru
Normal file
@@ -0,0 +1,32 @@
|
||||
meta {
|
||||
name: Login
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://{{readerIp}}/cloud/localRestLogin
|
||||
body: none
|
||||
auth: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: admin
|
||||
password: Zebra123!
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
const body = res.getBody();
|
||||
|
||||
if (body.message) {
|
||||
bru.setEnvVar("token", body.message);
|
||||
} else {
|
||||
bru.setEnvVar("token", "error");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
237
brunoApi/rfidReaders/readerSpecific/Update Config.bru
Normal file
237
brunoApi/rfidReaders/readerSpecific/Update Config.bru
Normal file
@@ -0,0 +1,237 @@
|
||||
meta {
|
||||
name: Update Config
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
put {
|
||||
url: https://{{readerIp}}/cloud/config
|
||||
body: json
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
headers {
|
||||
Content-Type: application/json
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{token}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"GPIO-LED": {
|
||||
"GPODefaults": {
|
||||
"1": "HIGH",
|
||||
"2": "HIGH",
|
||||
"3": "HIGH",
|
||||
"4": "HIGH"
|
||||
},
|
||||
"LEDDefaults": {
|
||||
"3": "GREEN"
|
||||
},
|
||||
"TAG_READ": [
|
||||
{
|
||||
"pin": 1,
|
||||
"state": "HIGH",
|
||||
"type": "GPO"
|
||||
}
|
||||
]
|
||||
},
|
||||
"READER-GATEWAY": {
|
||||
"batching": [
|
||||
{
|
||||
"maxPayloadSizePerReport": 256000,
|
||||
"reportingInterval": 2000
|
||||
},
|
||||
{
|
||||
"maxPayloadSizePerReport": 256000,
|
||||
"reportingInterval": 2000
|
||||
}
|
||||
],
|
||||
"endpointConfig": {
|
||||
"data": {
|
||||
"event": {
|
||||
"connections": [
|
||||
{
|
||||
"additionalOptions": {
|
||||
"retention": {
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
}
|
||||
},
|
||||
"description": "",
|
||||
"name": "LST",
|
||||
"options": {
|
||||
"URL": "https://usday1prod.alpla.net/lst/old/api/rfid/taginfo/line3.4",
|
||||
"security": {
|
||||
"CACertificateFileLocation": "",
|
||||
"authenticationOptions": {},
|
||||
"authenticationType": "NONE",
|
||||
"verifyHost": false,
|
||||
"verifyPeer": false
|
||||
}
|
||||
},
|
||||
"type": "httpPost"
|
||||
},
|
||||
{
|
||||
"additionalOptions": {
|
||||
"retention": {
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
}
|
||||
},
|
||||
"description": "",
|
||||
"name": "mgt",
|
||||
"options": {
|
||||
"URL": "https://usday1prod.alpla.net/lst/old/api/rfid/mgtevents/line3.4",
|
||||
"security": {
|
||||
"CACertificateFileLocation": "",
|
||||
"authenticationOptions": {},
|
||||
"authenticationType": "NONE",
|
||||
"verifyHost": false,
|
||||
"verifyPeer": false
|
||||
}
|
||||
},
|
||||
"type": "httpPost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"managementEventConfig": {
|
||||
"errors": {
|
||||
"antenna": false,
|
||||
"cpu": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"database": true,
|
||||
"flash": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"ntp": true,
|
||||
"radio": true,
|
||||
"radio_control": true,
|
||||
"ram": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 90
|
||||
},
|
||||
"reader_gateway": true,
|
||||
"userApp": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 120
|
||||
}
|
||||
},
|
||||
"gpiEvents": true,
|
||||
"gpoEvents": true,
|
||||
"heartbeat": {
|
||||
"fields": {
|
||||
"radio_control": [
|
||||
"ANTENNAS",
|
||||
"RADIO_ACTIVITY",
|
||||
"RADIO_CONNECTION",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_ERRORS",
|
||||
"NUM_WARNINGS",
|
||||
"NUM_TAG_READS",
|
||||
"NUM_TAG_READS_PER_ANTENNA",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"NUM_RADIO_PACKETS_RXED"
|
||||
],
|
||||
"reader_gateway": [
|
||||
"NUM_DATA_MESSAGES_RXED",
|
||||
"NUM_MANAGEMENT_EVENTS_TXED",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"NUM_DATA_MESSAGES_RETAINED",
|
||||
"NUM_DATA_MESSAGES_DROPPED",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_ERRORS",
|
||||
"NUM_WARNINGS",
|
||||
"INTERFACE_CONNECTION_STATUS",
|
||||
"NOLOCKQ_DEPTH"
|
||||
],
|
||||
"system": [
|
||||
"CPU",
|
||||
"FLASH",
|
||||
"NTP",
|
||||
"RAM",
|
||||
"SYSTEMTIME",
|
||||
"TEMPERATURE",
|
||||
"UPTIME",
|
||||
"GPO",
|
||||
"GPI",
|
||||
"POWER_NEGOTIATION",
|
||||
"POWER_SOURCE",
|
||||
"MAC_ADDRESS",
|
||||
"HOSTNAME"
|
||||
],
|
||||
"userapps": [
|
||||
"STATUS",
|
||||
"CPU",
|
||||
"RAM",
|
||||
"UPTIME",
|
||||
"NUM_DATA_MESSAGES_RXED",
|
||||
"NUM_DATA_MESSAGES_TXED",
|
||||
"INCOMING_DATA_BUFFER_PERCENTAGE_REMAINING",
|
||||
"OUTGOING_DATA_BUFFER_PERCENTAGE_REMAINING"
|
||||
]
|
||||
},
|
||||
"interval": 60
|
||||
},
|
||||
"userappEvents": true,
|
||||
"warnings": {
|
||||
"cpu": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"database": true,
|
||||
"flash": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"ntp": true,
|
||||
"radio_api": true,
|
||||
"radio_control": true,
|
||||
"ram": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 80
|
||||
},
|
||||
"reader_gateway": true,
|
||||
"temperature": {
|
||||
"ambient": 75,
|
||||
"pa": 105
|
||||
},
|
||||
"userApp": {
|
||||
"reportIntervalInSec": 1800,
|
||||
"threshold": 60
|
||||
}
|
||||
}
|
||||
},
|
||||
"retention": [
|
||||
{
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
},
|
||||
{
|
||||
"maxEventRetentionTimeInMin": 500,
|
||||
"maxNumEvents": 150000,
|
||||
"throttle": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
12
brunoApi/rfidReaders/readerSpecific/folder.bru
Normal file
12
brunoApi/rfidReaders/readerSpecific/folder.bru
Normal file
@@ -0,0 +1,12 @@
|
||||
meta {
|
||||
name: readerSpecific
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: basic
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: admin
|
||||
password: Zebra123!
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user