@naisys/erp 3.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @naisys/erp might be problematic. Click here for more details.
- package/bin/naisys-erp +2 -0
- package/client-dist/android-chrome-192x192.png +0 -0
- package/client-dist/android-chrome-512x512.png +0 -0
- package/client-dist/apple-touch-icon.png +0 -0
- package/client-dist/assets/index-45dVo30p.css +1 -0
- package/client-dist/assets/index-Dffms7F_.js +168 -0
- package/client-dist/assets/naisys-logo-CzoPnn5I.webp +0 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +42 -0
- package/client-dist/site.webmanifest +22 -0
- package/dist/api-reference.d.ts +10 -0
- package/dist/api-reference.js +101 -0
- package/dist/audit.d.ts +5 -0
- package/dist/audit.js +14 -0
- package/dist/auth-middleware.d.ts +18 -0
- package/dist/auth-middleware.js +203 -0
- package/dist/dbConfig.d.ts +5 -0
- package/dist/dbConfig.js +10 -0
- package/dist/erpDb.d.ts +10 -0
- package/dist/erpDb.js +34 -0
- package/dist/erpServer.d.ts +10 -0
- package/dist/erpServer.js +321 -0
- package/dist/error-handler.d.ts +7 -0
- package/dist/error-handler.js +17 -0
- package/dist/generated/prisma/client.d.ts +154 -0
- package/dist/generated/prisma/client.js +35 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +637 -0
- package/dist/generated/prisma/commonInputTypes.js +11 -0
- package/dist/generated/prisma/enums.d.ts +59 -0
- package/dist/generated/prisma/enums.js +60 -0
- package/dist/generated/prisma/internal/class.d.ts +406 -0
- package/dist/generated/prisma/internal/class.js +50 -0
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +2722 -0
- package/dist/generated/prisma/internal/prismaNamespace.js +366 -0
- package/dist/generated/prisma/models/Attachment.d.ts +1455 -0
- package/dist/generated/prisma/models/Attachment.js +2 -0
- package/dist/generated/prisma/models/AuditLog.d.ts +1359 -0
- package/dist/generated/prisma/models/AuditLog.js +2 -0
- package/dist/generated/prisma/models/Field.d.ts +1880 -0
- package/dist/generated/prisma/models/Field.js +2 -0
- package/dist/generated/prisma/models/FieldAttachment.d.ts +1245 -0
- package/dist/generated/prisma/models/FieldAttachment.js +2 -0
- package/dist/generated/prisma/models/FieldRecord.d.ts +1625 -0
- package/dist/generated/prisma/models/FieldRecord.js +2 -0
- package/dist/generated/prisma/models/FieldSet.d.ts +1577 -0
- package/dist/generated/prisma/models/FieldSet.js +2 -0
- package/dist/generated/prisma/models/FieldValue.d.ts +1908 -0
- package/dist/generated/prisma/models/FieldValue.js +2 -0
- package/dist/generated/prisma/models/Item.d.ts +1858 -0
- package/dist/generated/prisma/models/Item.js +2 -0
- package/dist/generated/prisma/models/ItemInstance.d.ts +1987 -0
- package/dist/generated/prisma/models/ItemInstance.js +2 -0
- package/dist/generated/prisma/models/LaborTicket.d.ts +1867 -0
- package/dist/generated/prisma/models/LaborTicket.js +2 -0
- package/dist/generated/prisma/models/Operation.d.ts +2578 -0
- package/dist/generated/prisma/models/Operation.js +2 -0
- package/dist/generated/prisma/models/OperationDependency.d.ts +1434 -0
- package/dist/generated/prisma/models/OperationDependency.js +2 -0
- package/dist/generated/prisma/models/OperationFieldRef.d.ts +1539 -0
- package/dist/generated/prisma/models/OperationFieldRef.js +2 -0
- package/dist/generated/prisma/models/OperationRun.d.ts +2563 -0
- package/dist/generated/prisma/models/OperationRun.js +2 -0
- package/dist/generated/prisma/models/OperationRunComment.d.ts +1366 -0
- package/dist/generated/prisma/models/OperationRunComment.js +2 -0
- package/dist/generated/prisma/models/Order.d.ts +1931 -0
- package/dist/generated/prisma/models/Order.js +2 -0
- package/dist/generated/prisma/models/OrderRevision.d.ts +1962 -0
- package/dist/generated/prisma/models/OrderRevision.js +2 -0
- package/dist/generated/prisma/models/OrderRun.d.ts +2310 -0
- package/dist/generated/prisma/models/OrderRun.js +2 -0
- package/dist/generated/prisma/models/SchemaVersion.d.ts +985 -0
- package/dist/generated/prisma/models/SchemaVersion.js +2 -0
- package/dist/generated/prisma/models/Session.d.ts +1213 -0
- package/dist/generated/prisma/models/Session.js +2 -0
- package/dist/generated/prisma/models/Step.d.ts +2180 -0
- package/dist/generated/prisma/models/Step.js +2 -0
- package/dist/generated/prisma/models/StepRun.d.ts +1963 -0
- package/dist/generated/prisma/models/StepRun.js +2 -0
- package/dist/generated/prisma/models/User.d.ts +11819 -0
- package/dist/generated/prisma/models/User.js +2 -0
- package/dist/generated/prisma/models/UserPermission.d.ts +1348 -0
- package/dist/generated/prisma/models/UserPermission.js +2 -0
- package/dist/generated/prisma/models/WorkCenter.d.ts +1657 -0
- package/dist/generated/prisma/models/WorkCenter.js +2 -0
- package/dist/generated/prisma/models/WorkCenterUser.d.ts +1390 -0
- package/dist/generated/prisma/models/WorkCenterUser.js +2 -0
- package/dist/generated/prisma/models.d.ts +28 -0
- package/dist/generated/prisma/models.js +2 -0
- package/dist/hateoas.d.ts +7 -0
- package/dist/hateoas.js +61 -0
- package/dist/route-helpers.d.ts +318 -0
- package/dist/route-helpers.js +220 -0
- package/dist/routes/admin.d.ts +3 -0
- package/dist/routes/admin.js +147 -0
- package/dist/routes/audit.d.ts +3 -0
- package/dist/routes/audit.js +36 -0
- package/dist/routes/auth.d.ts +3 -0
- package/dist/routes/auth.js +112 -0
- package/dist/routes/dispatch.d.ts +3 -0
- package/dist/routes/dispatch.js +174 -0
- package/dist/routes/inventory.d.ts +3 -0
- package/dist/routes/inventory.js +70 -0
- package/dist/routes/item-fields.d.ts +3 -0
- package/dist/routes/item-fields.js +220 -0
- package/dist/routes/item-instances.d.ts +3 -0
- package/dist/routes/item-instances.js +426 -0
- package/dist/routes/items.d.ts +3 -0
- package/dist/routes/items.js +252 -0
- package/dist/routes/labor-tickets.d.ts +3 -0
- package/dist/routes/labor-tickets.js +268 -0
- package/dist/routes/operation-dependencies.d.ts +3 -0
- package/dist/routes/operation-dependencies.js +170 -0
- package/dist/routes/operation-field-refs.d.ts +3 -0
- package/dist/routes/operation-field-refs.js +263 -0
- package/dist/routes/operation-run-comments.d.ts +3 -0
- package/dist/routes/operation-run-comments.js +108 -0
- package/dist/routes/operation-run-transitions.d.ts +3 -0
- package/dist/routes/operation-run-transitions.js +249 -0
- package/dist/routes/operation-runs.d.ts +112 -0
- package/dist/routes/operation-runs.js +299 -0
- package/dist/routes/operations.d.ts +3 -0
- package/dist/routes/operations.js +283 -0
- package/dist/routes/order-revision-transitions.d.ts +3 -0
- package/dist/routes/order-revision-transitions.js +86 -0
- package/dist/routes/order-revisions.d.ts +51 -0
- package/dist/routes/order-revisions.js +327 -0
- package/dist/routes/order-run-transitions.d.ts +3 -0
- package/dist/routes/order-run-transitions.js +215 -0
- package/dist/routes/order-runs.d.ts +58 -0
- package/dist/routes/order-runs.js +335 -0
- package/dist/routes/orders.d.ts +3 -0
- package/dist/routes/orders.js +262 -0
- package/dist/routes/root.d.ts +3 -0
- package/dist/routes/root.js +123 -0
- package/dist/routes/schemas.d.ts +3 -0
- package/dist/routes/schemas.js +31 -0
- package/dist/routes/step-field-attachments.d.ts +3 -0
- package/dist/routes/step-field-attachments.js +231 -0
- package/dist/routes/step-fields.d.ts +100 -0
- package/dist/routes/step-fields.js +315 -0
- package/dist/routes/step-run-fields.d.ts +3 -0
- package/dist/routes/step-run-fields.js +438 -0
- package/dist/routes/step-run-transitions.d.ts +3 -0
- package/dist/routes/step-run-transitions.js +113 -0
- package/dist/routes/step-runs.d.ts +332 -0
- package/dist/routes/step-runs.js +324 -0
- package/dist/routes/steps.d.ts +3 -0
- package/dist/routes/steps.js +283 -0
- package/dist/routes/user-permissions.d.ts +3 -0
- package/dist/routes/user-permissions.js +100 -0
- package/dist/routes/users.d.ts +57 -0
- package/dist/routes/users.js +381 -0
- package/dist/routes/work-centers.d.ts +3 -0
- package/dist/routes/work-centers.js +280 -0
- package/dist/schema-registry.d.ts +3 -0
- package/dist/schema-registry.js +45 -0
- package/dist/services/attachment-service.d.ts +33 -0
- package/dist/services/attachment-service.js +118 -0
- package/dist/services/field-ref-service.d.ts +96 -0
- package/dist/services/field-ref-service.js +74 -0
- package/dist/services/field-service.d.ts +49 -0
- package/dist/services/field-service.js +114 -0
- package/dist/services/field-value-service.d.ts +61 -0
- package/dist/services/field-value-service.js +256 -0
- package/dist/services/item-instance-service.d.ts +152 -0
- package/dist/services/item-instance-service.js +155 -0
- package/dist/services/item-service.d.ts +47 -0
- package/dist/services/item-service.js +56 -0
- package/dist/services/labor-ticket-service.d.ts +40 -0
- package/dist/services/labor-ticket-service.js +148 -0
- package/dist/services/log-file-service.d.ts +4 -0
- package/dist/services/log-file-service.js +11 -0
- package/dist/services/operation-dependency-service.d.ts +33 -0
- package/dist/services/operation-dependency-service.js +30 -0
- package/dist/services/operation-run-comment-service.d.ts +17 -0
- package/dist/services/operation-run-comment-service.js +26 -0
- package/dist/services/operation-run-service.d.ts +126 -0
- package/dist/services/operation-run-service.js +347 -0
- package/dist/services/operation-service.d.ts +47 -0
- package/dist/services/operation-service.js +132 -0
- package/dist/services/order-revision-service.d.ts +53 -0
- package/dist/services/order-revision-service.js +264 -0
- package/dist/services/order-run-service.d.ts +138 -0
- package/dist/services/order-run-service.js +356 -0
- package/dist/services/order-service.d.ts +15 -0
- package/dist/services/order-service.js +68 -0
- package/dist/services/revision-diff-service.d.ts +3 -0
- package/dist/services/revision-diff-service.js +194 -0
- package/dist/services/step-run-service.d.ts +172 -0
- package/dist/services/step-run-service.js +106 -0
- package/dist/services/step-service.d.ts +104 -0
- package/dist/services/step-service.js +89 -0
- package/dist/services/user-service.d.ts +185 -0
- package/dist/services/user-service.js +132 -0
- package/dist/services/work-center-service.d.ts +29 -0
- package/dist/services/work-center-service.js +106 -0
- package/dist/supervisorAuth.d.ts +3 -0
- package/dist/supervisorAuth.js +16 -0
- package/dist/userService.d.ts +20 -0
- package/dist/userService.js +118 -0
- package/package.json +69 -0
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
|
|
6
|
+
<link rel="icon" type="image/x-icon" href="/erp/favicon.ico" />
|
|
7
|
+
<link
|
|
8
|
+
rel="apple-touch-icon"
|
|
9
|
+
sizes="180x180"
|
|
10
|
+
href="/erp/apple-touch-icon.png"
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
<link
|
|
14
|
+
rel="manifest"
|
|
15
|
+
crossorigin="use-credentials"
|
|
16
|
+
href="/erp/site.webmanifest"
|
|
17
|
+
/>
|
|
18
|
+
<meta
|
|
19
|
+
name="viewport"
|
|
20
|
+
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<meta name="theme-color" content="#1a1b1e" />
|
|
24
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
25
|
+
<meta
|
|
26
|
+
name="apple-mobile-web-app-status-bar-style"
|
|
27
|
+
content="black-translucent"
|
|
28
|
+
/>
|
|
29
|
+
<meta name="apple-mobile-web-app-title" content="NAISYS ERP" />
|
|
30
|
+
|
|
31
|
+
<meta name="description" content="NAISYS ERP" />
|
|
32
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
33
|
+
<meta name="format-detection" content="telephone=no" />
|
|
34
|
+
|
|
35
|
+
<title>NAISYS ERP</title>
|
|
36
|
+
<script type="module" crossorigin src="/erp/assets/index-Dffms7F_.js"></script>
|
|
37
|
+
<link rel="stylesheet" crossorigin href="/erp/assets/index-45dVo30p.css">
|
|
38
|
+
</head>
|
|
39
|
+
<body>
|
|
40
|
+
<div id="root"></div>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "NAISYS ERP",
|
|
3
|
+
"short_name": "ERP",
|
|
4
|
+
"icons": [
|
|
5
|
+
{
|
|
6
|
+
"src": "/erp/android-chrome-192x192.png",
|
|
7
|
+
"sizes": "192x192",
|
|
8
|
+
"type": "image/png"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"src": "/erp/android-chrome-512x512.png",
|
|
12
|
+
"sizes": "512x512",
|
|
13
|
+
"type": "image/png"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"theme_color": "#1a1b1e",
|
|
17
|
+
"background_color": "#1a1b1e",
|
|
18
|
+
"display": "standalone",
|
|
19
|
+
"start_url": "/erp/",
|
|
20
|
+
"scope": "/erp/",
|
|
21
|
+
"orientation": "portrait-primary"
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
/**
|
|
3
|
+
* Registers Scalar API reference routes for the ERP service.
|
|
4
|
+
*
|
|
5
|
+
* Can't register @scalar/fastify-api-reference twice in the same process
|
|
6
|
+
* (Fastify deduplicates fp() plugins by name), so we serve the Scalar
|
|
7
|
+
* standalone JS bundle and a minimal HTML page manually.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerApiReference(fastify: FastifyInstance): void;
|
|
10
|
+
//# sourceMappingURL=api-reference.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import path from "path";
|
|
4
|
+
/**
|
|
5
|
+
* Registers Scalar API reference routes for the ERP service.
|
|
6
|
+
*
|
|
7
|
+
* Can't register @scalar/fastify-api-reference twice in the same process
|
|
8
|
+
* (Fastify deduplicates fp() plugins by name), so we serve the Scalar
|
|
9
|
+
* standalone JS bundle and a minimal HTML page manually.
|
|
10
|
+
*/
|
|
11
|
+
export function registerApiReference(fastify) {
|
|
12
|
+
// Serve ERP OpenAPI spec filtered to ERP paths only
|
|
13
|
+
fastify.get("/api/erp/openapi.json", () => {
|
|
14
|
+
const spec = fastify.swagger();
|
|
15
|
+
const filteredPaths = {};
|
|
16
|
+
for (const [p, value] of Object.entries(spec.paths || {})) {
|
|
17
|
+
if (p.startsWith("/api/erp/")) {
|
|
18
|
+
filteredPaths[p] = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
...spec,
|
|
23
|
+
info: {
|
|
24
|
+
title: "NAISYS ERP API",
|
|
25
|
+
description: "AI-first ERP system - Order management and definitions",
|
|
26
|
+
version: "1.0.0",
|
|
27
|
+
},
|
|
28
|
+
paths: filteredPaths,
|
|
29
|
+
"x-tagGroups": [
|
|
30
|
+
{ name: "General", tags: ["Discovery", "Auth", "Users"] },
|
|
31
|
+
{
|
|
32
|
+
name: "Planning",
|
|
33
|
+
tags: [
|
|
34
|
+
"Orders",
|
|
35
|
+
"Order Revisions",
|
|
36
|
+
"Operations",
|
|
37
|
+
"Operation Dependencies",
|
|
38
|
+
"Operation Field Refs",
|
|
39
|
+
"Steps",
|
|
40
|
+
"Step Fields",
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "Execution",
|
|
45
|
+
tags: [
|
|
46
|
+
"Dispatch",
|
|
47
|
+
"Order Runs",
|
|
48
|
+
"Operation Runs",
|
|
49
|
+
"Operation Run Comments",
|
|
50
|
+
"Labor Tickets",
|
|
51
|
+
"Step Runs",
|
|
52
|
+
"Attachments",
|
|
53
|
+
"Work Centers",
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "Inventory",
|
|
58
|
+
tags: ["Items", "Item Fields", "Item Instances", "Inventory"],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "Administration",
|
|
62
|
+
tags: ["Admin", "Audit"],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
// Serve the Scalar standalone JS bundle from the installed package
|
|
68
|
+
const erpRequire = createRequire(import.meta.url);
|
|
69
|
+
const scalarDistDir = path.dirname(erpRequire.resolve("@scalar/fastify-api-reference"));
|
|
70
|
+
const scalarJs = readFileSync(path.join(scalarDistDir, "js/standalone.js"), "utf8");
|
|
71
|
+
fastify.get("/erp/api-reference", { schema: { hide: true } }, async (_request, reply) => {
|
|
72
|
+
return reply.redirect("/erp/api-reference/");
|
|
73
|
+
});
|
|
74
|
+
fastify.get("/erp/api-reference/js/scalar.js", { schema: { hide: true } }, async (_request, reply) => {
|
|
75
|
+
return reply
|
|
76
|
+
.header("Content-Type", "application/javascript; charset=utf-8")
|
|
77
|
+
.send(scalarJs);
|
|
78
|
+
});
|
|
79
|
+
fastify.get("/erp/api-reference/", { schema: { hide: true } }, async (_request, reply) => {
|
|
80
|
+
return reply.header("Content-Type", "text/html; charset=utf-8")
|
|
81
|
+
.send(`<!doctype html>
|
|
82
|
+
<html>
|
|
83
|
+
<head>
|
|
84
|
+
<title>NAISYS ERP API Reference</title>
|
|
85
|
+
<meta charset="utf-8" />
|
|
86
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<div id="app"></div>
|
|
90
|
+
<script src="/erp/api-reference/js/scalar.js"><\/script>
|
|
91
|
+
<script type="text/javascript">
|
|
92
|
+
Scalar.createApiReference('#app', {
|
|
93
|
+
url: "/api/erp/openapi.json",
|
|
94
|
+
theme: "kepler"
|
|
95
|
+
})
|
|
96
|
+
<\/script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=api-reference.js.map
|
package/dist/audit.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PrismaClient } from "./generated/prisma/client.js";
|
|
2
|
+
type PrismaTransaction = Parameters<Parameters<PrismaClient["$transaction"]>[0]>[0];
|
|
3
|
+
export declare function writeAuditEntry(erpTx: PrismaTransaction, entityType: string, entityId: number, action: string, field: string, oldValue: string | null, newValue: string | null, userId: number): Promise<void>;
|
|
4
|
+
export {};
|
|
5
|
+
//# sourceMappingURL=audit.d.ts.map
|
package/dist/audit.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function writeAuditEntry(erpTx, entityType, entityId, action, field, oldValue, newValue, userId) {
|
|
2
|
+
await erpTx.auditLog.create({
|
|
3
|
+
data: {
|
|
4
|
+
entityType,
|
|
5
|
+
entityId,
|
|
6
|
+
action,
|
|
7
|
+
field,
|
|
8
|
+
oldValue,
|
|
9
|
+
newValue,
|
|
10
|
+
userId,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AuthCache } from "@naisys/common";
|
|
2
|
+
import type { ErpPermission } from "@naisys/erp-shared";
|
|
3
|
+
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
|
4
|
+
export interface ErpUser {
|
|
5
|
+
id: number;
|
|
6
|
+
username: string;
|
|
7
|
+
permissions: ErpPermission[];
|
|
8
|
+
}
|
|
9
|
+
declare module "fastify" {
|
|
10
|
+
interface FastifyRequest {
|
|
11
|
+
erpUser?: ErpUser;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export declare const authCache: AuthCache<ErpUser>;
|
|
15
|
+
export declare function hasPermission(user: ErpUser | undefined, permission: ErpPermission): boolean;
|
|
16
|
+
export declare function requirePermission(permission: ErpPermission): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
17
|
+
export declare function registerAuthMiddleware(fastify: FastifyInstance): void;
|
|
18
|
+
//# sourceMappingURL=auth-middleware.d.ts.map
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { AuthCache } from "@naisys/common";
|
|
2
|
+
import { extractBearerToken, hashToken, SESSION_COOKIE_NAME, } from "@naisys/common-node";
|
|
3
|
+
import { findAgentByApiKey } from "@naisys/hub-database";
|
|
4
|
+
import { findSession, findUserByApiKey } from "@naisys/supervisor-database";
|
|
5
|
+
import erpDb from "./erpDb.js";
|
|
6
|
+
import { isSupervisorAuth } from "./supervisorAuth.js";
|
|
7
|
+
const PUBLIC_PREFIXES = ["/api/erp/auth/login", "/api/erp/client-config"];
|
|
8
|
+
export const authCache = new AuthCache();
|
|
9
|
+
async function loadPermissions(userId) {
|
|
10
|
+
const perms = await erpDb.userPermission.findMany({
|
|
11
|
+
where: { userId },
|
|
12
|
+
select: { permission: true },
|
|
13
|
+
});
|
|
14
|
+
return perms.map((p) => p.permission);
|
|
15
|
+
}
|
|
16
|
+
export function hasPermission(user, permission) {
|
|
17
|
+
if (!user)
|
|
18
|
+
return false;
|
|
19
|
+
return (user.permissions.includes(permission) ||
|
|
20
|
+
user.permissions.includes("erp_admin"));
|
|
21
|
+
}
|
|
22
|
+
export function requirePermission(permission) {
|
|
23
|
+
return async (request, reply) => {
|
|
24
|
+
if (!request.erpUser) {
|
|
25
|
+
reply.status(401).send({
|
|
26
|
+
statusCode: 401,
|
|
27
|
+
error: "Unauthorized",
|
|
28
|
+
message: "Authentication required",
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!hasPermission(request.erpUser, permission)) {
|
|
33
|
+
reply.status(403).send({
|
|
34
|
+
statusCode: 403,
|
|
35
|
+
error: "Forbidden",
|
|
36
|
+
message: `Permission '${permission}' required`,
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function isPublicRoute(url) {
|
|
43
|
+
// Exact match: API root
|
|
44
|
+
if (url === "/api/erp/" || url === "/api/erp")
|
|
45
|
+
return true;
|
|
46
|
+
// Prefix matches
|
|
47
|
+
for (const prefix of PUBLIC_PREFIXES) {
|
|
48
|
+
if (url.startsWith(prefix))
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
// Schema routes
|
|
52
|
+
if (url.startsWith("/api/erp/schemas"))
|
|
53
|
+
return true;
|
|
54
|
+
// Non-ERP-API paths (static files, supervisor routes, etc.)
|
|
55
|
+
if (!url.startsWith("/api/erp"))
|
|
56
|
+
return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
export function registerAuthMiddleware(fastify) {
|
|
60
|
+
const publicRead = process.env.PUBLIC_READ === "true";
|
|
61
|
+
fastify.decorateRequest("erpUser", undefined);
|
|
62
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
63
|
+
const token = request.cookies?.[SESSION_COOKIE_NAME];
|
|
64
|
+
if (token) {
|
|
65
|
+
const tokenHash = hashToken(token);
|
|
66
|
+
const cacheKey = `cookie:${tokenHash}`;
|
|
67
|
+
const cached = authCache.get(cacheKey);
|
|
68
|
+
if (cached !== undefined) {
|
|
69
|
+
// Cache hit (valid or negative)
|
|
70
|
+
if (cached)
|
|
71
|
+
request.erpUser = cached;
|
|
72
|
+
}
|
|
73
|
+
else if (isSupervisorAuth()) {
|
|
74
|
+
// SSO mode: supervisor DB is source of truth for sessions
|
|
75
|
+
const session = await findSession(tokenHash);
|
|
76
|
+
if (session) {
|
|
77
|
+
let localUser = await erpDb.user.findUnique({
|
|
78
|
+
where: { uuid: session.uuid },
|
|
79
|
+
});
|
|
80
|
+
if (!localUser) {
|
|
81
|
+
localUser = await erpDb.user.create({
|
|
82
|
+
data: {
|
|
83
|
+
uuid: session.uuid,
|
|
84
|
+
username: session.username,
|
|
85
|
+
passwordHash: session.passwordHash,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const permissions = await loadPermissions(localUser.id);
|
|
90
|
+
const erpUser = {
|
|
91
|
+
id: localUser.id,
|
|
92
|
+
username: localUser.username,
|
|
93
|
+
permissions,
|
|
94
|
+
};
|
|
95
|
+
authCache.set(cacheKey, erpUser);
|
|
96
|
+
request.erpUser = erpUser;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
authCache.set(cacheKey, null);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Standalone mode: local session only
|
|
104
|
+
const session = await erpDb.session.findUnique({
|
|
105
|
+
where: {
|
|
106
|
+
tokenHash,
|
|
107
|
+
expiresAt: { gt: new Date() },
|
|
108
|
+
},
|
|
109
|
+
include: { user: true },
|
|
110
|
+
});
|
|
111
|
+
if (session) {
|
|
112
|
+
const permissions = await loadPermissions(session.user.id);
|
|
113
|
+
const erpUser = {
|
|
114
|
+
id: session.user.id,
|
|
115
|
+
username: session.user.username,
|
|
116
|
+
permissions,
|
|
117
|
+
};
|
|
118
|
+
authCache.set(cacheKey, erpUser);
|
|
119
|
+
request.erpUser = erpUser;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
authCache.set(cacheKey, null);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// API key auth (for agents / machine-to-machine)
|
|
127
|
+
if (!request.erpUser) {
|
|
128
|
+
const apiKey = extractBearerToken(request.headers.authorization);
|
|
129
|
+
if (apiKey) {
|
|
130
|
+
const apiKeyHash = hashToken(apiKey);
|
|
131
|
+
const cacheKey = `apikey:${apiKeyHash}`;
|
|
132
|
+
const cached = authCache.get(cacheKey);
|
|
133
|
+
if (cached !== undefined) {
|
|
134
|
+
if (cached)
|
|
135
|
+
request.erpUser = cached;
|
|
136
|
+
}
|
|
137
|
+
else if (isSupervisorAuth()) {
|
|
138
|
+
// SSO mode: try supervisor DB (human users), then hub DB (agents)
|
|
139
|
+
const match = (await findUserByApiKey(apiKey)) ??
|
|
140
|
+
(await findAgentByApiKey(apiKey));
|
|
141
|
+
if (match) {
|
|
142
|
+
let localUser = await erpDb.user.findUnique({
|
|
143
|
+
where: { uuid: match.uuid },
|
|
144
|
+
});
|
|
145
|
+
if (!localUser) {
|
|
146
|
+
localUser = await erpDb.user.create({
|
|
147
|
+
data: {
|
|
148
|
+
uuid: match.uuid,
|
|
149
|
+
username: match.username,
|
|
150
|
+
passwordHash: "!api-key-only",
|
|
151
|
+
isAgent: true,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const permissions = await loadPermissions(localUser.id);
|
|
156
|
+
const erpUser = {
|
|
157
|
+
id: localUser.id,
|
|
158
|
+
username: localUser.username,
|
|
159
|
+
permissions,
|
|
160
|
+
};
|
|
161
|
+
authCache.set(cacheKey, erpUser);
|
|
162
|
+
request.erpUser = erpUser;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
authCache.set(cacheKey, null);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// Standalone mode: check local ERP user table
|
|
170
|
+
const localUser = await erpDb.user.findUnique({
|
|
171
|
+
where: { apiKey },
|
|
172
|
+
});
|
|
173
|
+
if (localUser) {
|
|
174
|
+
const permissions = await loadPermissions(localUser.id);
|
|
175
|
+
const erpUser = {
|
|
176
|
+
id: localUser.id,
|
|
177
|
+
username: localUser.username,
|
|
178
|
+
permissions,
|
|
179
|
+
};
|
|
180
|
+
authCache.set(cacheKey, erpUser);
|
|
181
|
+
request.erpUser = erpUser;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
authCache.set(cacheKey, null);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Check if auth is required
|
|
190
|
+
if (request.erpUser)
|
|
191
|
+
return; // Authenticated, always allowed
|
|
192
|
+
if (isPublicRoute(request.url))
|
|
193
|
+
return; // Public route
|
|
194
|
+
if (publicRead && request.method === "GET")
|
|
195
|
+
return; // Public read mode
|
|
196
|
+
reply.status(401).send({
|
|
197
|
+
statusCode: 401,
|
|
198
|
+
error: "Unauthorized",
|
|
199
|
+
message: "Authentication required",
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=auth-middleware.js.map
|
package/dist/dbConfig.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
export function erpDbPath() {
|
|
3
|
+
return join(process.env.NAISYS_FOLDER || "", "database", "naisys_erp.db");
|
|
4
|
+
}
|
|
5
|
+
export function erpDbUrl() {
|
|
6
|
+
return "file:" + erpDbPath();
|
|
7
|
+
}
|
|
8
|
+
/** We run migration scripts if this is greater than what's in the schema_version table */
|
|
9
|
+
export const ERP_DB_VERSION = 43;
|
|
10
|
+
//# sourceMappingURL=dbConfig.js.map
|
package/dist/erpDb.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PrismaClient } from "./generated/prisma/client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Initialize the ERP database connection and run SQLite pragmas.
|
|
4
|
+
* Must be called after the database directory/file exists (i.e. after migrations).
|
|
5
|
+
*/
|
|
6
|
+
export declare function initErpDb(): Promise<void>;
|
|
7
|
+
/** Lazily initialized Prisma client. First access creates the connection. */
|
|
8
|
+
declare const erpDb: PrismaClient;
|
|
9
|
+
export default erpDb;
|
|
10
|
+
//# sourceMappingURL=erpDb.d.ts.map
|
package/dist/erpDb.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
|
|
2
|
+
import { erpDbUrl } from "./dbConfig.js";
|
|
3
|
+
import { PrismaClient } from "./generated/prisma/client.js";
|
|
4
|
+
let _db;
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the ERP database connection and run SQLite pragmas.
|
|
7
|
+
* Must be called after the database directory/file exists (i.e. after migrations).
|
|
8
|
+
*/
|
|
9
|
+
export async function initErpDb() {
|
|
10
|
+
if (_db)
|
|
11
|
+
return;
|
|
12
|
+
const adapter = new PrismaBetterSqlite3({
|
|
13
|
+
url: erpDbUrl(),
|
|
14
|
+
timeout: 10_000, // Wait up to 10s for SQLite lock to be released
|
|
15
|
+
});
|
|
16
|
+
_db = new PrismaClient({ adapter });
|
|
17
|
+
// Enable WAL mode for better concurrent read/write performance
|
|
18
|
+
await _db.$executeRawUnsafe("PRAGMA journal_mode=WAL");
|
|
19
|
+
// NORMAL is safe with WAL and avoids an extra fsync per commit
|
|
20
|
+
await _db.$executeRawUnsafe("PRAGMA synchronous=NORMAL");
|
|
21
|
+
// SQLite doesn't enforce foreign keys by default — must be enabled per connection
|
|
22
|
+
await _db.$executeRawUnsafe("PRAGMA foreign_keys=ON");
|
|
23
|
+
}
|
|
24
|
+
/** Lazily initialized Prisma client. First access creates the connection. */
|
|
25
|
+
const erpDb = new Proxy({}, {
|
|
26
|
+
get(_target, prop, receiver) {
|
|
27
|
+
if (!_db) {
|
|
28
|
+
throw new Error("erpDb accessed before initialization. Ensure migrations have completed first.");
|
|
29
|
+
}
|
|
30
|
+
return Reflect.get(_db, prop, receiver);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
export default erpDb;
|
|
34
|
+
//# sourceMappingURL=erpDb.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import "./schema-registry.js";
|
|
3
|
+
import { type FastifyPluginAsync } from "fastify";
|
|
4
|
+
export { enableSupervisorAuth } from "./supervisorAuth.js";
|
|
5
|
+
/**
|
|
6
|
+
* Fastify plugin that registers ERP routes and static files.
|
|
7
|
+
* Can be used standalone or registered inside another Fastify app (e.g. supervisor).
|
|
8
|
+
*/
|
|
9
|
+
export declare const erpPlugin: FastifyPluginAsync;
|
|
10
|
+
//# sourceMappingURL=erpServer.d.ts.map
|