@naisys/erp 3.0.0-beta.9 → 3.0.1

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.
Files changed (83) hide show
  1. package/.env.example +5 -0
  2. package/client-dist/assets/index-CSiMTJfw.css +14 -0
  3. package/client-dist/assets/index-D5R6NBeW.js +11177 -0
  4. package/client-dist/assets/rolldown-runtime-B-1-B7_t.js +33 -0
  5. package/client-dist/assets/vendor-CbiEATh3.js +73851 -0
  6. package/client-dist/assets/vendor-D8d_HnwE.css +9604 -0
  7. package/client-dist/favicon-16x16.png +0 -0
  8. package/client-dist/favicon-32x32.png +0 -0
  9. package/client-dist/favicon.ico +0 -0
  10. package/client-dist/index.html +17 -2
  11. package/dist/{dbConfig.js → database/dbConfig.js} +1 -1
  12. package/dist/{erpDb.js → database/erpDb.js} +1 -1
  13. package/dist/erpRoutes.js +115 -0
  14. package/dist/erpServer.js +92 -162
  15. package/dist/error-handler.js +3 -0
  16. package/dist/generated/prisma/internal/class.js +6 -6
  17. package/dist/generated/prisma/internal/prismaNamespace.js +7 -5
  18. package/dist/middleware/auth-middleware.js +146 -0
  19. package/dist/route-helpers.js +2 -2
  20. package/dist/routes/admin.js +15 -7
  21. package/dist/routes/audit.js +1 -1
  22. package/dist/routes/{item-fields.js → items/item-fields.js} +24 -22
  23. package/dist/routes/{item-instances.js → items/item-instances.js} +42 -24
  24. package/dist/routes/{items.js → items/items.js} +35 -33
  25. package/dist/routes/{operation-dependencies.js → operations/operation-dependencies.js} +6 -6
  26. package/dist/routes/{operation-field-refs.js → operations/operation-field-refs.js} +6 -6
  27. package/dist/routes/{operation-run-comments.js → operations/operation-run-comments.js} +5 -5
  28. package/dist/routes/{operation-run-transitions.js → operations/operation-run-transitions.js} +29 -13
  29. package/dist/routes/{operation-runs.js → operations/operation-runs.js} +48 -10
  30. package/dist/routes/{operations.js → operations/operations.js} +6 -6
  31. package/dist/routes/{order-revision-transitions.js → orders/order-revision-transitions.js} +4 -4
  32. package/dist/routes/{order-revisions.js → orders/order-revisions.js} +6 -6
  33. package/dist/routes/{order-run-transitions.js → orders/order-run-transitions.js} +11 -5
  34. package/dist/routes/{order-runs.js → orders/order-runs.js} +7 -5
  35. package/dist/routes/{orders.js → orders/orders.js} +15 -11
  36. package/dist/routes/{dispatch.js → production/dispatch.js} +88 -7
  37. package/dist/routes/{inventory.js → production/inventory.js} +33 -10
  38. package/dist/routes/{labor-tickets.js → production/labor-tickets.js} +7 -7
  39. package/dist/routes/{work-centers.js → production/work-centers.js} +29 -29
  40. package/dist/routes/root.js +1 -1
  41. package/dist/routes/{step-field-attachments.js → steps/step-field-attachments.js} +8 -8
  42. package/dist/routes/{step-fields.js → steps/step-fields.js} +6 -6
  43. package/dist/routes/{step-run-fields.js → steps/step-run-fields.js} +9 -9
  44. package/dist/routes/{step-run-transitions.js → steps/step-run-transitions.js} +6 -6
  45. package/dist/routes/{step-runs.js → steps/step-runs.js} +7 -7
  46. package/dist/routes/{steps.js → steps/steps.js} +5 -5
  47. package/dist/routes/{auth.js → users/auth.js} +11 -23
  48. package/dist/routes/{user-permissions.js → users/user-permissions.js} +21 -7
  49. package/dist/routes/{users.js → users/users.js} +42 -20
  50. package/dist/services/attachment-service.js +2 -2
  51. package/dist/services/{item-instance-service.js → inventory/item-instance-service.js} +2 -2
  52. package/dist/services/{item-service.js → inventory/item-service.js} +2 -2
  53. package/dist/services/{operation-dependency-service.js → operations/operation-dependency-service.js} +1 -1
  54. package/dist/services/{operation-run-comment-service.js → operations/operation-run-comment-service.js} +1 -1
  55. package/dist/services/{operation-run-service.js → operations/operation-run-service.js} +15 -4
  56. package/dist/services/{operation-service.js → operations/operation-service.js} +2 -2
  57. package/dist/services/{step-run-service.js → operations/step-run-service.js} +1 -1
  58. package/dist/services/{step-service.js → operations/step-service.js} +2 -2
  59. package/dist/services/{order-revision-service.js → orders/order-revision-service.js} +4 -5
  60. package/dist/services/{order-run-service.js → orders/order-run-service.js} +68 -22
  61. package/dist/services/{order-service.js → orders/order-service.js} +11 -2
  62. package/dist/services/{revision-diff-service.js → orders/revision-diff-service.js} +11 -10
  63. package/dist/services/{field-ref-service.js → production/field-ref-service.js} +1 -1
  64. package/dist/services/{field-service.js → production/field-service.js} +2 -2
  65. package/dist/services/{field-value-service.js → production/field-value-service.js} +27 -3
  66. package/dist/services/production/labor-ticket-backfill.js +67 -0
  67. package/dist/services/{labor-ticket-service.js → production/labor-ticket-service.js} +21 -15
  68. package/dist/services/{work-center-service.js → production/work-center-service.js} +2 -2
  69. package/dist/services/user-service.js +94 -28
  70. package/dist/version.js +12 -0
  71. package/npm-shrinkwrap.json +2941 -0
  72. package/package.json +26 -24
  73. package/prisma/migrations/20260427010000_hash_user_api_keys/migration.sql +10 -0
  74. package/prisma/migrations/20260427020000_nullable_user_password_hash/migration.sql +39 -0
  75. package/prisma/migrations/20260517000000_add_op_run_tokens/migration.sql +2 -0
  76. package/prisma/schema.prisma +4 -2
  77. package/client-dist/assets/index-45dVo30p.css +0 -1
  78. package/client-dist/assets/index-C9uuPHLH.js +0 -168
  79. package/dist/auth-middleware.js +0 -203
  80. package/dist/userService.js +0 -118
  81. /package/bin/{naisys-erp → naisys-erp.js} +0 -0
  82. /package/dist/{supervisorAuth.js → middleware/supervisorAuth.js} +0 -0
  83. /package/dist/{audit.js → services/audit.js} +0 -0
