@litusarchieve18/agricore-utils 1.0.12 → 1.0.14
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.
- package/README.md +4 -0
- package/dist/chunk-LGEV4MBQ.mjs +776 -0
- package/dist/constants-DVuhM7rv.d.mts +417 -0
- package/dist/constants-DVuhM7rv.d.ts +417 -0
- package/dist/constants.d.mts +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +848 -0
- package/dist/constants.mjs +102 -0
- package/dist/index.d.mts +16 -417
- package/dist/index.d.ts +16 -417
- package/dist/index.js +515 -312
- package/dist/index.mjs +81 -555
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -30,8 +30,12 @@ __export(index_exports, {
|
|
|
30
30
|
AuditStatus: () => AuditStatus,
|
|
31
31
|
AuditType: () => AuditType,
|
|
32
32
|
CAT: () => CAT,
|
|
33
|
+
CATEGORY_TABS: () => CATEGORY_TABS,
|
|
33
34
|
Converter: () => Converter,
|
|
34
35
|
CropCycleStatus: () => CropCycleStatus,
|
|
36
|
+
DOCUMENT_DISPLAY_INFO: () => DOCUMENT_DISPLAY_INFO,
|
|
37
|
+
DOCUMENT_MENU_MAP: () => DOCUMENT_MENU_MAP,
|
|
38
|
+
DocumentCategory: () => DocumentCategory,
|
|
35
39
|
DocumentType: () => DocumentType,
|
|
36
40
|
ERROR_DICTIONARY: () => ERROR_DICTIONARY,
|
|
37
41
|
EVERYONE: () => EVERYONE,
|
|
@@ -287,7 +291,7 @@ var Converter = {
|
|
|
287
291
|
}
|
|
288
292
|
};
|
|
289
293
|
|
|
290
|
-
// src/
|
|
294
|
+
// src/constants.ts
|
|
291
295
|
var MOD = {
|
|
292
296
|
OPERATIONS: "OPR",
|
|
293
297
|
EQUIPMENT: "EQP",
|
|
@@ -301,7 +305,6 @@ var MOD = {
|
|
|
301
305
|
ALERTS: "ALT",
|
|
302
306
|
INFRA: "INF",
|
|
303
307
|
SESSION: "SES"
|
|
304
|
-
// ← added: used in generateSessionContext but was missing from errorRegistry.js
|
|
305
308
|
};
|
|
306
309
|
var CAT = {
|
|
307
310
|
SUBSCRIPTION: "1",
|
|
@@ -318,43 +321,6 @@ var ACT = {
|
|
|
318
321
|
UPLOAD: "05"
|
|
319
322
|
};
|
|
320
323
|
var SENTINEL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
321
|
-
var AppError = class extends Error {
|
|
322
|
-
status;
|
|
323
|
-
appCode;
|
|
324
|
-
isOperational;
|
|
325
|
-
constructor(status, mod, cat, act, message) {
|
|
326
|
-
super(message);
|
|
327
|
-
this.status = status;
|
|
328
|
-
this.appCode = `${mod}-${status}-${cat}${act}`;
|
|
329
|
-
this.isOperational = true;
|
|
330
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
var ErrorFactory = {
|
|
334
|
-
unauthorized: (mod, msg = "Authentication required") => new AppError(401, mod, CAT.PERMISSION, ACT.GENERIC, msg),
|
|
335
|
-
forbidden: (mod, act, msg = "Access denied") => new AppError(403, mod, CAT.PERMISSION, act, msg),
|
|
336
|
-
badRequest: (mod, act, msg = "Bad request") => new AppError(400, mod, CAT.VALIDATION, act, msg),
|
|
337
|
-
notFound: (mod, act = ACT.GENERIC, msg = "Record not found") => new AppError(404, mod, CAT.VALIDATION, act, msg),
|
|
338
|
-
conflict: (mod, act, msg = "Record already exists") => new AppError(409, mod, CAT.INTEGRITY, act, msg),
|
|
339
|
-
integrity: (mod, act = ACT.GENERIC, msg = "Data integrity violation") => new AppError(409, mod, CAT.INTEGRITY, act, msg),
|
|
340
|
-
subscription: (mod, msg = "Module locked or subscription required") => new AppError(403, mod, CAT.SUBSCRIPTION, ACT.GENERIC, msg),
|
|
341
|
-
internal: (mod, msg = "Internal server error") => new AppError(500, mod, CAT.INTEGRITY, ACT.GENERIC, msg)
|
|
342
|
-
};
|
|
343
|
-
var handleAppErrorResponse = (error, mod = MOD.ADMIN) => {
|
|
344
|
-
if (error instanceof AppError && error.isOperational) {
|
|
345
|
-
return Response.json(
|
|
346
|
-
{ error: { appCode: error.appCode, message: error.message } },
|
|
347
|
-
{ status: error.status }
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
const e = error;
|
|
351
|
-
console.error(`[${mod}] UNEXPECTED ERROR:`, e?.message);
|
|
352
|
-
console.error(`[${mod}] STACK:`, e?.stack);
|
|
353
|
-
return Response.json(
|
|
354
|
-
{ error: { appCode: `${mod}-500-000`, message: "An unexpected server error occurred. Please try again." } },
|
|
355
|
-
{ status: 500 }
|
|
356
|
-
);
|
|
357
|
-
};
|
|
358
324
|
var MODULE_LABELS = {
|
|
359
325
|
[MOD.OPERATIONS]: "Operations",
|
|
360
326
|
[MOD.EQUIPMENT]: "Equipment & Fleet",
|
|
@@ -392,147 +358,6 @@ var ERROR_DICTIONARY = {
|
|
|
392
358
|
"403": { title: "Edit Conflict", text: "This record was recently updated by someone else. Please refresh and try again." },
|
|
393
359
|
"404": { title: "Record In Use", text: "This record cannot be deleted because it is referenced by other data." }
|
|
394
360
|
};
|
|
395
|
-
var FALLBACK = {
|
|
396
|
-
title: "Something Went Wrong",
|
|
397
|
-
text: "An unexpected error occurred. Please try again or contact your administrator."
|
|
398
|
-
};
|
|
399
|
-
var resolveAppError = (appCode) => {
|
|
400
|
-
if (!appCode || typeof appCode !== "string") {
|
|
401
|
-
return { ...FALLBACK, module: "UNK", moduleLabel: "Unknown", status: 500, catAct: "000", isSubscriptionIssue: false, isPermissionIssue: false };
|
|
402
|
-
}
|
|
403
|
-
const parts = appCode.split("-");
|
|
404
|
-
if (parts.length !== 3) {
|
|
405
|
-
return { ...FALLBACK, module: "UNK", moduleLabel: "Unknown", status: 500, catAct: "000", isSubscriptionIssue: false, isPermissionIssue: false };
|
|
406
|
-
}
|
|
407
|
-
const [mod, status, catAct] = parts;
|
|
408
|
-
const details = ERROR_DICTIONARY[catAct] || FALLBACK;
|
|
409
|
-
return {
|
|
410
|
-
title: details.title,
|
|
411
|
-
text: details.text,
|
|
412
|
-
module: mod,
|
|
413
|
-
moduleLabel: MODULE_LABELS[mod] || mod,
|
|
414
|
-
status: parseInt(status, 10),
|
|
415
|
-
catAct,
|
|
416
|
-
isSubscriptionIssue: catAct.startsWith("1"),
|
|
417
|
-
isPermissionIssue: catAct.startsWith("2")
|
|
418
|
-
};
|
|
419
|
-
};
|
|
420
|
-
var extractAppCode = (error) => {
|
|
421
|
-
const e = error;
|
|
422
|
-
return e?.response?.data?.error?.appCode ?? e?.data?.error?.appCode ?? null;
|
|
423
|
-
};
|
|
424
|
-
var resolveError = (error) => {
|
|
425
|
-
const appCode = extractAppCode(error);
|
|
426
|
-
if (appCode) return resolveAppError(appCode);
|
|
427
|
-
const e = error;
|
|
428
|
-
return { ...FALLBACK, module: "UNK", moduleLabel: "Unknown", status: e?.status || 500, catAct: "000", isSubscriptionIssue: false, isPermissionIssue: false };
|
|
429
|
-
};
|
|
430
|
-
function generateCanonicalId() {
|
|
431
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
432
|
-
return crypto.randomUUID();
|
|
433
|
-
}
|
|
434
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
435
|
-
const r = Math.random() * 16 | 0;
|
|
436
|
-
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
function generateInfrastructureFields(activeScope, availableScopes) {
|
|
440
|
-
if (!activeScope || !availableScopes?.length) {
|
|
441
|
-
throw ErrorFactory.unauthorized("[generateInfrastructureFields] Missing scope context.");
|
|
442
|
-
}
|
|
443
|
-
let companyId = null;
|
|
444
|
-
let legalEntityId = null;
|
|
445
|
-
let facilityId = null;
|
|
446
|
-
let pointer = activeScope;
|
|
447
|
-
while (pointer) {
|
|
448
|
-
if (pointer.type === "company") companyId = pointer.base44Id;
|
|
449
|
-
if (pointer.type === "legalEntity") legalEntityId = pointer.base44Id;
|
|
450
|
-
if (pointer.type === "facility") facilityId = pointer.base44Id;
|
|
451
|
-
const parentId = pointer.parentId ?? null;
|
|
452
|
-
pointer = parentId ? availableScopes.find((s) => s.base44Id === parentId) || null : null;
|
|
453
|
-
}
|
|
454
|
-
if (!companyId) {
|
|
455
|
-
throw ErrorFactory.unauthorized("[generateInfrastructureFields] Could not resolve companyId from scope tree.");
|
|
456
|
-
}
|
|
457
|
-
return {
|
|
458
|
-
canonicalId: crypto.randomUUID(),
|
|
459
|
-
companyId,
|
|
460
|
-
legalEntityId,
|
|
461
|
-
facilityId,
|
|
462
|
-
authScopeId: activeScope.authScopeId
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
function buildRlsFilter(session, extra = {}) {
|
|
466
|
-
return { ...extra, authScopeId: { $in: session.readScopes || [] } };
|
|
467
|
-
}
|
|
468
|
-
function canRead(session, authScopeId) {
|
|
469
|
-
return session.readScopes?.includes(authScopeId) ?? false;
|
|
470
|
-
}
|
|
471
|
-
function canWrite(session, authScopeId, resourcePath) {
|
|
472
|
-
const override = session.resourceOverrides?.[authScopeId]?.[resourcePath];
|
|
473
|
-
if (override === "WRITE") return true;
|
|
474
|
-
if (override === "NONE" || override === "READ") return false;
|
|
475
|
-
return session.writeScopes?.includes(authScopeId) ?? false;
|
|
476
|
-
}
|
|
477
|
-
function assertCanWrite(session, authScopeId, resourcePath) {
|
|
478
|
-
const override = session.resourceOverrides?.[authScopeId]?.[resourcePath];
|
|
479
|
-
if (override === "WRITE") return;
|
|
480
|
-
if (override === "NONE" || override === "READ") {
|
|
481
|
-
throw new AppError(
|
|
482
|
-
403,
|
|
483
|
-
MOD.SESSION,
|
|
484
|
-
CAT.PERMISSION,
|
|
485
|
-
ACT.GENERIC,
|
|
486
|
-
`You do not have write access to "${resourcePath}" in this scope.`
|
|
487
|
-
);
|
|
488
|
-
}
|
|
489
|
-
if (session.writeScopes?.includes(authScopeId)) return;
|
|
490
|
-
throw new AppError(
|
|
491
|
-
403,
|
|
492
|
-
MOD.SESSION,
|
|
493
|
-
CAT.PERMISSION,
|
|
494
|
-
ACT.GENERIC,
|
|
495
|
-
"You do not have permission to modify records in this scope."
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
function resolveAccess(session, activeScopeId, tabConfig) {
|
|
499
|
-
const resourcePath = tabConfig?.resourceId;
|
|
500
|
-
const overrides = session.resourceOverrides?.[activeScopeId] ?? {};
|
|
501
|
-
if (resourcePath) {
|
|
502
|
-
const segments = resourcePath.split(".");
|
|
503
|
-
for (let len = segments.length; len >= 1; len--) {
|
|
504
|
-
const path = segments.slice(0, len).join(".");
|
|
505
|
-
const override = overrides[path];
|
|
506
|
-
if (override === "NONE" || override === "READ" || override === "WRITE") return override;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
const allowedRoles = tabConfig?.allowedRoles ?? [];
|
|
510
|
-
const requiredPrivileges = tabConfig?.requiredPrivileges ?? [];
|
|
511
|
-
const hasRestrictions = allowedRoles.length > 0 || requiredPrivileges.length > 0;
|
|
512
|
-
if (hasRestrictions) {
|
|
513
|
-
const role = session?.role ?? "";
|
|
514
|
-
const privileges = session?.privileges ?? [];
|
|
515
|
-
const passesRole = allowedRoles.length > 0 && allowedRoles.includes(role);
|
|
516
|
-
const passesPrivilege = requiredPrivileges.length > 0 && requiredPrivileges.some((p) => privileges.includes(p));
|
|
517
|
-
return passesRole || passesPrivilege ? "WRITE" : "NONE";
|
|
518
|
-
}
|
|
519
|
-
return "READ";
|
|
520
|
-
}
|
|
521
|
-
function resolveMenuVisibility(session, authScopeId, resourcePath) {
|
|
522
|
-
const override = session.resourceOverrides?.[authScopeId]?.[resourcePath];
|
|
523
|
-
if (override === "NONE") return "hidden";
|
|
524
|
-
if (override === "READ") return "read_only";
|
|
525
|
-
if (override === "WRITE") return "active";
|
|
526
|
-
if (session.writeScopes?.includes(authScopeId)) return "active";
|
|
527
|
-
if (session.readScopes?.includes(authScopeId)) return "read_only";
|
|
528
|
-
return "hidden";
|
|
529
|
-
}
|
|
530
|
-
function isTabVisible(session, activeScopeId, tabConfig) {
|
|
531
|
-
return resolveAccess(session, activeScopeId, tabConfig) !== "NONE";
|
|
532
|
-
}
|
|
533
|
-
function isTabWritable(session, activeScopeId, tabConfig) {
|
|
534
|
-
return resolveAccess(session, activeScopeId, tabConfig) === "WRITE";
|
|
535
|
-
}
|
|
536
361
|
var UserRole = {
|
|
537
362
|
OWNER: "owner",
|
|
538
363
|
MANAGER: "manager",
|
|
@@ -752,17 +577,6 @@ var FORM_REGISTRY = {
|
|
|
752
577
|
// ── Audit & Compliance ───────────────────────────────────────────────────
|
|
753
578
|
auditProjectForm: { label: "Audit Project", menuUrl: "/admin", module: "Audit & Compliance" }
|
|
754
579
|
};
|
|
755
|
-
function getFormMeta(componentId) {
|
|
756
|
-
return FORM_REGISTRY[componentId] ?? null;
|
|
757
|
-
}
|
|
758
|
-
function getFormsGroupedByModule() {
|
|
759
|
-
const groups = {};
|
|
760
|
-
for (const [id, meta] of Object.entries(FORM_REGISTRY)) {
|
|
761
|
-
if (!groups[meta.module]) groups[meta.module] = [];
|
|
762
|
-
groups[meta.module].push({ id, ...meta });
|
|
763
|
-
}
|
|
764
|
-
return groups;
|
|
765
|
-
}
|
|
766
580
|
var TASK_TYPE_REGISTRY = {
|
|
767
581
|
INVOICE_OCR: {
|
|
768
582
|
label: "Invoice / Receipt OCR",
|
|
@@ -797,20 +611,6 @@ var TASK_TYPE_REGISTRY = {
|
|
|
797
611
|
approveAction: "ATTACH_REPORT_FILE"
|
|
798
612
|
}
|
|
799
613
|
};
|
|
800
|
-
var getTaskTypeOptions = () => Object.entries(TASK_TYPE_REGISTRY).map(([value, config]) => ({
|
|
801
|
-
value,
|
|
802
|
-
label: config.label,
|
|
803
|
-
description: config.description,
|
|
804
|
-
mimeTypes: config.mimeTypes,
|
|
805
|
-
icon: config.icon
|
|
806
|
-
}));
|
|
807
|
-
var getAcceptedMimeTypes = (taskType) => {
|
|
808
|
-
const config = TASK_TYPE_REGISTRY[taskType];
|
|
809
|
-
return config ? config.mimeTypes.join(",") : "*/*";
|
|
810
|
-
};
|
|
811
|
-
var getApproveAction = (taskType) => {
|
|
812
|
-
return TASK_TYPE_REGISTRY[taskType]?.approveAction ?? null;
|
|
813
|
-
};
|
|
814
614
|
var RESOURCE_PATHS = {
|
|
815
615
|
// ── Top-level menu sections ────────────────────────────────────────────────
|
|
816
616
|
dashboard: "dashboard",
|
|
@@ -862,7 +662,6 @@ var AGRI_ONLY = [
|
|
|
862
662
|
];
|
|
863
663
|
var RESOURCE_MODULE_MAP = {
|
|
864
664
|
// ── Top-level menu sections ────────────────────────────────────────────────
|
|
865
|
-
// CORE features (accessible to everyone with an account)
|
|
866
665
|
[RESOURCE_PATHS.dashboard]: [],
|
|
867
666
|
[RESOURCE_PATHS.operations]: ALL_OPERATIONS,
|
|
868
667
|
[RESOURCE_PATHS.equipment]: ALL_OPERATIONS,
|
|
@@ -917,17 +716,8 @@ var ROLE_SECTIONS_BY_KEY = {
|
|
|
917
716
|
};
|
|
918
717
|
var EVERYONE = Object.values(UserRole);
|
|
919
718
|
var RESOURCE_REQUIREMENTS = {
|
|
920
|
-
[RESOURCE_PATHS.dashboard]: {
|
|
921
|
-
|
|
922
|
-
},
|
|
923
|
-
[RESOURCE_PATHS.operations]: {
|
|
924
|
-
allowedRoles: EVERYONE
|
|
925
|
-
},
|
|
926
|
-
// [RESOURCE_PATHS.equipment]: EVERYONE,
|
|
927
|
-
// [RESOURCE_PATHS.safety]: ALL_OPERATIONS,
|
|
928
|
-
// [RESOURCE_PATHS.team]: ALL_OPERATIONS,
|
|
929
|
-
// [RESOURCE_PATHS.records]: ALL_OPERATIONS,
|
|
930
|
-
// [RESOURCE_PATHS.adminSettings]: ALL_OPERATIONS,
|
|
719
|
+
[RESOURCE_PATHS.dashboard]: { allowedRoles: EVERYONE },
|
|
720
|
+
[RESOURCE_PATHS.operations]: { allowedRoles: EVERYONE },
|
|
931
721
|
// ── Records & Reports ──────────────────────────────────────────────────────
|
|
932
722
|
[RESOURCE_PATHS.documentHub]: {
|
|
933
723
|
allowedRoles: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR],
|
|
@@ -949,7 +739,6 @@ var RESOURCE_REQUIREMENTS = {
|
|
|
949
739
|
allowedRoles: [UserRole.OWNER, UserRole.MANAGER, UserRole.SUPERVISOR],
|
|
950
740
|
requiredPrivileges: [UserPrivilege.ADMIN, UserPrivilege.AUDIT_COMPLIANCE, UserPrivilege.FINANCE, UserPrivilege.HR]
|
|
951
741
|
},
|
|
952
|
-
// [RESOURCE_PATHS.analytics]: ALL_OPERATIONS,
|
|
953
742
|
// ── Admin Settings ─────────────────────────────────────────────────────────
|
|
954
743
|
[RESOURCE_PATHS.adminUserRoster]: {
|
|
955
744
|
allowedRoles: [UserRole.OWNER],
|
|
@@ -966,101 +755,7 @@ var RESOURCE_REQUIREMENTS = {
|
|
|
966
755
|
[RESOURCE_PATHS.adminCompanySettings]: {
|
|
967
756
|
allowedRoles: [UserRole.OWNER]
|
|
968
757
|
}
|
|
969
|
-
// // ── Operations ────────────────────────────────────────────────────────────
|
|
970
|
-
// [RESOURCE_PATHS.farmManagement]: [EnabledModule.FARM_MANAGEMENT],
|
|
971
|
-
// [RESOURCE_PATHS.packhouse]: [EnabledModule.PACKHOUSE],
|
|
972
|
-
// [RESOURCE_PATHS.accommodation]: [EnabledModule.ACCOMMODATION],
|
|
973
|
-
// // ── Equipment & Fleet ──────────────────────────────────────────────────────
|
|
974
|
-
// [RESOURCE_PATHS.equipPreStart]: ALL_OPERATIONS,
|
|
975
|
-
// [RESOURCE_PATHS.equipMaintenance]: ALL_OPERATIONS,
|
|
976
|
-
// [RESOURCE_PATHS.equipFuelRegister]: ALL_OPERATIONS,
|
|
977
|
-
// [RESOURCE_PATHS.equipCalibration]: AGRI_ONLY,
|
|
978
|
-
// [RESOURCE_PATHS.equipFleet]: ALL_OPERATIONS,
|
|
979
|
-
// // ── Safety, Quality & Compliance ──────────────────────────────────────────
|
|
980
|
-
// [RESOURCE_PATHS.safetyAuditing]: ALL_OPERATIONS,
|
|
981
|
-
// [RESOURCE_PATHS.safetyFood]: AGRI_ONLY,
|
|
982
|
-
// [RESOURCE_PATHS.safetyWhs]: ALL_OPERATIONS,
|
|
983
|
-
// [RESOURCE_PATHS.safetyCleaning]: ALL_OPERATIONS,
|
|
984
|
-
// // ── Team & HR ──────────────────────────────────────────────────────────────
|
|
985
|
-
// [RESOURCE_PATHS.teamStaff]: ALL_OPERATIONS,
|
|
986
|
-
// [RESOURCE_PATHS.teamTraining]: ALL_OPERATIONS,
|
|
987
758
|
};
|
|
988
|
-
function evaluateBaseAccess(resourcePath, session, requirements = {}) {
|
|
989
|
-
if (!session?.role) return "NONE";
|
|
990
|
-
if (session.role === UserRole.OWNER) return "WRITE";
|
|
991
|
-
const requiredModules = RESOURCE_MODULE_MAP[resourcePath] || [];
|
|
992
|
-
if (requiredModules.length > 0) {
|
|
993
|
-
const hasModule = requiredModules.some((m) => session.enabledModules?.includes(m));
|
|
994
|
-
if (!hasModule) return "NONE";
|
|
995
|
-
}
|
|
996
|
-
const topLevel = resourcePath.split(".")[0];
|
|
997
|
-
const defaultRoles = ROLE_SECTIONS_BY_KEY[topLevel] || [];
|
|
998
|
-
const allowedRoles = requirements.allowedRoles?.length ? requirements.allowedRoles : defaultRoles;
|
|
999
|
-
if (!allowedRoles.includes(session.role)) return "NONE";
|
|
1000
|
-
if (requirements.requiredPrivileges?.length) {
|
|
1001
|
-
const hasPrivilege = requirements.requiredPrivileges.some((p) => session.privileges?.includes(p));
|
|
1002
|
-
if (!hasPrivilege) return "NONE";
|
|
1003
|
-
}
|
|
1004
|
-
return session.role === UserRole.MANAGER ? "WRITE" : "READ";
|
|
1005
|
-
}
|
|
1006
|
-
function resolveEffectiveAccess(base, override) {
|
|
1007
|
-
return override === void 0 || override === null ? base : override;
|
|
1008
|
-
}
|
|
1009
|
-
function findSpecificOverride(session, activeScopeId, resourcePath) {
|
|
1010
|
-
const scopeOverrides = session.resourceOverrides?.[activeScopeId];
|
|
1011
|
-
if (!scopeOverrides) return null;
|
|
1012
|
-
const parts = resourcePath.split(".");
|
|
1013
|
-
let currentPath = "";
|
|
1014
|
-
let bestOverride = null;
|
|
1015
|
-
for (const part of parts) {
|
|
1016
|
-
currentPath = currentPath ? `${currentPath}.${part}` : part;
|
|
1017
|
-
if (scopeOverrides[currentPath] !== void 0) {
|
|
1018
|
-
bestOverride = scopeOverrides[currentPath];
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
return bestOverride;
|
|
1022
|
-
}
|
|
1023
|
-
async function assertTenantBoundary(db, session, targets) {
|
|
1024
|
-
if (!session.companyId) {
|
|
1025
|
-
throw ErrorFactory.internal(MOD.ADMIN, "Missing requester company context.");
|
|
1026
|
-
}
|
|
1027
|
-
if (targets.targetUserId) {
|
|
1028
|
-
const targetUserAssignments = await db.UserRoleAssignment.filter({
|
|
1029
|
-
userId: targets.targetUserId,
|
|
1030
|
-
companyId: session.companyId
|
|
1031
|
-
});
|
|
1032
|
-
if (targetUserAssignments.length === 0) {
|
|
1033
|
-
console.error(`[SECURITY BREACH] User tried to modify targetUserId ${targets.targetUserId}`);
|
|
1034
|
-
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid target user.");
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
if (targets.authScopeId) {
|
|
1038
|
-
const matchedScope = session.availableScopes?.find(
|
|
1039
|
-
(scope) => scope.authScopeId === targets.authScopeId
|
|
1040
|
-
);
|
|
1041
|
-
if (!matchedScope) {
|
|
1042
|
-
console.error(`[SECURITY BREACH] Scope ${targets.authScopeId} not found in user's permitted list.`);
|
|
1043
|
-
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Tenant boundary violation.");
|
|
1044
|
-
}
|
|
1045
|
-
if (targets.expectedScopeType && matchedScope.type !== targets.expectedScopeType) {
|
|
1046
|
-
console.error(`[VALIDATION ERROR] Expected type ${targets.expectedScopeType} but got ${matchedScope.type}`);
|
|
1047
|
-
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid authorization scope level.");
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
return true;
|
|
1051
|
-
}
|
|
1052
|
-
async function verifyAndExtractSession(token, secret) {
|
|
1053
|
-
if (!token) {
|
|
1054
|
-
throw ErrorFactory.unauthorized(MOD.SESSION, "UNAUTHORIZED: No secure session token provided.");
|
|
1055
|
-
}
|
|
1056
|
-
try {
|
|
1057
|
-
const secretKey = new TextEncoder().encode(secret);
|
|
1058
|
-
const { payload } = await (0, import_jose.jwtVerify)(token, secretKey);
|
|
1059
|
-
return payload;
|
|
1060
|
-
} catch (error) {
|
|
1061
|
-
throw ErrorFactory.unauthorized(MOD.SESSION, "UNAUTHORIZED: No secure session token provided.");
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
759
|
var DocumentType = /* @__PURE__ */ ((DocumentType2) => {
|
|
1065
760
|
DocumentType2["SPRAY_RECORD"] = "SPRAY_RECORD";
|
|
1066
761
|
DocumentType2["FERTIGATION_RECORD"] = "FERTIGATION_RECORD";
|
|
@@ -1097,6 +792,510 @@ var DocumentType = /* @__PURE__ */ ((DocumentType2) => {
|
|
|
1097
792
|
DocumentType2["SENSITIVE_IDENTITY"] = "SENSITIVE_IDENTITY";
|
|
1098
793
|
return DocumentType2;
|
|
1099
794
|
})(DocumentType || {});
|
|
795
|
+
var DocumentCategory = /* @__PURE__ */ ((DocumentCategory2) => {
|
|
796
|
+
DocumentCategory2["AGRONOMY_TESTING"] = "Agronomy & Testing";
|
|
797
|
+
DocumentCategory2["FARM_OPERATIONS"] = "Farm Operations";
|
|
798
|
+
DocumentCategory2["COMPLIANCE_QA"] = "Compliance & Quality";
|
|
799
|
+
DocumentCategory2["INVENTORY_LOG"] = "Inventory & Logistics";
|
|
800
|
+
DocumentCategory2["ASSETS_MAINT"] = "Assets & Maintenance";
|
|
801
|
+
DocumentCategory2["HR_LEGAL_SAFETY"] = "HR, Legal & Safety";
|
|
802
|
+
DocumentCategory2["FINANCE_ADMIN"] = "Finance & Admin";
|
|
803
|
+
return DocumentCategory2;
|
|
804
|
+
})(DocumentCategory || {});
|
|
805
|
+
var DOCUMENT_DISPLAY_INFO = {
|
|
806
|
+
// --- AGRONOMY & TESTING ---
|
|
807
|
+
["SOIL_TEST" /* SOIL_TEST */]: {
|
|
808
|
+
label: "Soil Analysis",
|
|
809
|
+
description: "Detailed lab results for soil nutrients, pH, and health.",
|
|
810
|
+
category: "Agronomy & Testing" /* AGRONOMY_TESTING */
|
|
811
|
+
},
|
|
812
|
+
["LEAF_TEST" /* LEAF_TEST */]: {
|
|
813
|
+
label: "Leaf & Tissue Test",
|
|
814
|
+
description: "Nutrient analysis of plant tissue samples.",
|
|
815
|
+
category: "Agronomy & Testing" /* AGRONOMY_TESTING */
|
|
816
|
+
},
|
|
817
|
+
["WATER_TEST" /* WATER_TEST */]: {
|
|
818
|
+
label: "Water Quality Test",
|
|
819
|
+
description: "Testing for irrigation suitability and microbial levels.",
|
|
820
|
+
category: "Agronomy & Testing" /* AGRONOMY_TESTING */
|
|
821
|
+
},
|
|
822
|
+
["MRL_TESTING" /* MRL_TESTING */]: {
|
|
823
|
+
label: "MRL Residue Analysis",
|
|
824
|
+
description: "Maximum Residue Limit reports for chemical compliance.",
|
|
825
|
+
category: "Agronomy & Testing" /* AGRONOMY_TESTING */
|
|
826
|
+
},
|
|
827
|
+
["HEAVY_METAL" /* HEAVY_METAL */]: {
|
|
828
|
+
label: "Heavy Metal Analysis",
|
|
829
|
+
description: "Testing for lead, cadmium, and other metal contaminants.",
|
|
830
|
+
category: "Agronomy & Testing" /* AGRONOMY_TESTING */
|
|
831
|
+
},
|
|
832
|
+
["MICROBIAL" /* MICROBIAL */]: {
|
|
833
|
+
label: "Microbial Testing",
|
|
834
|
+
description: "Pathogen detection reports (e.g., E. coli, Salmonella).",
|
|
835
|
+
category: "Agronomy & Testing" /* AGRONOMY_TESTING */
|
|
836
|
+
},
|
|
837
|
+
// --- FARM OPERATIONS ---
|
|
838
|
+
["SPRAY_RECORD" /* SPRAY_RECORD */]: {
|
|
839
|
+
label: "Spray Operations",
|
|
840
|
+
description: "Chemical application logs and weather conditions.",
|
|
841
|
+
category: "Farm Operations" /* FARM_OPERATIONS */
|
|
842
|
+
},
|
|
843
|
+
["FERTIGATION_RECORD" /* FERTIGATION_RECORD */]: {
|
|
844
|
+
label: "Fertigation Logs",
|
|
845
|
+
description: "Records of liquid fertilizer applied via irrigation.",
|
|
846
|
+
category: "Farm Operations" /* FARM_OPERATIONS */
|
|
847
|
+
},
|
|
848
|
+
["FERTILIZER_RECORD" /* FERTILIZER_RECORD */]: {
|
|
849
|
+
label: "Fertilizer Records",
|
|
850
|
+
description: "Manual granular or bulk fertilizer application logs.",
|
|
851
|
+
category: "Farm Operations" /* FARM_OPERATIONS */
|
|
852
|
+
},
|
|
853
|
+
["HARVEST_LOG" /* HARVEST_LOG */]: {
|
|
854
|
+
label: "Harvest Manifests",
|
|
855
|
+
description: "Daily picking volumes and block yield data.",
|
|
856
|
+
category: "Farm Operations" /* FARM_OPERATIONS */
|
|
857
|
+
},
|
|
858
|
+
["PACKHOUSE_INSPECTION" /* PACKHOUSE_INSPECTION */]: {
|
|
859
|
+
label: "Packhouse Inspections",
|
|
860
|
+
description: "Quality and hygiene checks conducted in the packhouse.",
|
|
861
|
+
category: "Farm Operations" /* FARM_OPERATIONS */
|
|
862
|
+
},
|
|
863
|
+
["MAPPING_TRAFIC_MANAGEMENT" /* MAPPING_TRAFIC_MANAGEMENT */]: {
|
|
864
|
+
label: "Traffic Management",
|
|
865
|
+
description: "Maps and rules for vehicle and heavy machinery movement.",
|
|
866
|
+
category: "Farm Operations" /* FARM_OPERATIONS */
|
|
867
|
+
},
|
|
868
|
+
// --- COMPLIANCE & QUALITY ---
|
|
869
|
+
["QA" /* QA */]: {
|
|
870
|
+
label: "Quality Assurance",
|
|
871
|
+
description: "General product quality checks and grading standards.",
|
|
872
|
+
category: "Compliance & Quality" /* COMPLIANCE_QA */
|
|
873
|
+
},
|
|
874
|
+
["PEST" /* PEST */]: {
|
|
875
|
+
label: "Pest & Disease Monitoring",
|
|
876
|
+
description: "Scouting reports and pest pressure assessments.",
|
|
877
|
+
category: "Compliance & Quality" /* COMPLIANCE_QA */
|
|
878
|
+
},
|
|
879
|
+
["AUDIT_REPORT" /* AUDIT_REPORT */]: {
|
|
880
|
+
label: "External Audit Reports",
|
|
881
|
+
description: "Final results from Freshcare, GlobalGAP, or Organic audits.",
|
|
882
|
+
category: "Compliance & Quality" /* COMPLIANCE_QA */
|
|
883
|
+
},
|
|
884
|
+
["RISK_ASSESSMENTS" /* RISK_ASSESSMENTS */]: {
|
|
885
|
+
label: "Risk Assessments",
|
|
886
|
+
description: "Operational hazard identification and safety mitigations.",
|
|
887
|
+
category: "Compliance & Quality" /* COMPLIANCE_QA */
|
|
888
|
+
},
|
|
889
|
+
// --- ASSETS & MAINTENANCE ---
|
|
890
|
+
["SERVICING_MAINTENANCE" /* SERVICING_MAINTENANCE */]: {
|
|
891
|
+
label: "Equipment Maintenance",
|
|
892
|
+
description: "Service history and repair logs for farm machinery.",
|
|
893
|
+
category: "Assets & Maintenance" /* ASSETS_MAINT */
|
|
894
|
+
},
|
|
895
|
+
["EQUIPMENT_CALIBRATION_RECORD" /* EQUIPMENT_CALIBRATION_RECORD */]: {
|
|
896
|
+
label: "Calibration Records",
|
|
897
|
+
description: "Accuracy checks for sprayers, scales, and moisture probes.",
|
|
898
|
+
category: "Assets & Maintenance" /* ASSETS_MAINT */
|
|
899
|
+
},
|
|
900
|
+
["CLEANING" /* CLEANING */]: {
|
|
901
|
+
label: "Sanitation Logs",
|
|
902
|
+
description: "Daily/weekly cleaning schedules for tools and facilities.",
|
|
903
|
+
category: "Assets & Maintenance" /* ASSETS_MAINT */
|
|
904
|
+
},
|
|
905
|
+
["MAPPING_FARM_MAPS" /* MAPPING_FARM_MAPS */]: {
|
|
906
|
+
label: "Farm Infrastructure Maps",
|
|
907
|
+
description: "Layouts of blocks, irrigation, and fixed assets.",
|
|
908
|
+
category: "Assets & Maintenance" /* ASSETS_MAINT */
|
|
909
|
+
},
|
|
910
|
+
// --- INVENTORY & LOGISTICS ---
|
|
911
|
+
["INVENTORY_CHEMICALS" /* INVENTORY_CHEMICALS */]: {
|
|
912
|
+
label: "Chemical Inventory",
|
|
913
|
+
description: "Manifests for stored pesticides and fertilizers.",
|
|
914
|
+
category: "Inventory & Logistics" /* INVENTORY_LOG */
|
|
915
|
+
},
|
|
916
|
+
["INVENTORY_BOX_CARTON" /* INVENTORY_BOX_CARTON */]: {
|
|
917
|
+
label: "Packaging Inventory",
|
|
918
|
+
description: "Stock levels for cartons, bins, and packaging materials.",
|
|
919
|
+
category: "Inventory & Logistics" /* INVENTORY_LOG */
|
|
920
|
+
},
|
|
921
|
+
["INVENTORY_CONSUMABLES" /* INVENTORY_CONSUMABLES */]: {
|
|
922
|
+
label: "General Consumables",
|
|
923
|
+
description: "Stock tracking for fuel, PPE, and workshop supplies.",
|
|
924
|
+
category: "Inventory & Logistics" /* INVENTORY_LOG */
|
|
925
|
+
},
|
|
926
|
+
["STOCKTAKE" /* STOCKTAKE */]: {
|
|
927
|
+
label: "Physical Stocktake",
|
|
928
|
+
description: "Periodic audits of physical vs. recorded stock levels.",
|
|
929
|
+
category: "Inventory & Logistics" /* INVENTORY_LOG */
|
|
930
|
+
},
|
|
931
|
+
["STOCKTAKE_MSDS" /* STOCKTAKE_MSDS */]: {
|
|
932
|
+
label: "MSDS / Safety Data",
|
|
933
|
+
description: "Material Safety Data Sheets for stored chemicals.",
|
|
934
|
+
category: "Inventory & Logistics" /* INVENTORY_LOG */
|
|
935
|
+
},
|
|
936
|
+
// --- HR, LEGAL & SAFETY ---
|
|
937
|
+
["TRAINING_RECORDS" /* TRAINING_RECORDS */]: {
|
|
938
|
+
label: "Staff Inductions",
|
|
939
|
+
description: "General staff onboarding and safety training logs.",
|
|
940
|
+
category: "HR, Legal & Safety" /* HR_LEGAL_SAFETY */
|
|
941
|
+
},
|
|
942
|
+
["TRAINING_CERTIFICATE" /* TRAINING_CERTIFICATE */]: {
|
|
943
|
+
label: "Training Certificates",
|
|
944
|
+
description: "External licenses, ChemCert, and first aid tickets.",
|
|
945
|
+
category: "HR, Legal & Safety" /* HR_LEGAL_SAFETY */
|
|
946
|
+
},
|
|
947
|
+
["SOPS" /* SOPS */]: {
|
|
948
|
+
label: "Standard Procedures (SOPs)",
|
|
949
|
+
description: "Formalized instructions for farm and packhouse tasks.",
|
|
950
|
+
category: "HR, Legal & Safety" /* HR_LEGAL_SAFETY */
|
|
951
|
+
},
|
|
952
|
+
["LEGAL_CONTRACT" /* LEGAL_CONTRACT */]: {
|
|
953
|
+
label: "Legal Contracts",
|
|
954
|
+
description: "Lease agreements, supply contracts, and employment terms.",
|
|
955
|
+
category: "HR, Legal & Safety" /* HR_LEGAL_SAFETY */
|
|
956
|
+
},
|
|
957
|
+
["INCIDENT_REPORT" /* INCIDENT_REPORT */]: {
|
|
958
|
+
label: "Incident Reports",
|
|
959
|
+
description: "Logs of injuries, near-misses, or property damage.",
|
|
960
|
+
category: "HR, Legal & Safety" /* HR_LEGAL_SAFETY */
|
|
961
|
+
},
|
|
962
|
+
["SENSITIVE_IDENTITY" /* SENSITIVE_IDENTITY */]: {
|
|
963
|
+
label: "Identity & Visas",
|
|
964
|
+
description: "Sensitive data: Passports, ID cards, and work permits.",
|
|
965
|
+
category: "HR, Legal & Safety" /* HR_LEGAL_SAFETY */
|
|
966
|
+
},
|
|
967
|
+
["MAPPING_EMERGENCY" /* MAPPING_EMERGENCY */]: {
|
|
968
|
+
label: "Emergency Response Maps",
|
|
969
|
+
description: "Evacuation routes and emergency assembly points.",
|
|
970
|
+
category: "HR, Legal & Safety" /* HR_LEGAL_SAFETY */
|
|
971
|
+
},
|
|
972
|
+
// --- FINANCE & ADMIN ---
|
|
973
|
+
["INVOICE" /* INVOICE */]: {
|
|
974
|
+
label: "Supplier Invoices",
|
|
975
|
+
description: "Billing records for inputs, utilities, and services.",
|
|
976
|
+
category: "Finance & Admin" /* FINANCE_ADMIN */
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
var CATEGORY_TABS = [
|
|
980
|
+
"All",
|
|
981
|
+
...Object.values(DocumentCategory)
|
|
982
|
+
];
|
|
983
|
+
var DOCUMENT_MENU_MAP = {
|
|
984
|
+
["SPRAY_RECORD" /* SPRAY_RECORD */]: [RESOURCE_PATHS.farmManagement],
|
|
985
|
+
["FERTIGATION_RECORD" /* FERTIGATION_RECORD */]: [RESOURCE_PATHS.farmManagement],
|
|
986
|
+
["FERTILIZER_RECORD" /* FERTILIZER_RECORD */]: [RESOURCE_PATHS.farmManagement],
|
|
987
|
+
["QA" /* QA */]: [RESOURCE_PATHS.safetyAuditing],
|
|
988
|
+
["CLEANING" /* CLEANING */]: [RESOURCE_PATHS.safetyCleaning],
|
|
989
|
+
["PEST" /* PEST */]: [RESOURCE_PATHS.farmManagement],
|
|
990
|
+
["EQUIPMENT_CALIBRATION_RECORD" /* EQUIPMENT_CALIBRATION_RECORD */]: [RESOURCE_PATHS.equipCalibration],
|
|
991
|
+
["SERVICING_MAINTENANCE" /* SERVICING_MAINTENANCE */]: [RESOURCE_PATHS.equipMaintenance],
|
|
992
|
+
["INVENTORY_CONSUMABLES" /* INVENTORY_CONSUMABLES */]: [RESOURCE_PATHS.packhouse],
|
|
993
|
+
["INVENTORY_BOX_CARTON" /* INVENTORY_BOX_CARTON */]: [RESOURCE_PATHS.packhouse],
|
|
994
|
+
["INVENTORY_CHEMICALS" /* INVENTORY_CHEMICALS */]: [RESOURCE_PATHS.packhouse, RESOURCE_PATHS.farmManagement],
|
|
995
|
+
["STOCKTAKE" /* STOCKTAKE */]: [RESOURCE_PATHS.packhouse],
|
|
996
|
+
["STOCKTAKE_MSDS" /* STOCKTAKE_MSDS */]: [RESOURCE_PATHS.safetyWhs],
|
|
997
|
+
["MAPPING_FARM_MAPS" /* MAPPING_FARM_MAPS */]: [RESOURCE_PATHS.adminBlueprintsSites],
|
|
998
|
+
["MAPPING_TRAFIC_MANAGEMENT" /* MAPPING_TRAFIC_MANAGEMENT */]: [RESOURCE_PATHS.adminBlueprintsSites],
|
|
999
|
+
["MAPPING_EMERGENCY" /* MAPPING_EMERGENCY */]: [RESOURCE_PATHS.adminBlueprintsSites],
|
|
1000
|
+
["INCIDENT_REPORT" /* INCIDENT_REPORT */]: [RESOURCE_PATHS.safetyWhs],
|
|
1001
|
+
["RISK_ASSESSMENTS" /* RISK_ASSESSMENTS */]: [RESOURCE_PATHS.safetyWhs],
|
|
1002
|
+
["TRAINING_RECORDS" /* TRAINING_RECORDS */]: [RESOURCE_PATHS.teamTraining],
|
|
1003
|
+
["TRAINING_CERTIFICATE" /* TRAINING_CERTIFICATE */]: [RESOURCE_PATHS.teamTraining],
|
|
1004
|
+
["SOPS" /* SOPS */]: [RESOURCE_PATHS.documentHubTemplates],
|
|
1005
|
+
["SOIL_TEST" /* SOIL_TEST */]: [RESOURCE_PATHS.farmManagement],
|
|
1006
|
+
["LEAF_TEST" /* LEAF_TEST */]: [RESOURCE_PATHS.farmManagement],
|
|
1007
|
+
["WATER_TEST" /* WATER_TEST */]: [RESOURCE_PATHS.farmManagement, RESOURCE_PATHS.safetyFood],
|
|
1008
|
+
["MRL_TESTING" /* MRL_TESTING */]: [RESOURCE_PATHS.safetyFood],
|
|
1009
|
+
["HEAVY_METAL" /* HEAVY_METAL */]: [RESOURCE_PATHS.safetyFood],
|
|
1010
|
+
["MICROBIAL" /* MICROBIAL */]: [RESOURCE_PATHS.safetyFood],
|
|
1011
|
+
["HARVEST_LOG" /* HARVEST_LOG */]: [RESOURCE_PATHS.farmManagement],
|
|
1012
|
+
["PACKHOUSE_INSPECTION" /* PACKHOUSE_INSPECTION */]: [RESOURCE_PATHS.packhouse],
|
|
1013
|
+
["INVOICE" /* INVOICE */]: [RESOURCE_PATHS.documentHub],
|
|
1014
|
+
["LEGAL_CONTRACT" /* LEGAL_CONTRACT */]: [RESOURCE_PATHS.documentHub],
|
|
1015
|
+
["AUDIT_REPORT" /* AUDIT_REPORT */]: [RESOURCE_PATHS.safetyAuditing, RESOURCE_PATHS.dashboard],
|
|
1016
|
+
["SENSITIVE_IDENTITY" /* SENSITIVE_IDENTITY */]: [RESOURCE_PATHS.adminUserRoster]
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
// src/index.ts
|
|
1020
|
+
var AppError = class extends Error {
|
|
1021
|
+
status;
|
|
1022
|
+
appCode;
|
|
1023
|
+
isOperational;
|
|
1024
|
+
constructor(status, mod, cat, act, message) {
|
|
1025
|
+
super(message);
|
|
1026
|
+
this.status = status;
|
|
1027
|
+
this.appCode = `${mod}-${status}-${cat}${act}`;
|
|
1028
|
+
this.isOperational = true;
|
|
1029
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
var ErrorFactory = {
|
|
1033
|
+
unauthorized: (mod, msg = "Authentication required") => new AppError(401, mod, CAT.PERMISSION, ACT.GENERIC, msg),
|
|
1034
|
+
forbidden: (mod, act, msg = "Access denied") => new AppError(403, mod, CAT.PERMISSION, act, msg),
|
|
1035
|
+
badRequest: (mod, act, msg = "Bad request") => new AppError(400, mod, CAT.VALIDATION, act, msg),
|
|
1036
|
+
notFound: (mod, act = ACT.GENERIC, msg = "Record not found") => new AppError(404, mod, CAT.VALIDATION, act, msg),
|
|
1037
|
+
conflict: (mod, act, msg = "Record already exists") => new AppError(409, mod, CAT.INTEGRITY, act, msg),
|
|
1038
|
+
integrity: (mod, act = ACT.GENERIC, msg = "Data integrity violation") => new AppError(409, mod, CAT.INTEGRITY, act, msg),
|
|
1039
|
+
subscription: (mod, msg = "Module locked or subscription required") => new AppError(403, mod, CAT.SUBSCRIPTION, ACT.GENERIC, msg),
|
|
1040
|
+
internal: (mod, msg = "Internal server error") => new AppError(500, mod, CAT.INTEGRITY, ACT.GENERIC, msg)
|
|
1041
|
+
};
|
|
1042
|
+
var handleAppErrorResponse = (error, mod = MOD.ADMIN) => {
|
|
1043
|
+
if (error instanceof AppError && error.isOperational) {
|
|
1044
|
+
return Response.json(
|
|
1045
|
+
{ error: { appCode: error.appCode, message: error.message } },
|
|
1046
|
+
{ status: error.status }
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
const e = error;
|
|
1050
|
+
console.error(`[${mod}] UNEXPECTED ERROR:`, e?.message);
|
|
1051
|
+
console.error(`[${mod}] STACK:`, e?.stack);
|
|
1052
|
+
return Response.json(
|
|
1053
|
+
{ error: { appCode: `${mod}-500-000`, message: "An unexpected server error occurred. Please try again." } },
|
|
1054
|
+
{ status: 500 }
|
|
1055
|
+
);
|
|
1056
|
+
};
|
|
1057
|
+
var FALLBACK = {
|
|
1058
|
+
title: "Something Went Wrong",
|
|
1059
|
+
text: "An unexpected error occurred. Please try again or contact your administrator."
|
|
1060
|
+
};
|
|
1061
|
+
var resolveAppError = (appCode) => {
|
|
1062
|
+
if (!appCode || typeof appCode !== "string") {
|
|
1063
|
+
return { ...FALLBACK, module: "UNK", moduleLabel: "Unknown", status: 500, catAct: "000", isSubscriptionIssue: false, isPermissionIssue: false };
|
|
1064
|
+
}
|
|
1065
|
+
const parts = appCode.split("-");
|
|
1066
|
+
if (parts.length !== 3) {
|
|
1067
|
+
return { ...FALLBACK, module: "UNK", moduleLabel: "Unknown", status: 500, catAct: "000", isSubscriptionIssue: false, isPermissionIssue: false };
|
|
1068
|
+
}
|
|
1069
|
+
const [mod, status, catAct] = parts;
|
|
1070
|
+
const details = ERROR_DICTIONARY[catAct] || FALLBACK;
|
|
1071
|
+
return {
|
|
1072
|
+
title: details.title,
|
|
1073
|
+
text: details.text,
|
|
1074
|
+
module: mod,
|
|
1075
|
+
moduleLabel: MODULE_LABELS[mod] || mod,
|
|
1076
|
+
status: parseInt(status, 10),
|
|
1077
|
+
catAct,
|
|
1078
|
+
isSubscriptionIssue: catAct.startsWith("1"),
|
|
1079
|
+
isPermissionIssue: catAct.startsWith("2")
|
|
1080
|
+
};
|
|
1081
|
+
};
|
|
1082
|
+
var extractAppCode = (error) => {
|
|
1083
|
+
const e = error;
|
|
1084
|
+
return e?.response?.data?.error?.appCode ?? e?.data?.error?.appCode ?? null;
|
|
1085
|
+
};
|
|
1086
|
+
var resolveError = (error) => {
|
|
1087
|
+
const appCode = extractAppCode(error);
|
|
1088
|
+
if (appCode) return resolveAppError(appCode);
|
|
1089
|
+
const e = error;
|
|
1090
|
+
return { ...FALLBACK, module: "UNK", moduleLabel: "Unknown", status: e?.status || 500, catAct: "000", isSubscriptionIssue: false, isPermissionIssue: false };
|
|
1091
|
+
};
|
|
1092
|
+
function generateCanonicalId() {
|
|
1093
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1094
|
+
return crypto.randomUUID();
|
|
1095
|
+
}
|
|
1096
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1097
|
+
const r = Math.random() * 16 | 0;
|
|
1098
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
function generateInfrastructureFields(activeScope, availableScopes) {
|
|
1102
|
+
if (!activeScope || !availableScopes?.length) {
|
|
1103
|
+
throw ErrorFactory.unauthorized("[generateInfrastructureFields] Missing scope context.");
|
|
1104
|
+
}
|
|
1105
|
+
let companyId = null;
|
|
1106
|
+
let legalEntityId = null;
|
|
1107
|
+
let facilityId = null;
|
|
1108
|
+
let pointer = activeScope;
|
|
1109
|
+
while (pointer) {
|
|
1110
|
+
if (pointer.type === "company") companyId = pointer.base44Id;
|
|
1111
|
+
if (pointer.type === "legalEntity") legalEntityId = pointer.base44Id;
|
|
1112
|
+
if (pointer.type === "facility") facilityId = pointer.base44Id;
|
|
1113
|
+
const parentId = pointer.parentId ?? null;
|
|
1114
|
+
pointer = parentId ? availableScopes.find((s) => s.base44Id === parentId) || null : null;
|
|
1115
|
+
}
|
|
1116
|
+
if (!companyId) {
|
|
1117
|
+
throw ErrorFactory.unauthorized("[generateInfrastructureFields] Could not resolve companyId from scope tree.");
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
canonicalId: crypto.randomUUID(),
|
|
1121
|
+
companyId,
|
|
1122
|
+
legalEntityId,
|
|
1123
|
+
facilityId,
|
|
1124
|
+
authScopeId: activeScope.authScopeId
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function buildRlsFilter(session, extra = {}) {
|
|
1128
|
+
return { ...extra, authScopeId: { $in: session.readScopes || [] } };
|
|
1129
|
+
}
|
|
1130
|
+
function canRead(session, authScopeId) {
|
|
1131
|
+
return session.readScopes?.includes(authScopeId) ?? false;
|
|
1132
|
+
}
|
|
1133
|
+
function canWrite(session, authScopeId, resourcePath) {
|
|
1134
|
+
const override = session.resourceOverrides?.[authScopeId]?.[resourcePath];
|
|
1135
|
+
if (override === "WRITE") return true;
|
|
1136
|
+
if (override === "NONE" || override === "READ") return false;
|
|
1137
|
+
return session.writeScopes?.includes(authScopeId) ?? false;
|
|
1138
|
+
}
|
|
1139
|
+
function assertCanWrite(session, authScopeId, resourcePath) {
|
|
1140
|
+
const override = session.resourceOverrides?.[authScopeId]?.[resourcePath];
|
|
1141
|
+
if (override === "WRITE") return;
|
|
1142
|
+
if (override === "NONE" || override === "READ") {
|
|
1143
|
+
throw new AppError(
|
|
1144
|
+
403,
|
|
1145
|
+
MOD.SESSION,
|
|
1146
|
+
CAT.PERMISSION,
|
|
1147
|
+
ACT.GENERIC,
|
|
1148
|
+
`You do not have write access to "${resourcePath}" in this scope.`
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
if (session.writeScopes?.includes(authScopeId)) return;
|
|
1152
|
+
throw new AppError(
|
|
1153
|
+
403,
|
|
1154
|
+
MOD.SESSION,
|
|
1155
|
+
CAT.PERMISSION,
|
|
1156
|
+
ACT.GENERIC,
|
|
1157
|
+
"You do not have permission to modify records in this scope."
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
function resolveAccess(session, activeScopeId, tabConfig) {
|
|
1161
|
+
const resourcePath = tabConfig?.resourceId;
|
|
1162
|
+
const overrides = session.resourceOverrides?.[activeScopeId] ?? {};
|
|
1163
|
+
if (resourcePath) {
|
|
1164
|
+
const segments = resourcePath.split(".");
|
|
1165
|
+
for (let len = segments.length; len >= 1; len--) {
|
|
1166
|
+
const path = segments.slice(0, len).join(".");
|
|
1167
|
+
const override = overrides[path];
|
|
1168
|
+
if (override === "NONE" || override === "READ" || override === "WRITE") return override;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
const allowedRoles = tabConfig?.allowedRoles ?? [];
|
|
1172
|
+
const requiredPrivileges = tabConfig?.requiredPrivileges ?? [];
|
|
1173
|
+
const hasRestrictions = allowedRoles.length > 0 || requiredPrivileges.length > 0;
|
|
1174
|
+
if (hasRestrictions) {
|
|
1175
|
+
const role = session?.role ?? "";
|
|
1176
|
+
const privileges = session?.privileges ?? [];
|
|
1177
|
+
const passesRole = allowedRoles.length > 0 && allowedRoles.includes(role);
|
|
1178
|
+
const passesPrivilege = requiredPrivileges.length > 0 && requiredPrivileges.some((p) => privileges.includes(p));
|
|
1179
|
+
return passesRole || passesPrivilege ? "WRITE" : "NONE";
|
|
1180
|
+
}
|
|
1181
|
+
return "READ";
|
|
1182
|
+
}
|
|
1183
|
+
function resolveMenuVisibility(session, authScopeId, resourcePath) {
|
|
1184
|
+
const override = session.resourceOverrides?.[authScopeId]?.[resourcePath];
|
|
1185
|
+
if (override === "NONE") return "hidden";
|
|
1186
|
+
if (override === "READ") return "read_only";
|
|
1187
|
+
if (override === "WRITE") return "active";
|
|
1188
|
+
if (session.writeScopes?.includes(authScopeId)) return "active";
|
|
1189
|
+
if (session.readScopes?.includes(authScopeId)) return "read_only";
|
|
1190
|
+
return "hidden";
|
|
1191
|
+
}
|
|
1192
|
+
function isTabVisible(session, activeScopeId, tabConfig) {
|
|
1193
|
+
return resolveAccess(session, activeScopeId, tabConfig) !== "NONE";
|
|
1194
|
+
}
|
|
1195
|
+
function isTabWritable(session, activeScopeId, tabConfig) {
|
|
1196
|
+
return resolveAccess(session, activeScopeId, tabConfig) === "WRITE";
|
|
1197
|
+
}
|
|
1198
|
+
function evaluateBaseAccess(resourcePath, session, requirements = {}) {
|
|
1199
|
+
if (!session?.role) return "NONE";
|
|
1200
|
+
if (session.role === UserRole.OWNER) return "WRITE";
|
|
1201
|
+
const requiredModules = RESOURCE_MODULE_MAP[resourcePath] || [];
|
|
1202
|
+
if (requiredModules.length > 0) {
|
|
1203
|
+
const hasModule = requiredModules.some((m) => session.enabledModules?.includes(m));
|
|
1204
|
+
if (!hasModule) return "NONE";
|
|
1205
|
+
}
|
|
1206
|
+
const topLevel = resourcePath.split(".")[0];
|
|
1207
|
+
const defaultRoles = ROLE_SECTIONS_BY_KEY[topLevel] || [];
|
|
1208
|
+
const allowedRoles = requirements.allowedRoles?.length ? requirements.allowedRoles : defaultRoles;
|
|
1209
|
+
if (!allowedRoles.includes(session.role)) return "NONE";
|
|
1210
|
+
if (requirements.requiredPrivileges?.length) {
|
|
1211
|
+
const hasPrivilege = requirements.requiredPrivileges.some((p) => session.privileges?.includes(p));
|
|
1212
|
+
if (!hasPrivilege) return "NONE";
|
|
1213
|
+
}
|
|
1214
|
+
return session.role === UserRole.MANAGER ? "WRITE" : "READ";
|
|
1215
|
+
}
|
|
1216
|
+
function resolveEffectiveAccess(base, override) {
|
|
1217
|
+
return override === void 0 || override === null ? base : override;
|
|
1218
|
+
}
|
|
1219
|
+
function findSpecificOverride(session, activeScopeId, resourcePath) {
|
|
1220
|
+
const scopeOverrides = session.resourceOverrides?.[activeScopeId];
|
|
1221
|
+
if (!scopeOverrides) return null;
|
|
1222
|
+
const parts = resourcePath.split(".");
|
|
1223
|
+
let currentPath = "";
|
|
1224
|
+
let bestOverride = null;
|
|
1225
|
+
for (const part of parts) {
|
|
1226
|
+
currentPath = currentPath ? `${currentPath}.${part}` : part;
|
|
1227
|
+
if (scopeOverrides[currentPath] !== void 0) {
|
|
1228
|
+
bestOverride = scopeOverrides[currentPath];
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return bestOverride;
|
|
1232
|
+
}
|
|
1233
|
+
async function assertTenantBoundary(db, session, targets) {
|
|
1234
|
+
if (!session.companyId) {
|
|
1235
|
+
throw ErrorFactory.internal(MOD.ADMIN, "Missing requester company context.");
|
|
1236
|
+
}
|
|
1237
|
+
if (targets.targetUserId) {
|
|
1238
|
+
const targetUserAssignments = await db.UserRoleAssignment.filter({
|
|
1239
|
+
userId: targets.targetUserId,
|
|
1240
|
+
companyId: session.companyId
|
|
1241
|
+
});
|
|
1242
|
+
if (targetUserAssignments.length === 0) {
|
|
1243
|
+
console.error(`[SECURITY BREACH] User tried to modify targetUserId ${targets.targetUserId}`);
|
|
1244
|
+
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid target user.");
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (targets.authScopeId) {
|
|
1248
|
+
const matchedScope = session.availableScopes?.find(
|
|
1249
|
+
(scope) => scope.authScopeId === targets.authScopeId
|
|
1250
|
+
);
|
|
1251
|
+
if (!matchedScope) {
|
|
1252
|
+
console.error(`[SECURITY BREACH] Scope ${targets.authScopeId} not found in user's permitted list.`);
|
|
1253
|
+
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Tenant boundary violation.");
|
|
1254
|
+
}
|
|
1255
|
+
if (targets.expectedScopeType && matchedScope.type !== targets.expectedScopeType) {
|
|
1256
|
+
console.error(`[VALIDATION ERROR] Expected type ${targets.expectedScopeType} but got ${matchedScope.type}`);
|
|
1257
|
+
throw ErrorFactory.forbidden(MOD.ADMIN, ACT.EDIT, "Invalid authorization scope level.");
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return true;
|
|
1261
|
+
}
|
|
1262
|
+
async function verifyAndExtractSession(token, secret) {
|
|
1263
|
+
if (!token) {
|
|
1264
|
+
throw ErrorFactory.unauthorized(MOD.SESSION, "UNAUTHORIZED: No secure session token provided.");
|
|
1265
|
+
}
|
|
1266
|
+
try {
|
|
1267
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
1268
|
+
const { payload } = await (0, import_jose.jwtVerify)(token, secretKey);
|
|
1269
|
+
return payload;
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
throw ErrorFactory.unauthorized(MOD.SESSION, "UNAUTHORIZED: No secure session token provided.");
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
function getFormMeta(componentId) {
|
|
1275
|
+
return FORM_REGISTRY[componentId] ?? null;
|
|
1276
|
+
}
|
|
1277
|
+
function getFormsGroupedByModule() {
|
|
1278
|
+
const groups = {};
|
|
1279
|
+
for (const [id, meta] of Object.entries(FORM_REGISTRY)) {
|
|
1280
|
+
if (!groups[meta.module]) groups[meta.module] = [];
|
|
1281
|
+
groups[meta.module].push({ id, ...meta });
|
|
1282
|
+
}
|
|
1283
|
+
return groups;
|
|
1284
|
+
}
|
|
1285
|
+
var getTaskTypeOptions = () => Object.entries(TASK_TYPE_REGISTRY).map(([value, config]) => ({
|
|
1286
|
+
value,
|
|
1287
|
+
label: config.label,
|
|
1288
|
+
description: config.description,
|
|
1289
|
+
mimeTypes: config.mimeTypes,
|
|
1290
|
+
icon: config.icon
|
|
1291
|
+
}));
|
|
1292
|
+
var getAcceptedMimeTypes = (taskType) => {
|
|
1293
|
+
const config = TASK_TYPE_REGISTRY[taskType];
|
|
1294
|
+
return config ? config.mimeTypes.join(",") : "*/*";
|
|
1295
|
+
};
|
|
1296
|
+
var getApproveAction = (taskType) => {
|
|
1297
|
+
return TASK_TYPE_REGISTRY[taskType]?.approveAction ?? null;
|
|
1298
|
+
};
|
|
1100
1299
|
function generateDocumentPath(params) {
|
|
1101
1300
|
const sanitizedName = params.siteName.replace(/[^a-z0-9]/gi, "_");
|
|
1102
1301
|
if (params.isStatic) {
|
|
@@ -1146,8 +1345,12 @@ function calculateFileMetadataMatch(criteria, fileMetadata, auditContext) {
|
|
|
1146
1345
|
AuditStatus,
|
|
1147
1346
|
AuditType,
|
|
1148
1347
|
CAT,
|
|
1348
|
+
CATEGORY_TABS,
|
|
1149
1349
|
Converter,
|
|
1150
1350
|
CropCycleStatus,
|
|
1351
|
+
DOCUMENT_DISPLAY_INFO,
|
|
1352
|
+
DOCUMENT_MENU_MAP,
|
|
1353
|
+
DocumentCategory,
|
|
1151
1354
|
DocumentType,
|
|
1152
1355
|
ERROR_DICTIONARY,
|
|
1153
1356
|
EVERYONE,
|