Binary file
Binary file
Binary file
@@ -4,6 +4,18 @@
4
4
  <meta charset="UTF-8" />
5
5
 
6
6
  <link rel="icon" type="image/x-icon" href="/erp/favicon.ico" />
7
+ <link
8
+ rel="icon"
9
+ type="image/png"
10
+ sizes="32x32"
11
+ href="/erp/favicon-32x32.png"
12
+ />
13
+ <link
14
+ rel="icon"
15
+ type="image/png"
16
+ sizes="16x16"
17
+ href="/erp/favicon-16x16.png"
18
+ />
7
19
  <link
8
20
  rel="apple-touch-icon"
9
21
  sizes="180x180"
@@ -33,8 +45,11 @@
33
45
  <meta name="format-detection" content="telephone=no" />
34
46
 
35
47
  <title>NAISYS ERP</title>
36
- <script type="module" crossorigin src="/erp/assets/index-C9uuPHLH.js"></script>
37
- <link rel="stylesheet" crossorigin href="/erp/assets/index-45dVo30p.css">
48
+ <script type="module" crossorigin src="/erp/assets/index-D5R6NBeW.js"></script>
49
+ <link rel="modulepreload" crossorigin href="/erp/assets/rolldown-runtime-B-1-B7_t.js">
50
+ <link rel="modulepreload" crossorigin href="/erp/assets/vendor-CbiEATh3.js">
51
+ <link rel="stylesheet" crossorigin href="/erp/assets/vendor-D8d_HnwE.css">
52
+ <link rel="stylesheet" crossorigin href="/erp/assets/index-CSiMTJfw.css">
38
53
  </head>
39
54
  <body>
40
55
  <div id="root"></div>
@@ -6,5 +6,5 @@ export function erpDbUrl() {
6
6
  return "file:" + erpDbPath();
7
7
  }
8
8
  /** We run migration scripts if this is greater than what's in the schema_version table */
9
- export const ERP_DB_VERSION = 43;
9
+ export const ERP_DB_VERSION = 45;
10
10
  //# sourceMappingURL=dbConfig.js.map
@@ -1,6 +1,6 @@
1
1
  import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
2
+ import { PrismaClient } from "../generated/prisma/client.js";
2
3
  import { erpDbUrl } from "./dbConfig.js";
3
- import { PrismaClient } from "./generated/prisma/client.js";
4
4
  let _db;
5
5
  /**
6
6
  * Initialize the ERP database connection and run SQLite pragmas.
@@ -0,0 +1,115 @@
1
+ import { isSupervisorAuth } from "./middleware/supervisorAuth.js";
2
+ import adminRoutes from "./routes/admin.js";
3
+ import auditRoutes from "./routes/audit.js";
4
+ import itemFieldRoutes from "./routes/items/item-fields.js";
5
+ import itemInstanceRoutes from "./routes/items/item-instances.js";
6
+ import itemRoutes from "./routes/items/items.js";
7
+ import operationDependencyRoutes from "./routes/operations/operation-dependencies.js";
8
+ import operationFieldRefRoutes from "./routes/operations/operation-field-refs.js";
9
+ import operationRunCommentRoutes from "./routes/operations/operation-run-comments.js";
10
+ import operationRunTransitionRoutes from "./routes/operations/operation-run-transitions.js";
11
+ import operationRunRoutes from "./routes/operations/operation-runs.js";
12
+ import operationRoutes from "./routes/operations/operations.js";
13
+ import orderRevisionTransitionRoutes from "./routes/orders/order-revision-transitions.js";
14
+ import orderRevisionRoutes from "./routes/orders/order-revisions.js";
15
+ import orderRunTransitionRoutes from "./routes/orders/order-run-transitions.js";
16
+ import orderRunRoutes from "./routes/orders/order-runs.js";
17
+ import orderRoutes from "./routes/orders/orders.js";
18
+ import dispatchRoutes from "./routes/production/dispatch.js";
19
+ import inventoryRoutes from "./routes/production/inventory.js";
20
+ import laborTicketRoutes from "./routes/production/labor-tickets.js";
21
+ import workCenterRoutes from "./routes/production/work-centers.js";
22
+ import rootRoute from "./routes/root.js";
23
+ import schemaRoutes from "./routes/schemas.js";
24
+ import stepFieldAttachmentRoutes from "./routes/steps/step-field-attachments.js";
25
+ import stepFieldRoutes from "./routes/steps/step-fields.js";
26
+ import stepRunFieldRoutes from "./routes/steps/step-run-fields.js";
27
+ import stepRunTransitionRoutes from "./routes/steps/step-run-transitions.js";
28
+ import stepRunRoutes from "./routes/steps/step-runs.js";
29
+ import stepRoutes from "./routes/steps/steps.js";
30
+ import authRoutes from "./routes/users/auth.js";
31
+ import userPermissionRoutes from "./routes/users/user-permissions.js";
32
+ import userRoutes from "./routes/users/users.js";
33
+ export const erpRoutes = (fastify) => {
34
+ fastify.register(adminRoutes, { prefix: "/erp/api/admin" });
35
+ fastify.register(auditRoutes, { prefix: "/erp/api/audit" });
36
+ fastify.register(authRoutes, { prefix: "/erp/api/auth" });
37
+ fastify.register(dispatchRoutes, { prefix: "/erp/api/dispatch" });
38
+ fastify.register(inventoryRoutes, { prefix: "/erp/api/inventory" });
39
+ fastify.register(rootRoute, { prefix: "/erp/api" });
40
+ fastify.register(itemRoutes, { prefix: "/erp/api/items" });
41
+ fastify.register(itemFieldRoutes, {
42
+ prefix: "/erp/api/items/:key/fields",
43
+ });
44
+ fastify.register(itemInstanceRoutes, {
45
+ prefix: "/erp/api/items/:key/instances",
46
+ });
47
+ fastify.register(orderRoutes, {
48
+ prefix: "/erp/api/orders",
49
+ });
50
+ fastify.register(orderRevisionRoutes, {
51
+ prefix: "/erp/api/orders/:orderKey/revs",
52
+ });
53
+ fastify.register(orderRevisionTransitionRoutes, {
54
+ prefix: "/erp/api/orders/:orderKey/revs",
55
+ });
56
+ fastify.register(orderRunRoutes, {
57
+ prefix: "/erp/api/orders/:orderKey/runs",
58
+ });
59
+ fastify.register(orderRunTransitionRoutes, {
60
+ prefix: "/erp/api/orders/:orderKey/runs",
61
+ });
62
+ fastify.register(operationRoutes, {
63
+ prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops",
64
+ });
65
+ fastify.register(operationDependencyRoutes, {
66
+ prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops/:seqNo/deps",
67
+ });
68
+ fastify.register(operationFieldRefRoutes, {
69
+ prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops/:seqNo/field-refs",
70
+ });
71
+ fastify.register(operationRunRoutes, {
72
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops",
73
+ });
74
+ fastify.register(operationRunTransitionRoutes, {
75
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops",
76
+ });
77
+ fastify.register(laborTicketRoutes, {
78
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/labor",
79
+ });
80
+ fastify.register(operationRunCommentRoutes, {
81
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/comments",
82
+ });
83
+ fastify.register(stepRunRoutes, {
84
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps",
85
+ });
86
+ fastify.register(stepRunTransitionRoutes, {
87
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps",
88
+ });
89
+ fastify.register(stepRunFieldRoutes, {
90
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps",
91
+ });
92
+ fastify.register(stepFieldAttachmentRoutes, {
93
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps/:stepSeqNo/fields/:fieldSeqNo/attachments",
94
+ });
95
+ fastify.register(stepFieldAttachmentRoutes, {
96
+ prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps/:stepSeqNo/sets/:setIndex/fields/:fieldSeqNo/attachments",
97
+ });
98
+ fastify.register(stepRoutes, {
99
+ prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops/:seqNo/steps",
100
+ });
101
+ fastify.register(stepFieldRoutes, {
102
+ prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops/:seqNo/steps/:stepSeqNo/fields",
103
+ });
104
+ fastify.register(schemaRoutes, { prefix: "/erp/api/schemas" });
105
+ fastify.register(userRoutes, { prefix: "/erp/api/users" });
106
+ fastify.register(userPermissionRoutes, { prefix: "/erp/api/users" });
107
+ fastify.register(workCenterRoutes, { prefix: "/erp/api/work-centers" });
108
+ // Public endpoint to expose client configuration
109
+ fastify.get("/erp/api/client-config", { schema: { hide: true } }, () => ({
110
+ publicRead: process.env.PUBLIC_READ === "true",
111
+ supervisorAuth: isSupervisorAuth(),
112
+ }));
113
+ return Promise.resolve();
114
+ };
115
+ //# sourceMappingURL=erpRoutes.js.map
package/dist/erpServer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "dotenv/config";
2
2
  import "./schema-registry.js";
3
- import { expandNaisysFolder } from "@naisys/common-node";
3
+ import { cwdWithTilde, ensureDotEnv, expandNaisysFolder, promptSuperAdminPassword, runSetupWizard, } from "@naisys/common-node";
4
4
  expandNaisysFolder();
5
5
  // Important to load dotenv before any other imports, to ensure environment variables are available
6
6
  import cookie from "@fastify/cookie";
@@ -9,59 +9,31 @@ import multipart from "@fastify/multipart";
9
9
  import { fastifyRateLimit as rateLimit } from "@fastify/rate-limit";
10
10
  import staticFiles from "@fastify/static";
11
11
  import swagger from "@fastify/swagger";
12
- import { commonErrorHandler, registerLenientJsonParser, registerSecurityHeaders, } from "@naisys/common";
12
+ import { commonErrorHandler, registerLenientJsonParser, registerSecurityHeaders, SUPER_ADMIN_USERNAME, } from "@naisys/common";
13
+ import { createFileLogger } from "@naisys/common-node";
13
14
  import { createHubDatabaseClient, deployPrismaMigrations, } from "@naisys/hub-database";
14
- import { createSupervisorDatabaseClient, handleResetPassword, } from "@naisys/supervisor-database";
15
+ import { createSupervisorDatabaseClient } from "@naisys/supervisor-database";
15
16
  import Fastify from "fastify";
16
17
  import { jsonSchemaTransform, jsonSchemaTransformObject, serializerCompiler, validatorCompiler, } from "fastify-type-provider-zod";
17
18
  import path from "path";
18
- import pino from "pino";
19
19
  import { fileURLToPath } from "url";
20
+ import { takeCoverage } from "v8";
20
21
  import { registerApiReference } from "./api-reference.js";
21
- import { registerAuthMiddleware } from "./auth-middleware.js";
22
- import { ERP_DB_VERSION, erpDbPath } from "./dbConfig.js";
23
- import { initErpDb } from "./erpDb.js";
24
- import adminRoutes from "./routes/admin.js";
25
- import auditRoutes from "./routes/audit.js";
26
- import authRoutes from "./routes/auth.js";
27
- import dispatchRoutes from "./routes/dispatch.js";
28
- import inventoryRoutes from "./routes/inventory.js";
29
- import itemFieldRoutes from "./routes/item-fields.js";
30
- import itemInstanceRoutes from "./routes/item-instances.js";
31
- import itemRoutes from "./routes/items.js";
32
- import laborTicketRoutes from "./routes/labor-tickets.js";
33
- import operationDependencyRoutes from "./routes/operation-dependencies.js";
34
- import operationFieldRefRoutes from "./routes/operation-field-refs.js";
35
- import operationRunCommentRoutes from "./routes/operation-run-comments.js";
36
- import operationRunTransitionRoutes from "./routes/operation-run-transitions.js";
37
- import operationRunRoutes from "./routes/operation-runs.js";
38
- import operationRoutes from "./routes/operations.js";
39
- import orderRevisionTransitionRoutes from "./routes/order-revision-transitions.js";
40
- import orderRevisionRoutes from "./routes/order-revisions.js";
41
- import orderRunTransitionRoutes from "./routes/order-run-transitions.js";
42
- import orderRunRoutes from "./routes/order-runs.js";
43
- import orderRoutes from "./routes/orders.js";
44
- import rootRoute from "./routes/root.js";
45
- import schemaRoutes from "./routes/schemas.js";
46
- import stepFieldAttachmentRoutes from "./routes/step-field-attachments.js";
47
- import stepFieldRoutes from "./routes/step-fields.js";
48
- import stepRunFieldRoutes from "./routes/step-run-fields.js";
49
- import stepRunTransitionRoutes from "./routes/step-run-transitions.js";
50
- import stepRunRoutes from "./routes/step-runs.js";
51
- import stepRoutes from "./routes/steps.js";
52
- import userPermissionRoutes from "./routes/user-permissions.js";
53
- import userRoutes from "./routes/users.js";
54
- import workCenterRoutes from "./routes/work-centers.js";
55
- import { isSupervisorAuth } from "./supervisorAuth.js";
56
- import { ensureLocalSuperAdmin, ensureSupervisorSuperAdmin, resetLocalPassword, } from "./userService.js";
57
- export { enableSupervisorAuth } from "./supervisorAuth.js";
22
+ import { ERP_DB_VERSION, erpDbPath } from "./database/dbConfig.js";
23
+ import { initErpDb } from "./database/erpDb.js";
24
+ import { erpRoutes } from "./erpRoutes.js";
25
+ import { registerAuthMiddleware } from "./middleware/auth-middleware.js";
26
+ import { isSupervisorAuth } from "./middleware/supervisorAuth.js";
27
+ import { backfillOpRunTokens } from "./services/production/labor-ticket-backfill.js";
28
+ import { ensureLocalSuperAdmin, ensureSupervisorSuperAdmin, } from "./services/user-service.js";
29
+ export { enableSupervisorAuth } from "./middleware/supervisorAuth.js";
58
30
  const __filename = fileURLToPath(import.meta.url);
59
31
  const __dirname = path.dirname(__filename);
60
32
  /**
61
33
  * Fastify plugin that registers ERP routes and static files.
62
34
  * Can be used standalone or registered inside another Fastify app (e.g. supervisor).
63
35
  */
64
- export const erpPlugin = async (fastify) => {
36
+ export const erpPlugin = async (fastify, opts) => {
65
37
  const isProd = process.env.NODE_ENV === "production";
66
38
  // Cookie plugin (guard for supervisor embedding)
67
39
  if (!fastify.hasDecorator("parseCookie")) {
@@ -73,7 +45,7 @@ export const erpPlugin = async (fastify) => {
73
45
  }
74
46
  // Rate limiting — moderate global default, strict overrides on sensitive routes
75
47
  await fastify.register(rateLimit, {
76
- max: 500,
48
+ max: Number(process.env.ERP_API_RATE_LIMIT) || 500,
77
49
  timeWindow: "1 minute",
78
50
  allowList: (request) => !request.url.match(/^\/(supervisor|erp)\/api\//),
79
51
  });
@@ -94,17 +66,17 @@ export const erpPlugin = async (fastify) => {
94
66
  throw new Error("[ERP] Supervisor database not available. Required for supervisor auth.");
95
67
  }
96
68
  await ensureSupervisorSuperAdmin();
69
+ await backfillOpRunTokens();
97
70
  }
98
71
  else {
99
- await ensureLocalSuperAdmin();
72
+ await ensureLocalSuperAdmin(opts.superAdminPassword);
100
73
  }
101
74
  fastify.setErrorHandler(commonErrorHandler);
102
75
  registerAuthMiddleware(fastify);
103
76
  // ERP-specific file logger (works in both standalone and hosted mode)
104
77
  const naisysFolder = process.env.NAISYS_FOLDER;
105
78
  if (naisysFolder) {
106
- const erpLogDest = path.join(naisysFolder, "logs", "erp.log");
107
- const erpFileLogger = pino({ level: "info" }, pino.destination({ dest: erpLogDest, mkdir: true }));
79
+ const erpFileLogger = createFileLogger("erp.log");
108
80
  erpFileLogger.info("ERP plugin initialized");
109
81
  fastify.addHook("onResponse", async (request, reply) => {
110
82
  if (!request.url.startsWith("/erp/api"))
@@ -123,86 +95,8 @@ export const erpPlugin = async (fastify) => {
123
95
  }, `${request.method} ${request.url} error`);
124
96
  });
125
97
  }
126
- // API routes under /erp/api prefix
127
- fastify.register(adminRoutes, { prefix: "/erp/api/admin" });
128
- fastify.register(auditRoutes, { prefix: "/erp/api/audit" });
129
- fastify.register(authRoutes, { prefix: "/erp/api/auth" });
130
- fastify.register(dispatchRoutes, { prefix: "/erp/api/dispatch" });
131
- fastify.register(inventoryRoutes, { prefix: "/erp/api/inventory" });
132
- fastify.register(rootRoute, { prefix: "/erp/api" });
133
- fastify.register(itemRoutes, { prefix: "/erp/api/items" });
134
- fastify.register(itemFieldRoutes, {
135
- prefix: "/erp/api/items/:key/fields",
136
- });
137
- fastify.register(itemInstanceRoutes, {
138
- prefix: "/erp/api/items/:key/instances",
139
- });
140
- fastify.register(orderRoutes, {
141
- prefix: "/erp/api/orders",
142
- });
143
- fastify.register(orderRevisionRoutes, {
144
- prefix: "/erp/api/orders/:orderKey/revs",
145
- });
146
- fastify.register(orderRevisionTransitionRoutes, {
147
- prefix: "/erp/api/orders/:orderKey/revs",
148
- });
149
- fastify.register(orderRunRoutes, {
150
- prefix: "/erp/api/orders/:orderKey/runs",
151
- });
152
- fastify.register(orderRunTransitionRoutes, {
153
- prefix: "/erp/api/orders/:orderKey/runs",
154
- });
155
- fastify.register(operationRoutes, {
156
- prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops",
157
- });
158
- fastify.register(operationDependencyRoutes, {
159
- prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops/:seqNo/deps",
160
- });
161
- fastify.register(operationFieldRefRoutes, {
162
- prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops/:seqNo/field-refs",
163
- });
164
- fastify.register(operationRunRoutes, {
165
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops",
166
- });
167
- fastify.register(operationRunTransitionRoutes, {
168
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops",
169
- });
170
- fastify.register(laborTicketRoutes, {
171
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/labor",
172
- });
173
- fastify.register(operationRunCommentRoutes, {
174
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/comments",
175
- });
176
- fastify.register(stepRunRoutes, {
177
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps",
178
- });
179
- fastify.register(stepRunTransitionRoutes, {
180
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps",
181
- });
182
- fastify.register(stepRunFieldRoutes, {
183
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps",
184
- });
185
- fastify.register(stepFieldAttachmentRoutes, {
186
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps/:stepSeqNo/fields/:fieldSeqNo/attachments",
187
- });
188
- fastify.register(stepFieldAttachmentRoutes, {
189
- prefix: "/erp/api/orders/:orderKey/runs/:runNo/ops/:seqNo/steps/:stepSeqNo/sets/:setIndex/fields/:fieldSeqNo/attachments",
190
- });
191
- fastify.register(stepRoutes, {
192
- prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops/:seqNo/steps",
193
- });
194
- fastify.register(stepFieldRoutes, {
195
- prefix: "/erp/api/orders/:orderKey/revs/:revNo/ops/:seqNo/steps/:stepSeqNo/fields",
196
- });
197
- fastify.register(schemaRoutes, { prefix: "/erp/api/schemas" });
198
- fastify.register(userRoutes, { prefix: "/erp/api/users" });
199
- fastify.register(userPermissionRoutes, { prefix: "/erp/api/users" });
200
- fastify.register(workCenterRoutes, { prefix: "/erp/api/work-centers" });
201
- // Public endpoint to expose client configuration (publicRead, etc.)
202
- fastify.get("/erp/api/client-config", { schema: { hide: true } }, () => ({
203
- publicRead: process.env.PUBLIC_READ === "true",
204
- supervisorAuth: isSupervisorAuth(),
205
- }));
98
+ // API routes
99
+ await fastify.register(erpRoutes);
206
100
  registerApiReference(fastify);
207
101
  // In production, serve the client build
208
102
  if (isProd) {
@@ -234,10 +128,13 @@ export const erpPlugin = async (fastify) => {
234
128
  });
235
129
  }
236
130
  };
237
- async function startServer() {
131
+ async function startServer(wizardRan) {
238
132
  const isProd = process.env.NODE_ENV === "production";
239
133
  const fastify = Fastify({
240
134
  pluginTimeout: 60_000,
135
+ // trustProxy: TLS terminates at the reverse proxy, so honor X-Forwarded-*
136
+ // headers — otherwise request.protocol reads the internal http hop.
137
+ trustProxy: true,
241
138
  logger: {
242
139
  transport: {
243
140
  target: "pino-pretty",
@@ -249,7 +146,7 @@ async function startServer() {
249
146
  fastify.setSerializerCompiler(serializerCompiler);
250
147
  registerLenientJsonParser(fastify);
251
148
  await fastify.register(cors, {
252
- origin: isProd ? false : ["http://localhost:3202"],
149
+ origin: isProd ? false : ["http://localhost:2202"],
253
150
  credentials: true,
254
151
  });
255
152
  registerSecurityHeaders(fastify, { enforceHsts: isProd });
@@ -268,54 +165,87 @@ async function startServer() {
268
165
  fastify.get("/", { schema: { hide: true } }, async (_request, reply) => {
269
166
  return reply.redirect("/erp/");
270
167
  });
271
- await fastify.register(erpPlugin);
272
- const port = Number(process.env.ERP_PORT) || 3201;
168
+ if (process.env.NODE_V8_COVERAGE) {
169
+ fastify.post("/erp/api/__coverage/flush", { schema: { hide: true } }, () => {
170
+ takeCoverage();
171
+ return { ok: true };
172
+ });
173
+ }
174
+ const superAdminPassword = wizardRan && !isSupervisorAuth()
175
+ ? await promptSuperAdminPassword("ERP Setup")
176
+ : undefined;
177
+ await fastify.register(erpPlugin, { superAdminPassword });
178
+ const port = Number(process.env.SERVER_PORT) || 3302;
273
179
  const host = isProd ? "0.0.0.0" : "localhost";
274
180
  try {
275
181
  await fastify.listen({ port, host });
276
182
  console.log(`[ERP] Running on http://${host}:${port}/erp`);
277
183
  console.log(`[ERP] API Reference: http://${host}:${port}/erp/api-reference`);
278
184
  console.log(`[ERP] Auth mode: ${isSupervisorAuth() ? "supervisor" : "standalone"}`);
185
+ if (!isSupervisorAuth()) {
186
+ console.log(`[ERP] Sign in as '${SUPER_ADMIN_USERNAME}' with the password set during setup. Use --setup to change it.`);
187
+ }
279
188
  }
280
189
  catch (err) {
281
190
  console.error("[ERP] Failed to start:", err);
282
- process.exit(1);
191
+ try {
192
+ await fastify.close();
193
+ }
194
+ catch {
195
+ // Startup already failed; preserve the original error as the cause.
196
+ }
197
+ throw err;
283
198
  }
199
+ return fastify;
284
200
  }
285
201
  // Start server if this file is run directly
286
202
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
287
- if (process.argv.includes("--reset-password")) {
288
- const usernameIdx = process.argv.indexOf("--username");
289
- const passwordIdx = process.argv.indexOf("--password");
290
- const username = usernameIdx !== -1 ? process.argv[usernameIdx + 1] : undefined;
291
- const password = passwordIdx !== -1 ? process.argv[passwordIdx + 1] : undefined;
292
- await initErpDb();
293
- if (isSupervisorAuth()) {
294
- void handleResetPassword({
295
- findLocalUser: async (username) => {
296
- const prisma = (await import("./erpDb.js")).default;
297
- const user = await prisma.user.findUnique({ where: { username } });
298
- return user
299
- ? { id: user.id, username: user.username, uuid: user.uuid }
300
- : null;
301
- },
302
- updateLocalPassword: async (userId, passwordHash) => {
303
- const prisma = (await import("./erpDb.js")).default;
304
- await prisma.user.update({
305
- where: { id: userId },
306
- data: { passwordHash },
307
- });
308
- },
309
- username,
310
- password,
311
- });
312
- }
313
- else {
314
- void resetLocalPassword();
315
- }
203
+ const erpWizardConfig = {
204
+ title: "NAISYS ERP Setup",
205
+ sections: [
206
+ {
207
+ type: "fields",
208
+ comment: "ERP configuration",
209
+ fields: [
210
+ {
211
+ key: "NAISYS_FOLDER",
212
+ label: "NAISYS Data Folder",
213
+ defaultValue: cwdWithTilde(),
214
+ },
215
+ { key: "SERVER_PORT", label: "Server Port" },
216
+ { key: "SUPERVISOR_AUTH", label: "Use Supervisor for Auth" },
217
+ ],
218
+ },
219
+ ],
220
+ };
221
+ const erpExampleUrl = new URL("../.env.example", import.meta.url);
222
+ let wizardRan = false;
223
+ if (process.argv.includes("--setup")) {
224
+ wizardRan = await runSetupWizard(path.resolve(".env"), erpExampleUrl, erpWizardConfig);
225
+ expandNaisysFolder();
316
226
  }
317
- else {
318
- void startServer();
227
+ if (process.env.NAISYS_SKIP_DOTENV_CHECK !== "1") {
228
+ wizardRan =
229
+ (await ensureDotEnv(erpExampleUrl, erpWizardConfig)) || wizardRan;
319
230
  }
231
+ const fastify = await startServer(wizardRan);
232
+ let shuttingDown = false;
233
+ const handleShutdown = async (signal) => {
234
+ if (shuttingDown) {
235
+ console.log("[ERP] Force exit");
236
+ process.exit(1);
237
+ }
238
+ shuttingDown = true;
239
+ console.log(`[ERP] Shutting down (${signal})...`);
240
+ try {
241
+ await fastify.close();
242
+ }
243
+ catch (err) {
244
+ console.error("[ERP] Error during fastify.close():", err);
245
+ }
246
+ process.exit(0);
247
+ };
248
+ process.on("SIGTERM", () => void handleShutdown("SIGTERM"));
249
+ process.on("SIGINT", () => void handleShutdown("SIGINT"));
320
250
  }
321
251
  //# sourceMappingURL=erpServer.js.map
@@ -2,6 +2,9 @@ function send(reply, statusCode, error, message) {
2
2
  reply.status(statusCode);
3
3
  return { statusCode, error, message };
4
4
  }
5
+ export function badRequest(reply, message) {
6
+ return send(reply, 400, "Bad Request", message);
7
+ }
5
8
  export function notFound(reply, message) {
6
9
  return send(reply, 404, "Not Found", message);
7
10
  }