Added loads of features, updated packages and created schema sync.
This commit is contained in:
parent
97b77f5155
commit
36151f35bd
@ -49,7 +49,7 @@ A comprehensive REST API for managing 3D printing farms, inventory, and producti
|
|||||||
KEYCLOAK_SECRET=your-client-secret
|
KEYCLOAK_SECRET=your-client-secret
|
||||||
|
|
||||||
# Application Configuration
|
# Application Configuration
|
||||||
APP_URL_CLIENT=http://localhost:3000
|
APP_URL_CLIENT=http://localhost:5173
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
||||||
# Optional: Scheduled Operations
|
# Optional: Scheduled Operations
|
||||||
|
|||||||
52
fcdev.js
Normal file
52
fcdev.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
async function syncModelsWithWS() {
|
||||||
|
const sourceDir = path.resolve(__dirname, 'src/schemas');
|
||||||
|
const targetDir = path.resolve(__dirname, '../farmcontrol-ws/src/database/schemas');
|
||||||
|
|
||||||
|
console.log(`Syncing schemas from ${sourceDir} to ${targetDir}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await syncDirectory(sourceDir, targetDir, sourceDir);
|
||||||
|
console.log('✅ Schema sync completed successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error syncing schemas:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncDirectory(source, target, rootSource) {
|
||||||
|
// Create target directory if it doesn't exist
|
||||||
|
try {
|
||||||
|
await fs.access(target);
|
||||||
|
} catch {
|
||||||
|
await fs.mkdir(target, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all items in source directory
|
||||||
|
const items = await fs.readdir(source, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const sourcePath = path.join(source, item.name);
|
||||||
|
const targetPath = path.join(target, item.name);
|
||||||
|
|
||||||
|
if (item.isDirectory()) {
|
||||||
|
// Recursively sync subdirectories
|
||||||
|
await syncDirectory(sourcePath, targetPath, rootSource);
|
||||||
|
} else if (item.isFile()) {
|
||||||
|
// Copy file from source to target
|
||||||
|
await fs.copyFile(sourcePath, targetPath);
|
||||||
|
console.log(` ✓ Copied: ${path.relative(rootSource, sourcePath)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the sync function when executed directly
|
||||||
|
syncModelsWithWS();
|
||||||
|
|
||||||
|
export { syncModelsWithWS };
|
||||||
13
nodemon.schemas.json
Normal file
13
nodemon.schemas.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"watch": ["src/schemas"],
|
||||||
|
"ext": "js",
|
||||||
|
"ignore": ["node_modules", "*.test.js", "*.spec.js", "../farmcontrol-ws"],
|
||||||
|
"exec": "node fcdev.js",
|
||||||
|
"delay": 2000,
|
||||||
|
"verbose": false,
|
||||||
|
"restartable": "rs",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development"
|
||||||
|
},
|
||||||
|
"colours": true
|
||||||
|
}
|
||||||
8127
package-lock.json
generated
8127
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@ -4,14 +4,15 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.0.0",
|
"@aws-sdk/client-s3": "^3.932.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.0.0",
|
"@aws-sdk/s3-request-presigner": "^3.932.0",
|
||||||
"@nats-io/transport-node": "^3.1.0",
|
"@nats-io/transport-node": "^3.2.0",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.13.2",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
|
"canonical-json": "^0.2.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.3",
|
||||||
"etcd3": "^1.1.2",
|
"etcd3": "^1.1.2",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
@ -20,26 +21,29 @@
|
|||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"keycloak-connect": "^26.1.1",
|
"keycloak-connect": "^26.1.1",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
"mongodb": "^6.18.0",
|
"mongodb": "^6.21.0",
|
||||||
"mongoose": "^8.17.1",
|
"mongoose": "^8.19.4",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.0.2",
|
||||||
|
"nanoid": "^5.1.6",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
"nodemailer": "*",
|
"nodemailer": "*",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.11",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
|
"redis": "^5.10.0",
|
||||||
"sequelize": "^6.37.7"
|
"sequelize": "^6.37.7"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.28.3",
|
"@babel/cli": "^7.28.3",
|
||||||
"@babel/core": "^7.28.3",
|
"@babel/core": "^7.28.5",
|
||||||
"@babel/node": "^7.28.0",
|
"@babel/node": "^7.28.0",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
||||||
"@babel/preset-env": "^7.28.3",
|
"@babel/preset-env": "^7.28.5",
|
||||||
"@babel/register": "^7.28.3",
|
"@babel/register": "^7.28.3",
|
||||||
"eslint": "^9.33.0",
|
"concurrently": "^9.2.1",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
@ -47,7 +51,10 @@
|
|||||||
"standard": "^17.1.2"
|
"standard": "^17.1.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon --exec babel-node --experimental-specifier-resolution=node src/index.js",
|
"syncModelsWithWS": "node fcdev.js",
|
||||||
|
"watch:schemas": "nodemon --config nodemon.schemas.json",
|
||||||
|
"dev": "concurrently --names \"API,SCHEMAS\" --prefix-colors \"cyan,yellow\" \"nodemon --exec babel-node --experimental-specifier-resolution=node src/index.js\" \"nodemon --config nodemon.schemas.json\"",
|
||||||
|
"dev:api": "nodemon --exec babel-node --experimental-specifier-resolution=node src/index.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"seed": "node src/mongo/seedData.js",
|
"seed": "node src/mongo/seedData.js",
|
||||||
"clear": "node src/mongo/clearDbs.js"
|
"clear": "node src/mongo/clearDbs.js"
|
||||||
|
|||||||
@ -1,21 +1,113 @@
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { fileModel } from '../schemas/management/file.schema.js';
|
import { fileModel } from '../schemas/management/file.schema.js';
|
||||||
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
deleteAuditLog,
|
deleteAuditLog,
|
||||||
distributeDelete,
|
distributeDelete,
|
||||||
expandObjectIds,
|
expandObjectIds,
|
||||||
modelHasRef,
|
modelHasRef,
|
||||||
getFieldsByRef,
|
getFieldsByRef,
|
||||||
|
jsonToCacheKey,
|
||||||
} from '../utils.js';
|
} from '../utils.js';
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import { editAuditLog, distributeUpdate, newAuditLog, distributeNew } from '../utils.js';
|
import {
|
||||||
|
editAuditLog,
|
||||||
|
distributeUpdate,
|
||||||
|
newAuditLog,
|
||||||
|
distributeNew,
|
||||||
|
distributeChildUpdate,
|
||||||
|
distributeChildDelete,
|
||||||
|
distributeChildNew,
|
||||||
|
} from '../utils.js';
|
||||||
import { getAllModels } from '../services/misc/model.js';
|
import { getAllModels } from '../services/misc/model.js';
|
||||||
|
import { redisServer } from './redis.js';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const logger = log4js.getLogger('Database');
|
const logger = log4js.getLogger('Database');
|
||||||
logger.level = process.env.LOG_LEVEL;
|
logger.level = process.env.LOG_LEVEL;
|
||||||
|
|
||||||
|
const cacheLogger = log4js.getLogger('DatabaseCache');
|
||||||
|
cacheLogger.level = process.env.LOG_LEVEL;
|
||||||
|
|
||||||
|
const CACHE_TTL_SECONDS = parseInt(process.env.REDIS_CACHE_TTL || '30', 10);
|
||||||
|
|
||||||
|
export const retrieveObjectCache = async ({ model, id, populate = [] }) => {
|
||||||
|
if (!model || !id) return undefined;
|
||||||
|
|
||||||
|
const cacheKeyObject = {
|
||||||
|
model: model.modelName,
|
||||||
|
id: id.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
||||||
|
|
||||||
|
cacheLogger.trace('Retrieving object from cache:', cacheKeyObject);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cachedObject = await redisServer.getKey(cacheKey);
|
||||||
|
if (cachedObject == null) {
|
||||||
|
cacheLogger.trace('Cache miss:', cacheKeyObject);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheLogger.trace('Cache hit:', {
|
||||||
|
model: model.modelName,
|
||||||
|
id: id.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return cachedObject;
|
||||||
|
} catch (err) {
|
||||||
|
cacheLogger.error('Error retrieving object from Redis cache:', err);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateObjectCache = async ({ model, id, object, populate = [] }) => {
|
||||||
|
if (!model || !id || !object) return object;
|
||||||
|
|
||||||
|
const cacheKeyObject = {
|
||||||
|
model: model.modelName,
|
||||||
|
id: id.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
||||||
|
|
||||||
|
cacheLogger.trace('Updating object cache:', cacheKeyObject);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cachedObject = (await redisServer.getKey(cacheKey)) || {};
|
||||||
|
const mergedObject = _.merge(cachedObject, object);
|
||||||
|
await redisServer.setKey(cacheKey, mergedObject, CACHE_TTL_SECONDS);
|
||||||
|
cacheLogger.trace('Updated object cache:', cacheKeyObject);
|
||||||
|
} catch (err) {
|
||||||
|
cacheLogger.error('Error updating object in Redis cache:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteObjectCache = async ({ model, id }) => {
|
||||||
|
if (!model || !id) return;
|
||||||
|
|
||||||
|
const cacheKeyObject = {
|
||||||
|
model: model.modelName,
|
||||||
|
id: id.toString(),
|
||||||
|
populate: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
||||||
|
|
||||||
|
cacheLogger.trace('Deleting object cache:', cacheKeyObject);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await redisServer.deleteKey(cacheKey);
|
||||||
|
cacheLogger.trace('Deleted object cache:', cacheKeyObject);
|
||||||
|
} catch (err) {
|
||||||
|
cacheLogger.error('Error deleting object from Redis cache:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Reusable function to list objects with aggregation, filtering, search, sorting, and pagination
|
// Reusable function to list objects with aggregation, filtering, search, sorting, and pagination
|
||||||
export const listObjects = async ({
|
export const listObjects = async ({
|
||||||
model,
|
model,
|
||||||
@ -47,6 +139,10 @@ export const listObjects = async ({
|
|||||||
sort = 'createdAt';
|
sort = 'createdAt';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
console.log('filter', filter);
|
||||||
|
}
|
||||||
|
|
||||||
// Translate any key ending with ._id to remove the ._id suffix for Mongoose
|
// Translate any key ending with ._id to remove the ._id suffix for Mongoose
|
||||||
Object.keys(filter).forEach((key) => {
|
Object.keys(filter).forEach((key) => {
|
||||||
if (key.endsWith('._id')) {
|
if (key.endsWith('._id')) {
|
||||||
@ -142,6 +238,8 @@ function nestGroups(groups, props, filter, idx = 0) {
|
|||||||
// Check if any group in this key matches the filter (by _id or name)
|
// Check if any group in this key matches the filter (by _id or name)
|
||||||
const matches = groupList.filter((group) => {
|
const matches = groupList.filter((group) => {
|
||||||
const { filterVals } = getKeyAndFilterVals(group._id[prop]);
|
const { filterVals } = getKeyAndFilterVals(group._id[prop]);
|
||||||
|
console.log('filterVals', filterVals);
|
||||||
|
console.log('filterValue', filterValue);
|
||||||
return filterVals.some((val) => val?.toString() === filterValue);
|
return filterVals.some((val) => val?.toString() === filterValue);
|
||||||
});
|
});
|
||||||
if (matches.length > 0) {
|
if (matches.length > 0) {
|
||||||
@ -269,6 +367,15 @@ export const listObjectsByProperties = async ({
|
|||||||
// Reusable function to get a single object by ID
|
// Reusable function to get a single object by ID
|
||||||
export const getObject = async ({ model, id, populate }) => {
|
export const getObject = async ({ model, id, populate }) => {
|
||||||
try {
|
try {
|
||||||
|
logger.trace('Getting object:', {
|
||||||
|
model,
|
||||||
|
id,
|
||||||
|
populate,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Try cache
|
||||||
|
const cachedObject = await retrieveObjectCache({ model, id, populate });
|
||||||
|
|
||||||
let query = model.findById(id).lean();
|
let query = model.findById(id).lean();
|
||||||
|
|
||||||
// Auto-populate file references if the model has them
|
// Auto-populate file references if the model has them
|
||||||
@ -297,7 +404,17 @@ export const getObject = async ({ model, id, populate }) => {
|
|||||||
return { error: 'Object not found.', code: 404 };
|
return { error: 'Object not found.', code: 404 };
|
||||||
}
|
}
|
||||||
|
|
||||||
return expandObjectIds(result);
|
const expanded = _.merge(cachedObject || {}, expandObjectIds(result));
|
||||||
|
|
||||||
|
// Update cache with the expanded object
|
||||||
|
await updateObjectCache({
|
||||||
|
model,
|
||||||
|
id: expanded._id,
|
||||||
|
object: expanded,
|
||||||
|
populate,
|
||||||
|
});
|
||||||
|
|
||||||
|
return expanded;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: error, code: 500 };
|
return { error: error, code: 500 };
|
||||||
}
|
}
|
||||||
@ -413,7 +530,24 @@ export const editObject = async ({ model, id, updateData, user, populate }) => {
|
|||||||
);
|
);
|
||||||
// Distribute update
|
// Distribute update
|
||||||
await distributeUpdate(updateData, id, parentType);
|
await distributeUpdate(updateData, id, parentType);
|
||||||
return { ...previousExpandedObject, ...updateData };
|
// Call childUpdate event for any child objects
|
||||||
|
await distributeChildUpdate(
|
||||||
|
previousExpandedObject,
|
||||||
|
{ ...previousExpandedObject, ...updateData },
|
||||||
|
id,
|
||||||
|
model
|
||||||
|
);
|
||||||
|
const updatedObject = { ...previousExpandedObject, ...updateData };
|
||||||
|
|
||||||
|
// Update cache with the new version
|
||||||
|
await updateObjectCache({
|
||||||
|
model,
|
||||||
|
id,
|
||||||
|
object: updatedObject,
|
||||||
|
populate,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedObject;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('editObject error:', error);
|
logger.error('editObject error:', error);
|
||||||
return { error: error.message, code: 500 };
|
return { error: error.message, code: 500 };
|
||||||
@ -435,6 +569,15 @@ export const newObject = async ({ model, newData, user = null }, distributeChang
|
|||||||
if (distributeChanges == true) {
|
if (distributeChanges == true) {
|
||||||
await distributeNew(created, parentType);
|
await distributeNew(created, parentType);
|
||||||
}
|
}
|
||||||
|
await distributeChildNew(created, created._id, model);
|
||||||
|
|
||||||
|
// Cache the newly created object
|
||||||
|
await updateObjectCache({
|
||||||
|
model,
|
||||||
|
id: created._id,
|
||||||
|
object: created,
|
||||||
|
populate: [],
|
||||||
|
});
|
||||||
|
|
||||||
return created;
|
return created;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -462,6 +605,11 @@ export const deleteObject = async ({ model, id, user = null }, distributeChanges
|
|||||||
await distributeDelete(deleted, parentType);
|
await distributeDelete(deleted, parentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await distributeChildDelete(deleted, id, model);
|
||||||
|
|
||||||
|
// Invalidate cache for this object
|
||||||
|
await deleteObjectCache({ model, id });
|
||||||
|
|
||||||
return { deleted: true, object: deleted };
|
return { deleted: true, object: deleted };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('deleteObject error:', error);
|
logger.error('deleteObject error:', error);
|
||||||
|
|||||||
68
src/database/redis.js
Normal file
68
src/database/redis.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { createClient } from 'redis';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
|
||||||
|
const REDIS_URL = process.env.REDIS_URL;
|
||||||
|
const REDIS_HOST = process.env.REDIS_HOST || '127.0.0.1';
|
||||||
|
const REDIS_PORT = process.env.REDIS_PORT || 6379;
|
||||||
|
const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
|
||||||
|
|
||||||
|
const logger = log4js.getLogger('Redis');
|
||||||
|
logger.level = LOG_LEVEL;
|
||||||
|
|
||||||
|
class RedisServer {
|
||||||
|
constructor() {
|
||||||
|
const url = REDIS_URL || `redis://${REDIS_HOST}:${REDIS_PORT}`;
|
||||||
|
|
||||||
|
this.client = createClient({
|
||||||
|
url,
|
||||||
|
password: REDIS_PASSWORD,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.on('error', (err) => {
|
||||||
|
logger.error('Redis Client Error', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
if (this.connected) return;
|
||||||
|
await this.client.connect();
|
||||||
|
this.connected = true;
|
||||||
|
logger.info('Connected to Redis');
|
||||||
|
}
|
||||||
|
|
||||||
|
async setKey(key, value, ttlSeconds) {
|
||||||
|
await this.connect();
|
||||||
|
const payload = typeof value === 'string' ? value : JSON.stringify(value);
|
||||||
|
if (ttlSeconds) {
|
||||||
|
await this.client.set(key, payload, { EX: ttlSeconds });
|
||||||
|
} else {
|
||||||
|
await this.client.set(key, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getKey(key) {
|
||||||
|
await this.connect();
|
||||||
|
const value = await this.client.get(key);
|
||||||
|
if (value == null) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteKey(key) {
|
||||||
|
await this.connect();
|
||||||
|
await this.client.del(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const redisServer = new RedisServer();
|
||||||
|
|
||||||
|
export { RedisServer, redisServer };
|
||||||
@ -43,7 +43,7 @@ import { initializeBuckets } from './services/storage/ceph.js';
|
|||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8080;
|
const PORT = process.env.PORT || 8787;
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const logger = log4js.getLogger('App');
|
const logger = log4js.getLogger('App');
|
||||||
@ -70,8 +70,8 @@ async function initializeApp() {
|
|||||||
dbConnect();
|
dbConnect();
|
||||||
|
|
||||||
// Connect to Etcd
|
// Connect to Etcd
|
||||||
etcdServer.connect();
|
//etcdServer.connect();
|
||||||
logger.info('Connected to Etcd');
|
//logger.info('Connected to Etcd');
|
||||||
|
|
||||||
// Connect to NATS
|
// Connect to NATS
|
||||||
natsServer.connect();
|
natsServer.connect();
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import jwt from 'jsonwebtoken';
|
|||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import NodeCache from 'node-cache';
|
import NodeCache from 'node-cache';
|
||||||
import { userModel } from './schemas/management/user.schema.js';
|
import { userModel } from './schemas/management/user.schema.js';
|
||||||
|
import { getObject } from './database/database.js';
|
||||||
|
import { hostModel } from './schemas/management/host.schema.js';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const logger = log4js.getLogger('Keycloak');
|
const logger = log4js.getLogger('Keycloak');
|
||||||
@ -120,6 +122,19 @@ const isAuthenticated = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Checking host authentication', req.headers);
|
||||||
|
|
||||||
|
const hostId = req.headers['x-host-id'];
|
||||||
|
const authCode = req.headers['x-auth-code'];
|
||||||
|
if (hostId && authCode) {
|
||||||
|
const host = await getObject({ model: hostModel, id: hostId });
|
||||||
|
if (host && host.authCode == authCode) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return res.status(401).json({ error: 'Not Authenticated', code: 'UNAUTHORIZED' });
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback to session-based authentication
|
// Fallback to session-based authentication
|
||||||
console.log('Using session token');
|
console.log('Using session token');
|
||||||
if (req.session && req.session['keycloak-token']) {
|
if (req.session && req.session['keycloak-token']) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { isAuthenticated } from '../../keycloak.js';
|
import { isAuthenticated } from '../../keycloak.js';
|
||||||
import { parseFilter } from '../../utils.js';
|
import { getFilter, convertPropertiesString } from '../../utils.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import {
|
||||||
@ -8,26 +8,27 @@ import {
|
|||||||
getPartStockRouteHandler,
|
getPartStockRouteHandler,
|
||||||
editPartStockRouteHandler,
|
editPartStockRouteHandler,
|
||||||
newPartStockRouteHandler,
|
newPartStockRouteHandler,
|
||||||
|
deletePartStockRouteHandler,
|
||||||
|
listPartStocksByPropertiesRouteHandler,
|
||||||
} from '../../services/inventory/partstocks.js';
|
} from '../../services/inventory/partstocks.js';
|
||||||
|
|
||||||
// list of partStocks
|
// list of part stocks
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, property } = req.query;
|
const { page, limit, property, search, sort, order } = req.query;
|
||||||
|
const allowedFilters = ['part', 'state', 'startingQuantity', 'currentQuantity', 'part._id'];
|
||||||
|
const filter = getFilter(req.query, allowedFilters);
|
||||||
|
listPartStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||||
|
});
|
||||||
|
|
||||||
const allowedFilters = ['country'];
|
router.get('/properties', isAuthenticated, (req, res) => {
|
||||||
|
let properties = convertPropertiesString(req.query.properties);
|
||||||
var filter = {};
|
const allowedFilters = ['part', 'state.type'];
|
||||||
|
const filter = getFilter(req.query, allowedFilters, false);
|
||||||
for (const [key, value] of Object.entries(req.query)) {
|
var masterFilter = {};
|
||||||
for (var i = 0; i < allowedFilters.length; i++) {
|
if (req.query.masterFilter) {
|
||||||
if (key == allowedFilters[i]) {
|
masterFilter = JSON.parse(req.query.masterFilter);
|
||||||
const parsedFilter = parseFilter(key, value);
|
|
||||||
filter = { ...filter, ...parsedFilter };
|
|
||||||
}
|
}
|
||||||
}
|
listPartStocksByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
|
||||||
}
|
|
||||||
|
|
||||||
listPartStocksRouteHandler(req, res, page, limit, property, filter);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/', isAuthenticated, (req, res) => {
|
router.post('/', isAuthenticated, (req, res) => {
|
||||||
@ -38,9 +39,12 @@ router.get('/:id', isAuthenticated, (req, res) => {
|
|||||||
getPartStockRouteHandler(req, res);
|
getPartStockRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
// update printer info
|
|
||||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||||
editPartStockRouteHandler(req, res);
|
editPartStockRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||||
|
deletePartStockRouteHandler(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,41 +1,50 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { isAuthenticated } from '../../keycloak.js';
|
import { isAuthenticated } from '../../keycloak.js';
|
||||||
import { parseFilter } from '../../utils.js';
|
import { getFilter, convertPropertiesString } from '../../utils.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import {
|
||||||
listStockEventsRouteHandler,
|
listStockEventsRouteHandler,
|
||||||
getStockEventRouteHandler,
|
getStockEventRouteHandler,
|
||||||
newStockEventRouteHandler,
|
newStockEventRouteHandler,
|
||||||
|
editStockEventRouteHandler,
|
||||||
|
deleteStockEventRouteHandler,
|
||||||
|
listStockEventsByPropertiesRouteHandler,
|
||||||
} from '../../services/inventory/stockevents.js';
|
} from '../../services/inventory/stockevents.js';
|
||||||
|
|
||||||
// List stock events
|
// list of stock events
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, sort, order } = req.query;
|
const { page, limit, sort, order } = req.query;
|
||||||
|
|
||||||
const allowedFilters = ['owner_.id', 'parent._id'];
|
const allowedFilters = ['owner_.id', 'parent._id'];
|
||||||
|
const filter = getFilter(req.query, allowedFilters);
|
||||||
var filter = {};
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(req.query)) {
|
|
||||||
for (var i = 0; i < allowedFilters.length; i++) {
|
|
||||||
if (key == allowedFilters[i]) {
|
|
||||||
const parsedFilter = parseFilter(key, value);
|
|
||||||
filter = { ...filter, ...parsedFilter };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
listStockEventsRouteHandler(req, res, page, limit, filter, sort, order);
|
listStockEventsRouteHandler(req, res, page, limit, filter, sort, order);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create new stock event
|
router.get('/properties', isAuthenticated, (req, res) => {
|
||||||
|
let properties = convertPropertiesString(req.query.properties);
|
||||||
|
const allowedFilters = ['owner_.id', 'parent._id'];
|
||||||
|
const filter = getFilter(req.query, allowedFilters, false);
|
||||||
|
var masterFilter = {};
|
||||||
|
if (req.query.masterFilter) {
|
||||||
|
masterFilter = JSON.parse(req.query.masterFilter);
|
||||||
|
}
|
||||||
|
listStockEventsByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
|
||||||
|
});
|
||||||
|
|
||||||
router.post('/', isAuthenticated, (req, res) => {
|
router.post('/', isAuthenticated, (req, res) => {
|
||||||
newStockEventRouteHandler(req, res);
|
newStockEventRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get specific stock event
|
|
||||||
router.get('/:id', isAuthenticated, (req, res) => {
|
router.get('/:id', isAuthenticated, (req, res) => {
|
||||||
getStockEventRouteHandler(req, res);
|
getStockEventRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||||
|
editStockEventRouteHandler(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||||
|
deleteStockEventRouteHandler(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
getGCodeFileRouteHandler,
|
getGCodeFileRouteHandler,
|
||||||
newGCodeFileRouteHandler,
|
newGCodeFileRouteHandler,
|
||||||
listGCodeFilesByPropertiesRouteHandler,
|
listGCodeFilesByPropertiesRouteHandler,
|
||||||
|
getGCodeFileContentRouteHandler,
|
||||||
} from '../../services/production/gcodefiles.js';
|
} from '../../services/production/gcodefiles.js';
|
||||||
import { convertPropertiesString, getFilter } from '../../utils.js';
|
import { convertPropertiesString, getFilter } from '../../utils.js';
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ router.get('/', isAuthenticated, (req, res) => {
|
|||||||
|
|
||||||
router.get('/properties', isAuthenticated, (req, res) => {
|
router.get('/properties', isAuthenticated, (req, res) => {
|
||||||
let properties = convertPropertiesString(req.query.properties);
|
let properties = convertPropertiesString(req.query.properties);
|
||||||
const allowedFilters = ['tags'];
|
const allowedFilters = ['filament'];
|
||||||
const filter = getFilter(req.query, allowedFilters, false);
|
const filter = getFilter(req.query, allowedFilters, false);
|
||||||
listGCodeFilesByPropertiesRouteHandler(req, res, properties, filter);
|
listGCodeFilesByPropertiesRouteHandler(req, res, properties, filter);
|
||||||
});
|
});
|
||||||
@ -35,6 +36,10 @@ router.get('/:id', isAuthenticated, (req, res) => {
|
|||||||
getGCodeFileRouteHandler(req, res);
|
getGCodeFileRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/:id/content', isAuthenticated, (req, res) => {
|
||||||
|
getGCodeFileContentRouteHandler(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
// update gcodeFile info
|
// update gcodeFile info
|
||||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||||
editGCodeFileRouteHandler(req, res);
|
editGCodeFileRouteHandler(req, res);
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { convertPropertiesString, getFilter } from '../../utils.js';
|
|||||||
// list of printers
|
// list of printers
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, property, search, sort, order } = req.query;
|
const { page, limit, property, search, sort, order } = req.query;
|
||||||
const allowedFilters = ['tags', 'host._id'];
|
const allowedFilters = ['tags', 'host._id', 'active', 'online', 'name'];
|
||||||
const filter = getFilter(req.query, allowedFilters);
|
const filter = getFilter(req.query, allowedFilters);
|
||||||
listPrintersRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
listPrintersRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
listSubJobsByPropertiesRouteHandler,
|
listSubJobsByPropertiesRouteHandler,
|
||||||
getSubJobRouteHandler,
|
getSubJobRouteHandler,
|
||||||
} from '../../services/production/subjobs.js';
|
} from '../../services/production/subjobs.js';
|
||||||
import { getFilter } from '../../utils.js';
|
import { getFilter, convertPropertiesString } from '../../utils.js';
|
||||||
|
|
||||||
// list of sub jobs
|
// list of sub jobs
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
// Define the main filamentStock schema
|
// Define the main filamentStock schema
|
||||||
const filamentStockSchema = new Schema(
|
const filamentStockSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
state: {
|
state: {
|
||||||
type: { type: String, required: true },
|
type: { type: String, required: true },
|
||||||
percent: { type: String, required: true },
|
progress: { type: Number, required: false },
|
||||||
},
|
},
|
||||||
startingWeight: {
|
startingWeight: {
|
||||||
net: { type: Number, required: true },
|
net: { type: Number, required: true },
|
||||||
@ -16,14 +18,14 @@ const filamentStockSchema = new Schema(
|
|||||||
net: { type: Number, required: true },
|
net: { type: Number, required: true },
|
||||||
gross: { type: Number, required: true },
|
gross: { type: Number, required: true },
|
||||||
},
|
},
|
||||||
filament: { type: mongoose.Schema.Types.ObjectId, ref: 'filament' },
|
filament: { type: mongoose.Schema.Types.ObjectId, ref: 'filament', required: true },
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
filamentStockSchema.virtual('id').get(function () {
|
filamentStockSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
// Define the main partStock schema
|
// Define the main partStock schema
|
||||||
const partStockSchema = new Schema(
|
const partStockSchema = new Schema(
|
||||||
{
|
{
|
||||||
name: { type: String, required: true },
|
_reference: { type: String, default: () => generateId()() },
|
||||||
fileName: { type: String, required: false },
|
state: {
|
||||||
part: { type: mongoose.Schema.Types.ObjectId, ref: 'part' },
|
type: { type: String, required: true },
|
||||||
|
progress: { type: Number, required: false },
|
||||||
|
},
|
||||||
|
part: { type: mongoose.Schema.Types.ObjectId, ref: 'part', required: true },
|
||||||
startingQuantity: { type: Number, required: true },
|
startingQuantity: { type: Number, required: true },
|
||||||
currentQuantity: { type: Number, required: true },
|
currentQuantity: { type: Number, required: true },
|
||||||
},
|
},
|
||||||
@ -15,7 +19,7 @@ const partStockSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
partStockSchema.virtual('id').get(function () {
|
partStockSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const stockAuditItemSchema = new Schema({
|
const stockAuditItemSchema = new Schema({
|
||||||
@ -11,6 +12,7 @@ const stockAuditItemSchema = new Schema({
|
|||||||
|
|
||||||
const stockAuditSchema = new Schema(
|
const stockAuditSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
type: { type: String, required: true },
|
type: { type: String, required: true },
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -28,7 +30,7 @@ const stockAuditSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
stockAuditSchema.virtual('id').get(function () {
|
stockAuditSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const stockEventSchema = new Schema(
|
const stockEventSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
value: { type: Number, required: true },
|
value: { type: Number, required: true },
|
||||||
current: { type: Number, required: true },
|
|
||||||
unit: { type: String, required: true },
|
unit: { type: String, required: true },
|
||||||
parent: {
|
parent: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
@ -33,7 +34,7 @@ const stockEventSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
stockEventSchema.virtual('id').get(function () {
|
stockEventSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const auditLogSchema = new Schema(
|
const auditLogSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
changes: {
|
changes: {
|
||||||
old: { type: Object, required: false },
|
old: { type: Object, required: false },
|
||||||
new: { type: Object, required: false },
|
new: { type: Object, required: false },
|
||||||
@ -19,27 +21,6 @@ const auditLogSchema = new Schema(
|
|||||||
parentType: {
|
parentType: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
enum: [
|
|
||||||
'printer',
|
|
||||||
'job',
|
|
||||||
'subJob',
|
|
||||||
'filamentStock',
|
|
||||||
'stockEvent',
|
|
||||||
'vendor',
|
|
||||||
'part',
|
|
||||||
'host',
|
|
||||||
'file',
|
|
||||||
'product',
|
|
||||||
'material',
|
|
||||||
'filament',
|
|
||||||
'gcodeFile',
|
|
||||||
'noteType',
|
|
||||||
'note',
|
|
||||||
'user',
|
|
||||||
'documentSize',
|
|
||||||
'documentTemplate',
|
|
||||||
'documentPrinter',
|
|
||||||
], // Add other models as needed
|
|
||||||
},
|
},
|
||||||
owner: {
|
owner: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
@ -57,7 +38,7 @@ const auditLogSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
auditLogSchema.virtual('id').get(function () {
|
auditLogSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,14 +1,21 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const documentJobSchema = new Schema(
|
const documentJobSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
unique: true,
|
unique: true,
|
||||||
},
|
},
|
||||||
objectType: { type: String, required: false },
|
objectType: { type: String, required: false },
|
||||||
|
object: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
refPath: 'objectType',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
state: {
|
state: {
|
||||||
type: { type: String, required: true, default: 'queued' },
|
type: { type: String, required: true, default: 'queued' },
|
||||||
percent: { type: Number, required: false },
|
percent: { type: Number, required: false },
|
||||||
@ -33,7 +40,7 @@ const documentJobSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
documentJobSchema.virtual('id').get(function () {
|
documentJobSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,28 +1,45 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
|
const connectionSchema = new Schema(
|
||||||
|
{
|
||||||
|
interface: { type: String, required: true },
|
||||||
|
protocol: { type: String, required: true },
|
||||||
|
host: { type: String, required: true },
|
||||||
|
port: { type: Number, required: false },
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
|
||||||
const documentPrinterSchema = new Schema(
|
const documentPrinterSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
unique: true,
|
unique: true,
|
||||||
},
|
},
|
||||||
|
connection: { type: connectionSchema, required: true },
|
||||||
|
currentDocumentSize: { type: Schema.Types.ObjectId, ref: 'documentSize', required: false },
|
||||||
tags: [{ type: String }],
|
tags: [{ type: String }],
|
||||||
online: { type: Boolean, required: true, default: false },
|
online: { type: Boolean, required: true, default: false },
|
||||||
|
active: { type: Boolean, required: true, default: true },
|
||||||
state: {
|
state: {
|
||||||
type: { type: String, required: true, default: 'offline' },
|
type: { type: String, required: true, default: 'offline' },
|
||||||
message: { type: String, required: false },
|
message: { type: String, required: false },
|
||||||
percent: { type: Number, required: false },
|
progress: { type: Number, required: false },
|
||||||
},
|
},
|
||||||
connectedAt: { type: Date, default: null },
|
connectedAt: { type: Date, default: null },
|
||||||
|
host: { type: Schema.Types.ObjectId, ref: 'host', required: true },
|
||||||
|
queue: [{ type: Schema.Types.ObjectId, ref: 'documentJob', required: false }],
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
documentPrinterSchema.virtual('id').get(function () {
|
documentPrinterSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const documentSizeSchema = new Schema(
|
const documentSizeSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@ -18,13 +20,18 @@ const documentSizeSchema = new Schema(
|
|||||||
required: true,
|
required: true,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
infiniteHeight: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
documentSizeSchema.virtual('id').get(function () {
|
documentSizeSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const documentTemplateSchema = new Schema(
|
const documentTemplateSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@ -52,7 +54,7 @@ const documentTemplateSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
documentTemplateSchema.virtual('id').get(function () {
|
documentTemplateSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const filamentSchema = new mongoose.Schema({
|
const filamentSchema = new mongoose.Schema({
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
barcode: { required: false, type: String },
|
barcode: { required: false, type: String },
|
||||||
url: { required: false, type: String },
|
url: { required: false, type: String },
|
||||||
@ -18,7 +20,7 @@ const filamentSchema = new mongoose.Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
filamentSchema.virtual('id').get(function () {
|
filamentSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
filamentSchema.set('toJSON', { virtuals: true });
|
filamentSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
|
|
||||||
const fileSchema = new mongoose.Schema(
|
const fileSchema = new mongoose.Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
type: { required: true, type: String },
|
type: { required: true, type: String },
|
||||||
extension: { required: true, type: String },
|
extension: { required: true, type: String },
|
||||||
@ -12,7 +14,7 @@ const fileSchema = new mongoose.Schema(
|
|||||||
);
|
);
|
||||||
|
|
||||||
fileSchema.virtual('id').get(function () {
|
fileSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
fileSchema.set('toJSON', { virtuals: true });
|
fileSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
|
|
||||||
// Define the device schema
|
// Define the device schema
|
||||||
const deviceInfoSchema = new mongoose.Schema(
|
const deviceInfoSchema = new mongoose.Schema(
|
||||||
@ -41,6 +42,7 @@ const deviceInfoSchema = new mongoose.Schema(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const hostSchema = new mongoose.Schema({
|
const hostSchema = new mongoose.Schema({
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
tags: [{ required: false, type: String }],
|
tags: [{ required: false, type: String }],
|
||||||
online: { required: true, type: Boolean, default: false },
|
online: { required: true, type: Boolean, default: false },
|
||||||
@ -53,10 +55,11 @@ const hostSchema = new mongoose.Schema({
|
|||||||
connectedAt: { required: false, type: Date },
|
connectedAt: { required: false, type: Date },
|
||||||
authCode: { type: { required: false, type: String } },
|
authCode: { type: { required: false, type: String } },
|
||||||
deviceInfo: { deviceInfoSchema },
|
deviceInfo: { deviceInfoSchema },
|
||||||
|
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
hostSchema.virtual('id').get(function () {
|
hostSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
hostSchema.set('toJSON', { virtuals: true });
|
hostSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
|
|
||||||
const materialSchema = new mongoose.Schema({
|
const materialSchema = new mongoose.Schema({
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
url: { required: false, type: String },
|
url: { required: false, type: String },
|
||||||
image: { required: false, type: Buffer },
|
image: { required: false, type: Buffer },
|
||||||
@ -8,7 +10,7 @@ const materialSchema = new mongoose.Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
materialSchema.virtual('id').get(function () {
|
materialSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
materialSchema.set('toJSON', { virtuals: true });
|
materialSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const noteTypeSchema = new Schema(
|
const noteTypeSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@ -23,7 +25,7 @@ const noteTypeSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
noteTypeSchema.virtual('id').get(function () {
|
noteTypeSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
// Define the main part schema
|
// Define the main part schema
|
||||||
const partSchema = new Schema(
|
const partSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { type: String, required: true },
|
name: { type: String, required: true },
|
||||||
fileName: { type: String, required: false },
|
fileName: { type: String, required: false },
|
||||||
product: { type: mongoose.Schema.Types.ObjectId, ref: 'product' },
|
|
||||||
globalPricing: { type: Boolean, default: true },
|
globalPricing: { type: Boolean, default: true },
|
||||||
priceMode: { type: String, default: 'margin' },
|
priceMode: { type: String, default: 'margin' },
|
||||||
amount: { type: Number, required: false },
|
amount: { type: Number, required: false },
|
||||||
@ -19,7 +20,7 @@ const partSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
partSchema.virtual('id').get(function () {
|
partSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
|
const partSchema = new Schema({
|
||||||
|
part: { type: Schema.Types.ObjectId, ref: 'part', required: true },
|
||||||
|
quantity: { type: Number, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
// Define the main product schema
|
// Define the main product schema
|
||||||
const productSchema = new Schema(
|
const productSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { type: String, required: true },
|
name: { type: String, required: true },
|
||||||
tags: [{ type: String }],
|
tags: [{ type: String }],
|
||||||
version: { type: String },
|
version: { type: String },
|
||||||
@ -11,12 +18,13 @@ const productSchema = new Schema(
|
|||||||
margin: { type: Number, required: false },
|
margin: { type: Number, required: false },
|
||||||
amount: { type: Number, required: false },
|
amount: { type: Number, required: false },
|
||||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
|
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
|
||||||
|
parts: [partSchema],
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
productSchema.virtual('id').get(function () {
|
productSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
|
|
||||||
const userSchema = new mongoose.Schema(
|
const userSchema = new mongoose.Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
username: { required: true, type: String },
|
username: { required: true, type: String },
|
||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
firstName: { required: false, type: String },
|
firstName: { required: false, type: String },
|
||||||
@ -13,7 +15,7 @@ const userSchema = new mongoose.Schema(
|
|||||||
);
|
);
|
||||||
|
|
||||||
userSchema.virtual('id').get(function () {
|
userSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
userSchema.set('toJSON', { virtuals: true });
|
userSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
|
|
||||||
const vendorSchema = new mongoose.Schema(
|
const vendorSchema = new mongoose.Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
website: { required: false, type: String },
|
website: { required: false, type: String },
|
||||||
email: { required: false, type: String },
|
email: { required: false, type: String },
|
||||||
@ -13,7 +15,7 @@ const vendorSchema = new mongoose.Schema(
|
|||||||
);
|
);
|
||||||
|
|
||||||
vendorSchema.virtual('id').get(function () {
|
vendorSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
vendorSchema.set('toJSON', { virtuals: true });
|
vendorSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const noteSchema = new mongoose.Schema({
|
const noteSchema = new mongoose.Schema({
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
parent: {
|
parent: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
refPath: 'parentType',
|
refPath: 'parentType',
|
||||||
@ -38,7 +40,7 @@ const noteSchema = new mongoose.Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
noteSchema.virtual('id').get(function () {
|
noteSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
noteSchema.set('toJSON', { virtuals: true });
|
noteSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,22 +1,30 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const gcodeFileSchema = new mongoose.Schema({
|
const partSchema = new mongoose.Schema({
|
||||||
|
part: { type: Schema.Types.ObjectId, ref: 'part', required: true },
|
||||||
|
quantity: { type: Number, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const gcodeFileSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
gcodeFileName: { required: false, type: String },
|
gcodeFileName: { required: false, type: String },
|
||||||
size: { type: Number, required: false },
|
size: { type: Number, required: false },
|
||||||
filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true },
|
filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true },
|
||||||
parts: [{ type: Schema.Types.ObjectId, ref: 'part', required: true }],
|
parts: [partSchema],
|
||||||
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
||||||
cost: { type: Number, required: false },
|
cost: { type: Number, required: false },
|
||||||
createdAt: { type: Date },
|
},
|
||||||
updatedAt: { type: Date },
|
{ timestamps: true }
|
||||||
});
|
);
|
||||||
|
|
||||||
gcodeFileSchema.index({ name: 'text', brand: 'text' });
|
gcodeFileSchema.index({ name: 'text', brand: 'text' });
|
||||||
|
|
||||||
gcodeFileSchema.virtual('id').get(function () {
|
gcodeFileSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
gcodeFileSchema.set('toJSON', { virtuals: true });
|
gcodeFileSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const jobSchema = new mongoose.Schema(
|
const jobSchema = new mongoose.Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
state: {
|
state: {
|
||||||
type: { required: true, type: String },
|
type: { required: true, type: String },
|
||||||
|
progress: { type: Number, required: false },
|
||||||
},
|
},
|
||||||
printers: [{ type: Schema.Types.ObjectId, ref: 'printer', required: false }],
|
printers: [{ type: Schema.Types.ObjectId, ref: 'printer', required: false }],
|
||||||
createdAt: { required: true, type: Date },
|
createdAt: { required: true, type: Date },
|
||||||
@ -29,7 +32,7 @@ const jobSchema = new mongoose.Schema(
|
|||||||
);
|
);
|
||||||
|
|
||||||
jobSchema.virtual('id').get(function () {
|
jobSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
jobSchema.set('toJSON', { virtuals: true });
|
jobSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
// Define the moonraker connection schema
|
// Define the moonraker connection schema
|
||||||
@ -12,12 +13,13 @@ const moonrakerSchema = new Schema(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Define the alert schema
|
|
||||||
const alertSchema = new Schema(
|
const alertSchema = new Schema(
|
||||||
{
|
{
|
||||||
priority: { type: String, required: true }, // order to show
|
type: { type: String, required: true }, // error, info, message
|
||||||
type: { type: String, required: true }, // selectFilament, error, info, message,
|
|
||||||
message: { type: String, required: false },
|
message: { type: String, required: false },
|
||||||
|
actions: [{ type: String, required: false, default: [] }],
|
||||||
|
_id: { type: String, required: true },
|
||||||
|
canDismiss: { type: Boolean, required: true, default: true },
|
||||||
},
|
},
|
||||||
{ timestamps: true, _id: false }
|
{ timestamps: true, _id: false }
|
||||||
);
|
);
|
||||||
@ -25,11 +27,14 @@ const alertSchema = new Schema(
|
|||||||
// Define the main printer schema
|
// Define the main printer schema
|
||||||
const printerSchema = new Schema(
|
const printerSchema = new Schema(
|
||||||
{
|
{
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { type: String, required: true },
|
name: { type: String, required: true },
|
||||||
online: { type: Boolean, required: true, default: false },
|
online: { type: Boolean, required: true, default: false },
|
||||||
|
active: { type: Boolean, required: true, default: true },
|
||||||
state: {
|
state: {
|
||||||
type: { type: String, required: true, default: 'offline' },
|
type: { type: String, required: true, default: 'offline' },
|
||||||
percent: { type: Number, required: false },
|
message: { type: String, required: false },
|
||||||
|
progress: { type: Number, required: false },
|
||||||
},
|
},
|
||||||
connectedAt: { type: Date, default: null },
|
connectedAt: { type: Date, default: null },
|
||||||
loadedFilament: {
|
loadedFilament: {
|
||||||
@ -43,7 +48,7 @@ const printerSchema = new Schema(
|
|||||||
currentJob: { type: Schema.Types.ObjectId, ref: 'job' },
|
currentJob: { type: Schema.Types.ObjectId, ref: 'job' },
|
||||||
currentSubJob: { type: Schema.Types.ObjectId, ref: 'subJob' },
|
currentSubJob: { type: Schema.Types.ObjectId, ref: 'subJob' },
|
||||||
currentFilamentStock: { type: Schema.Types.ObjectId, ref: 'filamentStock' },
|
currentFilamentStock: { type: Schema.Types.ObjectId, ref: 'filamentStock' },
|
||||||
subJobs: [{ type: Schema.Types.ObjectId, ref: 'subJob' }],
|
queue: [{ type: Schema.Types.ObjectId, ref: 'subJob' }],
|
||||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', default: null },
|
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', default: null },
|
||||||
host: { type: Schema.Types.ObjectId, ref: 'host', default: null },
|
host: { type: Schema.Types.ObjectId, ref: 'host', default: null },
|
||||||
alerts: [alertSchema],
|
alerts: [alertSchema],
|
||||||
@ -53,7 +58,7 @@ const printerSchema = new Schema(
|
|||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
printerSchema.virtual('id').get(function () {
|
printerSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const subJobSchema = new mongoose.Schema({
|
const subJobSchema = new mongoose.Schema({
|
||||||
|
_reference: { type: String, default: () => generateId()() },
|
||||||
printer: {
|
printer: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'printer',
|
ref: 'printer',
|
||||||
@ -12,7 +14,7 @@ const subJobSchema = new mongoose.Schema({
|
|||||||
ref: 'job',
|
ref: 'job',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
subJobId: {
|
moonrakerJobId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
@ -23,7 +25,7 @@ const subJobSchema = new mongoose.Schema({
|
|||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
type: { required: true, type: String },
|
type: { required: true, type: String },
|
||||||
percent: { required: false, type: Number },
|
progress: { required: false, type: Number },
|
||||||
},
|
},
|
||||||
number: {
|
number: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -42,7 +44,7 @@ const subJobSchema = new mongoose.Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
subJobSchema.virtual('id').get(function () {
|
subJobSchema.virtual('id').get(function () {
|
||||||
return this._id.toHexString();
|
return this._id;
|
||||||
});
|
});
|
||||||
|
|
||||||
subJobSchema.set('toJSON', { virtuals: true });
|
subJobSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -120,6 +120,7 @@ export const newFilamentStockRouteHandler = async (req, res) => {
|
|||||||
startingWeight: req.body.startingWeight,
|
startingWeight: req.body.startingWeight,
|
||||||
currentWeight: req.body.currentWeight,
|
currentWeight: req.body.currentWeight,
|
||||||
filament: req.body.filament,
|
filament: req.body.filament,
|
||||||
|
state: req.body.state,
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
model: filamentStockModel,
|
model: filamentStockModel,
|
||||||
|
|||||||
@ -2,10 +2,17 @@ import dotenv from 'dotenv';
|
|||||||
import { partStockModel } from '../../schemas/inventory/partstock.schema.js';
|
import { partStockModel } from '../../schemas/inventory/partstock.schema.js';
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import {
|
||||||
|
deleteObject,
|
||||||
|
listObjects,
|
||||||
|
getObject,
|
||||||
|
editObject,
|
||||||
|
newObject,
|
||||||
|
listObjectsByProperties,
|
||||||
|
} from '../../database/database.js';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const logger = log4js.getLogger('PartStocks');
|
const logger = log4js.getLogger('Part Stocks');
|
||||||
logger.level = process.env.LOG_LEVEL;
|
logger.level = process.env.LOG_LEVEL;
|
||||||
|
|
||||||
export const listPartStocksRouteHandler = async (
|
export const listPartStocksRouteHandler = async (
|
||||||
@ -14,126 +21,139 @@ export const listPartStocksRouteHandler = async (
|
|||||||
page = 1,
|
page = 1,
|
||||||
limit = 25,
|
limit = 25,
|
||||||
property = '',
|
property = '',
|
||||||
filter = {}
|
filter = {},
|
||||||
|
search = '',
|
||||||
|
sort = '',
|
||||||
|
order = 'ascend'
|
||||||
) => {
|
) => {
|
||||||
try {
|
const result = await listObjects({
|
||||||
// Calculate the skip value based on the page number and limit
|
model: partStockModel,
|
||||||
const skip = (page - 1) * limit;
|
page,
|
||||||
|
limit,
|
||||||
|
property,
|
||||||
|
filter,
|
||||||
|
search,
|
||||||
|
sort,
|
||||||
|
order,
|
||||||
|
populate: [{ path: 'part' }],
|
||||||
|
});
|
||||||
|
|
||||||
let partStock;
|
if (result?.error) {
|
||||||
let aggregateCommand = [];
|
logger.error('Error listing part stocks.');
|
||||||
|
res.status(result.code).send(result);
|
||||||
if (filter != {}) {
|
return;
|
||||||
// use filtering if present
|
|
||||||
aggregateCommand.push({ $match: filter });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property != '') {
|
logger.debug(`List of part stocks (Page ${page}, Limit ${limit}). Count: ${result.length}`);
|
||||||
aggregateCommand.push({ $group: { _id: `$${property}` } }); // group all same properties
|
res.send(result);
|
||||||
aggregateCommand.push({ $project: { _id: 0, [property]: '$_id' } }); // rename _id to the property name
|
};
|
||||||
} else {
|
|
||||||
aggregateCommand.push({ $project: { image: 0, url: 0 } });
|
export const listPartStocksByPropertiesRouteHandler = async (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
properties = '',
|
||||||
|
filter = {},
|
||||||
|
masterFilter = {}
|
||||||
|
) => {
|
||||||
|
const result = await listObjectsByProperties({
|
||||||
|
model: partStockModel,
|
||||||
|
properties,
|
||||||
|
filter,
|
||||||
|
populate: ['part'],
|
||||||
|
masterFilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result?.error) {
|
||||||
|
logger.error('Error listing part stocks.');
|
||||||
|
res.status(result.code).send(result);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregateCommand.push({ $skip: skip });
|
logger.debug(`List of part stocks. Count: ${result.length}`);
|
||||||
aggregateCommand.push({ $limit: Number(limit) });
|
res.send(result);
|
||||||
|
|
||||||
console.log(aggregateCommand);
|
|
||||||
|
|
||||||
partStock = await partStockModel.aggregate(aggregateCommand);
|
|
||||||
|
|
||||||
logger.trace(
|
|
||||||
`List of partStocks (Page ${page}, Limit ${limit}, Property ${property}):`,
|
|
||||||
partStock
|
|
||||||
);
|
|
||||||
res.send(partStock);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error listing partStocks:', error);
|
|
||||||
res.status(500).send({ error: error });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPartStockRouteHandler = async (req, res) => {
|
export const getPartStockRouteHandler = async (req, res) => {
|
||||||
try {
|
const id = req.params.id;
|
||||||
// Get ID from params
|
const result = await getObject({
|
||||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
model: partStockModel,
|
||||||
// Fetch the partStock with the given remote address
|
id,
|
||||||
const partStock = await partStockModel.findOne({
|
populate: [{ path: 'part' }],
|
||||||
_id: id,
|
|
||||||
});
|
});
|
||||||
|
if (result?.error) {
|
||||||
if (!partStock) {
|
logger.warn(`Part Stock not found with supplied id.`);
|
||||||
logger.warn(`PartStock not found with supplied id.`);
|
return res.status(result.code).send(result);
|
||||||
return res.status(404).send({ error: 'Print job not found.' });
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace(`PartStock with ID: ${id}:`, partStock);
|
|
||||||
res.send(partStock);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching PartStock:', error);
|
|
||||||
res.status(500).send({ error: error.message });
|
|
||||||
}
|
}
|
||||||
|
logger.debug(`Retreived part stock with ID: ${id}`);
|
||||||
|
res.send(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editPartStockRouteHandler = async (req, res) => {
|
export const editPartStockRouteHandler = async (req, res) => {
|
||||||
try {
|
|
||||||
// Get ID from params
|
// Get ID from params
|
||||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||||
// Fetch the partStock with the given remote address
|
|
||||||
const partStock = await partStockModel.findOne({ _id: id });
|
|
||||||
|
|
||||||
if (!partStock) {
|
logger.trace(`Part Stock with ID: ${id}`);
|
||||||
// Error handling
|
|
||||||
logger.warn(`PartStock not found with supplied id.`);
|
const updateData = {};
|
||||||
return res.status(404).send({ error: 'Print job not found.' });
|
// Create audit log before updating
|
||||||
|
const result = await editObject({
|
||||||
|
model: partStockModel,
|
||||||
|
id,
|
||||||
|
updateData,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
logger.error('Error editing part stock:', result.error);
|
||||||
|
res.status(result).send(result);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace(`PartStock with ID: ${id}:`, partStock);
|
logger.debug(`Edited part stock with ID: ${id}`);
|
||||||
|
|
||||||
try {
|
res.send(result);
|
||||||
const updateData = {
|
|
||||||
updatedAt: new Date(),
|
|
||||||
contact: req.body.contact,
|
|
||||||
country: req.body.country,
|
|
||||||
name: req.body.name,
|
|
||||||
website: req.body.website,
|
|
||||||
phone: req.body.phone,
|
|
||||||
email: req.body.email,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await partStockModel.updateOne({ _id: id }, { $set: updateData });
|
|
||||||
if (result.nModified === 0) {
|
|
||||||
logger.error('No PartStock updated.');
|
|
||||||
res.status(500).send({ error: 'No partStocks updated.' });
|
|
||||||
}
|
|
||||||
} catch (updateError) {
|
|
||||||
logger.error('Error updating partStock:', updateError);
|
|
||||||
res.status(500).send({ error: updateError.message });
|
|
||||||
}
|
|
||||||
res.send('OK');
|
|
||||||
} catch (fetchError) {
|
|
||||||
logger.error('Error fetching partStock:', fetchError);
|
|
||||||
res.status(500).send({ error: fetchError.message });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newPartStockRouteHandler = async (req, res) => {
|
export const newPartStockRouteHandler = async (req, res) => {
|
||||||
try {
|
const newData = {
|
||||||
let { ...newPartStock } = req.body;
|
|
||||||
newPartStock = {
|
|
||||||
...newPartStock,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
|
startingQuantity: req.body.startingQuantity,
|
||||||
|
currentQuantity: req.body.currentQuantity,
|
||||||
|
part: req.body.part,
|
||||||
|
state: req.body.state,
|
||||||
};
|
};
|
||||||
|
const result = await newObject({
|
||||||
|
model: partStockModel,
|
||||||
|
newData,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
if (result.error) {
|
||||||
|
logger.error('No part stock created:', result.error);
|
||||||
|
return res.status(result.code).send(result);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await partStockModel.create(newPartStock);
|
logger.debug(`New part stock with ID: ${result._id}`);
|
||||||
if (result.nCreated === 0) {
|
|
||||||
logger.error('No partStock created.');
|
res.send(result);
|
||||||
res.status(500).send({ error: 'No partStock created.' });
|
};
|
||||||
}
|
|
||||||
res.status(200).send({ status: 'ok' });
|
export const deletePartStockRouteHandler = async (req, res) => {
|
||||||
} catch (updateError) {
|
// Get ID from params
|
||||||
logger.error('Error updating partStock:', updateError);
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||||
res.status(500).send({ error: updateError.message });
|
|
||||||
}
|
logger.trace(`Part Stock with ID: ${id}`);
|
||||||
|
|
||||||
|
const result = await deleteObject({
|
||||||
|
model: partStockModel,
|
||||||
|
id,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
if (result.error) {
|
||||||
|
logger.error('No part stock deleted:', result.error);
|
||||||
|
return res.status(result.code).send(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Deleted part stock with ID: ${result._id}`);
|
||||||
|
|
||||||
|
res.send(result);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,14 @@ import dotenv from 'dotenv';
|
|||||||
import { stockEventModel } from '../../schemas/inventory/stockevent.schema.js';
|
import { stockEventModel } from '../../schemas/inventory/stockevent.schema.js';
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
import {
|
||||||
|
deleteObject,
|
||||||
|
listObjects,
|
||||||
|
getObject,
|
||||||
|
editObject,
|
||||||
|
newObject,
|
||||||
|
listObjectsByProperties,
|
||||||
|
} from '../../database/database.js';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const logger = log4js.getLogger('Stock Events');
|
const logger = log4js.getLogger('Stock Events');
|
||||||
@ -13,94 +21,147 @@ export const listStockEventsRouteHandler = async (
|
|||||||
page = 1,
|
page = 1,
|
||||||
limit = 25,
|
limit = 25,
|
||||||
filter = {},
|
filter = {},
|
||||||
sort = 'createdAt',
|
sort = '',
|
||||||
order = 'ascend'
|
order = 'ascend'
|
||||||
) => {
|
) => {
|
||||||
try {
|
const result = await listObjects({
|
||||||
// Calculate the skip value based on the page number and limit
|
model: stockEventModel,
|
||||||
const skip = (page - 1) * limit;
|
page,
|
||||||
const sortOrder = order === 'descend' ? 1 : -1;
|
limit,
|
||||||
|
filter,
|
||||||
|
sort,
|
||||||
|
order,
|
||||||
|
populate: [
|
||||||
|
{ path: 'owner', select: 'name _id' },
|
||||||
|
{ path: 'parent', select: 'name _id' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
if (!sort || sort != '') {
|
if (result?.error) {
|
||||||
sort = 'createdAt';
|
logger.error('Error listing stock events.');
|
||||||
|
res.status(result.code).send(result);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate parent._id to parent for Mongoose
|
logger.debug(`List of stock events (Page ${page}, Limit ${limit}). Count: ${result.length}`);
|
||||||
if (filter['parent._id']) {
|
res.send(result);
|
||||||
filter.parent = filter['parent._id'];
|
};
|
||||||
delete filter['parent._id'];
|
|
||||||
|
export const listStockEventsByPropertiesRouteHandler = async (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
properties = '',
|
||||||
|
filter = {},
|
||||||
|
masterFilter = {}
|
||||||
|
) => {
|
||||||
|
const result = await listObjectsByProperties({
|
||||||
|
model: stockEventModel,
|
||||||
|
properties,
|
||||||
|
filter,
|
||||||
|
populate: [
|
||||||
|
{ path: 'owner', select: 'name _id' },
|
||||||
|
{ path: 'parent', select: 'name _id' },
|
||||||
|
],
|
||||||
|
masterFilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result?.error) {
|
||||||
|
logger.error('Error listing stock events.');
|
||||||
|
res.status(result.code).send(result);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate owner._id to parent for Mongoose
|
logger.debug(`List of stock events. Count: ${result.length}`);
|
||||||
if (filter['owner._id']) {
|
res.send(result);
|
||||||
filter.owner = filter['owner._id'];
|
|
||||||
delete filter['owner._id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use find with population and filter
|
|
||||||
let query = stockEventModel
|
|
||||||
.find(filter)
|
|
||||||
.sort({ [sort]: sortOrder })
|
|
||||||
.skip(skip)
|
|
||||||
.limit(Number(limit))
|
|
||||||
.populate('owner', 'name _id')
|
|
||||||
.populate('parent', 'name _id');
|
|
||||||
|
|
||||||
const stockEvents = await query;
|
|
||||||
logger.trace(
|
|
||||||
`List of stock events (Page ${page}, Limit ${limit}, Sort ${sort}, Order ${order}):`,
|
|
||||||
stockEvents
|
|
||||||
);
|
|
||||||
res.send(stockEvents);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error listing stock events:', error);
|
|
||||||
res.status(500).send({ error: error });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStockEventRouteHandler = async (req, res) => {
|
export const getStockEventRouteHandler = async (req, res) => {
|
||||||
try {
|
const id = req.params.id;
|
||||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
const result = await getObject({
|
||||||
const stockEvent = await stockEventModel
|
model: stockEventModel,
|
||||||
.findOne({
|
id,
|
||||||
_id: id,
|
populate: [
|
||||||
})
|
{ path: 'owner', select: 'name _id' },
|
||||||
.populate('filamentStock')
|
{ path: 'parent', select: 'name _id' },
|
||||||
.populate('subJob')
|
],
|
||||||
.populate('job');
|
});
|
||||||
|
if (result?.error) {
|
||||||
if (!stockEvent) {
|
|
||||||
logger.warn(`Stock event not found with supplied id.`);
|
logger.warn(`Stock event not found with supplied id.`);
|
||||||
return res.status(404).send({ error: 'Stock event not found.' });
|
return res.status(result.code).send(result);
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace(`Stock event with ID: ${id}:`, stockEvent);
|
|
||||||
res.send(stockEvent);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching stock event:', error);
|
|
||||||
res.status(500).send({ error: error.message });
|
|
||||||
}
|
}
|
||||||
|
logger.debug(`Retrieved stock event with ID: ${id}`);
|
||||||
|
res.send(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newStockEventRouteHandler = async (req, res) => {
|
export const newStockEventRouteHandler = async (req, res) => {
|
||||||
try {
|
const newData = {
|
||||||
const newStockEvent = {
|
|
||||||
type: req.body.type,
|
|
||||||
value: req.body.value,
|
value: req.body.value,
|
||||||
subJob: req.body.subJob ? new mongoose.Types.ObjectId(req.body.subJob) : null,
|
current: req.body.current,
|
||||||
job: req.body.job ? new mongoose.Types.ObjectId(req.body.job) : null,
|
unit: req.body.unit,
|
||||||
filamentStock: new mongoose.Types.ObjectId(req.body.filamentStock),
|
parent: req.body.parent,
|
||||||
timestamp: new Date(),
|
parentType: req.body.parentType,
|
||||||
|
owner: req.body.owner,
|
||||||
|
ownerType: req.body.ownerType,
|
||||||
|
timestamp: req.body.timestamp || new Date(),
|
||||||
};
|
};
|
||||||
|
const result = await newObject({
|
||||||
|
model: stockEventModel,
|
||||||
|
newData,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
if (result.error) {
|
||||||
|
logger.error('No stock event created:', result.error);
|
||||||
|
return res.status(result.code).send(result);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await stockEventModel.create(newStockEvent);
|
logger.debug(`New stock event with ID: ${result._id}`);
|
||||||
if (!result) {
|
|
||||||
logger.error('No stock event created.');
|
res.send(result);
|
||||||
return res.status(500).send({ error: 'No stock event created.' });
|
};
|
||||||
}
|
|
||||||
return res.send({ status: 'ok', id: result._id });
|
export const editStockEventRouteHandler = async (req, res) => {
|
||||||
} catch (error) {
|
// Get ID from params
|
||||||
logger.error('Error adding stock event:', error);
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||||
return res.status(500).send({ error: error.message });
|
|
||||||
}
|
logger.trace(`Stock Event with ID: ${id}`);
|
||||||
|
|
||||||
|
const updateData = {};
|
||||||
|
// Create audit log before updating
|
||||||
|
const result = await editObject({
|
||||||
|
model: stockEventModel,
|
||||||
|
id,
|
||||||
|
updateData,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
logger.error('Error editing stock event:', result.error);
|
||||||
|
res.status(result.code).send(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Edited stock event with ID: ${id}`);
|
||||||
|
|
||||||
|
res.send(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteStockEventRouteHandler = async (req, res) => {
|
||||||
|
// Get ID from params
|
||||||
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||||
|
|
||||||
|
logger.trace(`Stock Event with ID: ${id}`);
|
||||||
|
|
||||||
|
const result = await deleteObject({
|
||||||
|
model: stockEventModel,
|
||||||
|
id,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
if (result.error) {
|
||||||
|
logger.error('No stock event deleted:', result.error);
|
||||||
|
return res.status(result.code).send(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Deleted stock event with ID: ${result._id || id}`);
|
||||||
|
|
||||||
|
res.send(result);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -74,6 +74,7 @@ export const getDocumentJobRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: documentJobModel,
|
model: documentJobModel,
|
||||||
id,
|
id,
|
||||||
|
populate: ['documentTemplate', 'documentPrinter', 'object'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Document Job not found with supplied id.`);
|
logger.warn(`Document Job not found with supplied id.`);
|
||||||
@ -89,12 +90,7 @@ export const editDocumentJobRouteHandler = async (req, res) => {
|
|||||||
|
|
||||||
logger.trace(`Document Job with ID: ${id}`);
|
logger.trace(`Document Job with ID: ${id}`);
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {};
|
||||||
updatedAt: new Date(),
|
|
||||||
name: req.body.name,
|
|
||||||
width: req.body.width,
|
|
||||||
height: req.body.height,
|
|
||||||
};
|
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
model: documentJobModel,
|
model: documentJobModel,
|
||||||
@ -118,8 +114,14 @@ export const newDocumentJobRouteHandler = async (req, res) => {
|
|||||||
const newData = {
|
const newData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
width: req.body.width,
|
documentPrinter: req.body.documentPrinter,
|
||||||
height: req.body.height,
|
documentTemplate: req.body.documentTemplate,
|
||||||
|
objectType: req.body.objectType,
|
||||||
|
object: req.body.object,
|
||||||
|
content: req.body.content,
|
||||||
|
state: { type: 'draft' },
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
model: documentJobModel,
|
model: documentJobModel,
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const listDocumentPrintersRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: ['documentSize'],
|
populate: ['currentDocumentSize', 'host'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -60,7 +60,7 @@ export const listDocumentPrintersByPropertiesRouteHandler = async (
|
|||||||
model: documentPrinterModel,
|
model: documentPrinterModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: ['documentSize'],
|
populate: ['currentDocumentSize', 'host'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -78,7 +78,7 @@ export const getDocumentPrinterRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: documentPrinterModel,
|
model: documentPrinterModel,
|
||||||
id,
|
id,
|
||||||
populate: ['documentSize'],
|
populate: ['currentDocumentSize', 'host'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Document Template not found with supplied id.`);
|
logger.warn(`Document Template not found with supplied id.`);
|
||||||
@ -99,10 +99,9 @@ export const editDocumentPrinterRouteHandler = async (req, res) => {
|
|||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
tags: req.body.tags,
|
tags: req.body.tags,
|
||||||
active: req.body.active,
|
active: req.body.active,
|
||||||
global: req.body.global,
|
connection: req.body.connection,
|
||||||
parent: req.body.parent,
|
currentDocumentSize: req.body.currentDocumentSize,
|
||||||
documentSize: req.body.documentSize,
|
host: req.body.host,
|
||||||
documentPrinters: req.body.documentPrinters,
|
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -129,10 +128,9 @@ export const newDocumentPrinterRouteHandler = async (req, res) => {
|
|||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
tags: req.body.tags,
|
tags: req.body.tags,
|
||||||
active: req.body.active,
|
active: req.body.active,
|
||||||
isGlobal: req.body.isGlobal,
|
connection: req.body.connection,
|
||||||
globalDocumentPrinter: req.body.globalDocumentPrinter,
|
currentDocumentSize: req.body.currentDocumentSize,
|
||||||
documentSize: req.body.documentSize,
|
host: req.body.host,
|
||||||
documentPrinters: req.body.documentPrinters,
|
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
model: documentPrinterModel,
|
model: documentPrinterModel,
|
||||||
|
|||||||
@ -94,6 +94,7 @@ export const editDocumentSizeRouteHandler = async (req, res) => {
|
|||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
width: req.body.width,
|
width: req.body.width,
|
||||||
height: req.body.height,
|
height: req.body.height,
|
||||||
|
infiniteHeight: req.body.infiniteHeight,
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -120,6 +121,7 @@ export const newDocumentSizeRouteHandler = async (req, res) => {
|
|||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
width: req.body.width,
|
width: req.body.width,
|
||||||
height: req.body.height,
|
height: req.body.height,
|
||||||
|
infiniteHeight: req.body.infiniteHeight,
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
model: documentSizeModel,
|
model: documentSizeModel,
|
||||||
|
|||||||
@ -304,6 +304,11 @@ export const getFileContentRouteHandler = async (req, res) => {
|
|||||||
res.set('Content-Type', file.type || 'application/octet-stream');
|
res.set('Content-Type', file.type || 'application/octet-stream');
|
||||||
res.set('Content-Disposition', `attachment; filename="${file.name}${file.extension}"`);
|
res.set('Content-Disposition', `attachment; filename="${file.name}${file.extension}"`);
|
||||||
|
|
||||||
|
// Expose file size so clients can compute download progress
|
||||||
|
if (typeof file.size === 'number' && !Number.isNaN(file.size)) {
|
||||||
|
res.set('Content-Length', String(file.size));
|
||||||
|
}
|
||||||
|
|
||||||
// Stream or send buffer
|
// Stream or send buffer
|
||||||
if (body && typeof body.pipe === 'function') {
|
if (body && typeof body.pipe === 'function') {
|
||||||
// Handle stream errors
|
// Handle stream errors
|
||||||
@ -358,6 +363,12 @@ export const getFileContentRouteHandler = async (req, res) => {
|
|||||||
|
|
||||||
res.set('Content-Type', file.type || 'application/octet-stream');
|
res.set('Content-Type', file.type || 'application/octet-stream');
|
||||||
res.set('Content-Disposition', `inline; filename="${file.name}${file.extension || ''}"`);
|
res.set('Content-Disposition', `inline; filename="${file.name}${file.extension || ''}"`);
|
||||||
|
|
||||||
|
// Ensure Content-Length is set for progress events if possible.
|
||||||
|
const length =
|
||||||
|
typeof file.size === 'number' && !Number.isNaN(file.size) ? file.size : data.length;
|
||||||
|
res.set('Content-Length', String(length));
|
||||||
|
|
||||||
return res.send(data);
|
return res.send(data);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -76,6 +76,7 @@ export const getHostRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: hostModel,
|
model: hostModel,
|
||||||
id,
|
id,
|
||||||
|
populate: ['files'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Host not found with supplied id.`);
|
logger.warn(`Host not found with supplied id.`);
|
||||||
@ -96,6 +97,7 @@ export const editHostRouteHandler = async (req, res) => {
|
|||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
active: req.body.active,
|
active: req.body.active,
|
||||||
tags: req.body.tags,
|
tags: req.body.tags,
|
||||||
|
files: req.body.files,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const listPartsRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: ['vendor', 'product'],
|
populate: ['vendor'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -53,7 +53,7 @@ export const listPartsByPropertiesRouteHandler = async (req, res, properties = '
|
|||||||
model: partModel,
|
model: partModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: ['vendor', 'product'],
|
populate: ['vendor'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -71,7 +71,7 @@ export const getPartRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: partModel,
|
model: partModel,
|
||||||
id,
|
id,
|
||||||
populate: ['vendor', 'product'],
|
populate: ['vendor'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Part not found with supplied id.`);
|
logger.warn(`Part not found with supplied id.`);
|
||||||
@ -93,7 +93,6 @@ export const editPartRouteHandler = async (req, res) => {
|
|||||||
globalPricing: req.body.globalPricing,
|
globalPricing: req.body.globalPricing,
|
||||||
file: req.body?.file,
|
file: req.body?.file,
|
||||||
vendor: req.body?.vendor,
|
vendor: req.body?.vendor,
|
||||||
product: req.body?.product,
|
|
||||||
margin: req.body?.margin,
|
margin: req.body?.margin,
|
||||||
amount: req.body?.amount,
|
amount: req.body?.amount,
|
||||||
priceMode: req.body?.priceMode,
|
priceMode: req.body?.priceMode,
|
||||||
@ -124,7 +123,6 @@ export const newPartRouteHandler = async (req, res) => {
|
|||||||
globalPricing: req.body.globalPricing,
|
globalPricing: req.body.globalPricing,
|
||||||
file: req.body?.file,
|
file: req.body?.file,
|
||||||
vendor: req.body?.vendor,
|
vendor: req.body?.vendor,
|
||||||
product: req.body?.product,
|
|
||||||
margin: req.body?.margin,
|
margin: req.body?.margin,
|
||||||
amount: req.body?.amount,
|
amount: req.body?.amount,
|
||||||
priceMode: req.body?.priceMode,
|
priceMode: req.body?.priceMode,
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export const getProductRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: productModel,
|
model: productModel,
|
||||||
id,
|
id,
|
||||||
populate: ['vendor'],
|
populate: ['vendor', 'parts.part'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Product not found with supplied id.`);
|
logger.warn(`Product not found with supplied id.`);
|
||||||
@ -100,6 +100,8 @@ export const editProductRouteHandler = async (req, res) => {
|
|||||||
margin: req.body.margin,
|
margin: req.body.margin,
|
||||||
amount: req.body.amount,
|
amount: req.body.amount,
|
||||||
priceMode: req.body.priceMode,
|
priceMode: req.body.priceMode,
|
||||||
|
vendor: req.body.vendor,
|
||||||
|
parts: req.body.parts,
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -129,6 +131,8 @@ export const newProductRouteHandler = async (req, res) => {
|
|||||||
margin: req.body.margin,
|
margin: req.body.margin,
|
||||||
amount: req.body.amount,
|
amount: req.body.amount,
|
||||||
priceMode: req.body.priceMode,
|
priceMode: req.body.priceMode,
|
||||||
|
vendor: req.body.vendor,
|
||||||
|
parts: req.body.parts,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
|
|||||||
@ -17,6 +17,9 @@ import { noteModel } from '../../schemas/misc/note.schema.js';
|
|||||||
import { documentSizeModel } from '../../schemas/management/documentsize.schema.js';
|
import { documentSizeModel } from '../../schemas/management/documentsize.schema.js';
|
||||||
import { documentTemplateModel } from '../../schemas/management/documenttemplate.schema.js';
|
import { documentTemplateModel } from '../../schemas/management/documenttemplate.schema.js';
|
||||||
import { hostModel } from '../../schemas/management/host.schema.js';
|
import { hostModel } from '../../schemas/management/host.schema.js';
|
||||||
|
import { documentPrinterModel } from '../../schemas/management/documentprinter.schema.js';
|
||||||
|
import { documentJobModel } from '../../schemas/management/documentjob.schema.js';
|
||||||
|
import { fileModel } from '../../schemas/management/file.schema.js';
|
||||||
|
|
||||||
// Map prefixes to models and id fields
|
// Map prefixes to models and id fields
|
||||||
const PREFIX_MODEL_MAP = {
|
const PREFIX_MODEL_MAP = {
|
||||||
@ -39,7 +42,10 @@ const PREFIX_MODEL_MAP = {
|
|||||||
NTE: { model: noteModel, idField: '_id', type: 'note' },
|
NTE: { model: noteModel, idField: '_id', type: 'note' },
|
||||||
DSZ: { model: documentSizeModel, idField: '_id', type: 'documentSize' },
|
DSZ: { model: documentSizeModel, idField: '_id', type: 'documentSize' },
|
||||||
DTP: { model: documentTemplateModel, idField: '_id', type: 'documentTemplate' },
|
DTP: { model: documentTemplateModel, idField: '_id', type: 'documentTemplate' },
|
||||||
|
DPR: { model: documentPrinterModel, idField: '_id', type: 'documentPrinter' },
|
||||||
|
DJB: { model: documentJobModel, idField: '_id', type: 'documentJob' },
|
||||||
HST: { model: hostModel, idField: '_id', type: 'host' },
|
HST: { model: hostModel, idField: '_id', type: 'host' },
|
||||||
|
FLE: { model: fileModel, idField: '_id', type: 'file' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { getAllModels, getModelByName, getModelByPrefix, PREFIX_MODEL_MAP } from './model.js';
|
import { getAllModels, getModelByPrefix } from './model.js';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const logger = log4js.getLogger('Jobs');
|
const logger = log4js.getLogger('Spotlight');
|
||||||
logger.level = process.env.LOG_LEVEL;
|
logger.level = process.env.LOG_LEVEL;
|
||||||
|
|
||||||
// Helper function to build search filter from query parameters
|
// Helper function to build search filter from query parameters
|
||||||
@ -48,6 +48,7 @@ const trimSpotlightObject = (object, objectType) => {
|
|||||||
color: object.color || undefined,
|
color: object.color || undefined,
|
||||||
updatedAt: object.updatedAt || undefined,
|
updatedAt: object.updatedAt || undefined,
|
||||||
objectType: objectType || undefined,
|
objectType: objectType || undefined,
|
||||||
|
online: object.online || undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,6 +64,8 @@ export const getSpotlightRouteHandler = async (req, res) => {
|
|||||||
const delimiter = query.substring(3, 4);
|
const delimiter = query.substring(3, 4);
|
||||||
const suffix = query.substring(4);
|
const suffix = query.substring(4);
|
||||||
|
|
||||||
|
logger.trace(`Spotlight query: ${query}`);
|
||||||
|
|
||||||
if (delimiter == ':') {
|
if (delimiter == ':') {
|
||||||
const prefixEntry = getModelByPrefix(prefix);
|
const prefixEntry = getModelByPrefix(prefix);
|
||||||
if (!prefixEntry || !prefixEntry.model) {
|
if (!prefixEntry || !prefixEntry.model) {
|
||||||
@ -161,6 +164,7 @@ export const getSpotlightRouteHandler = async (req, res) => {
|
|||||||
res.status(200).send(deduped);
|
res.status(200).send(deduped);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
return res.status(200).send([]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in spotlight lookup:', error);
|
logger.error('Error in spotlight lookup:', error);
|
||||||
res.status(500).send({ error: error });
|
res.status(500).send({ error: error });
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
listObjectsByProperties,
|
listObjectsByProperties,
|
||||||
newObject,
|
newObject,
|
||||||
} from '../../database/database.js';
|
} from '../../database/database.js';
|
||||||
|
import { getFileContentRouteHandler } from '../management/files.js';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
@ -77,7 +78,7 @@ export const getGCodeFileRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: gcodeFileModel,
|
model: gcodeFileModel,
|
||||||
id,
|
id,
|
||||||
populate: ['filament'],
|
populate: ['filament', 'parts.part'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`GCodeFile not found with supplied id.`);
|
logger.warn(`GCodeFile not found with supplied id.`);
|
||||||
@ -87,17 +88,36 @@ export const getGCodeFileRouteHandler = async (req, res) => {
|
|||||||
res.send(result);
|
res.send(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getGCodeFileContentRouteHandler = async (req, res) => {
|
||||||
|
const id = req.params.id;
|
||||||
|
const result = await getObject({
|
||||||
|
model: gcodeFileModel,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result?.error) {
|
||||||
|
logger.error('Error getting gcodeFile content.');
|
||||||
|
res.status(result.code).send(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Retreived gcodeFile content with ID: ${id}`);
|
||||||
|
return getFileContentRouteHandler({ ...req, params: { id: result.file._id } }, res);
|
||||||
|
};
|
||||||
|
|
||||||
export const editGCodeFileRouteHandler = async (req, res) => {
|
export const editGCodeFileRouteHandler = async (req, res) => {
|
||||||
// Get ID from params
|
// Get ID from params
|
||||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||||
|
|
||||||
logger.trace(`GCodeFile with ID: ${id}`);
|
logger.trace(`GCodeFile with ID: ${id}`);
|
||||||
|
console.log('REQ.BODY', req.body);
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
file: req.body.file,
|
file: req.body.file,
|
||||||
filament: req.body.filament,
|
filament: req.body.filament,
|
||||||
|
parts: req.body.parts,
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -124,6 +144,7 @@ export const newGCodeFileRouteHandler = async (req, res) => {
|
|||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
file: req.body.file,
|
file: req.body.file,
|
||||||
filament: req.body.filament,
|
filament: req.body.filament,
|
||||||
|
parts: req.body.parts,
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
model: gcodeFileModel,
|
model: gcodeFileModel,
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export const listPrintersRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
|
populate: ['host'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -58,7 +59,7 @@ export const listPrintersByPropertiesRouteHandler = async (
|
|||||||
model: printerModel,
|
model: printerModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: 'vendor',
|
populate: ['vendor', 'host'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -99,6 +100,7 @@ export const editPrinterRouteHandler = async (req, res) => {
|
|||||||
tags: req.body.tags,
|
tags: req.body.tags,
|
||||||
vendor: req.body.vendor,
|
vendor: req.body.vendor,
|
||||||
host: req.body.host,
|
host: req.body.host,
|
||||||
|
active: req.body.active,
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -128,6 +130,7 @@ export const newPrinterRouteHandler = async (req, res) => {
|
|||||||
tags: req.body.tags,
|
tags: req.body.tags,
|
||||||
vendor: req.body.vendor,
|
vendor: req.body.vendor,
|
||||||
host: req.body.host,
|
host: req.body.host,
|
||||||
|
active: req.body.active,
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
model: printerModel,
|
model: printerModel,
|
||||||
|
|||||||
137
src/utils.js
137
src/utils.js
@ -3,6 +3,31 @@ import { auditLogModel } from './schemas/management/auditlog.schema.js';
|
|||||||
import exifr from 'exifr';
|
import exifr from 'exifr';
|
||||||
import { etcdServer } from './database/etcd.js';
|
import { etcdServer } from './database/etcd.js';
|
||||||
import { natsServer } from './database/nats.js';
|
import { natsServer } from './database/nats.js';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import canonicalize from 'canonical-json';
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const logger = log4js.getLogger('Utils');
|
||||||
|
logger.level = process.env.LOG_LEVEL;
|
||||||
|
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
|
||||||
|
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
|
const generateId = () => {
|
||||||
|
// 10 characters
|
||||||
|
return customAlphabet(ALPHABET, 12);
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildWildcardRegexPattern(input) {
|
||||||
|
// Escape all regex special chars except * (which we treat as a wildcard)
|
||||||
|
const escaped = input.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
// Convert * to "match anything"
|
||||||
|
const withWildcards = escaped.replace(/\*/g, '.*');
|
||||||
|
// Anchor so that, without *, this is an exact match
|
||||||
|
return `^${withWildcards}$`;
|
||||||
|
}
|
||||||
|
|
||||||
function parseFilter(property, value) {
|
function parseFilter(property, value) {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
@ -26,10 +51,12 @@ function parseFilter(property, value) {
|
|||||||
return { [property]: parseFloat(trimmed) };
|
return { [property]: parseFloat(trimmed) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to case-insensitive regex for non-numeric strings
|
// Default to case-insensitive regex for non-numeric strings.
|
||||||
|
// Supports * as a wildcard (e.g. "filament*" matches "filament stock").
|
||||||
|
const pattern = buildWildcardRegexPattern(trimmed);
|
||||||
return {
|
return {
|
||||||
[property]: {
|
[property]: {
|
||||||
$regex: trimmed,
|
$regex: pattern,
|
||||||
$options: 'i',
|
$options: 'i',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -396,6 +423,60 @@ async function distributeDelete(value, type) {
|
|||||||
await natsServer.publish(`${type}s.delete`, value);
|
await natsServer.publish(`${type}s.delete`, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function distributeChildUpdate(oldValue, newValue, id, model) {
|
||||||
|
const oldPopulatedObjects = populateObjects(oldValue, model) || [];
|
||||||
|
const oldPopulatedObjectIds = oldPopulatedObjects.map((populate) => populate._id.toString());
|
||||||
|
const newPopulatedObjects = populateObjects(newValue, model) || [];
|
||||||
|
const newPopulatedObjectIds = newPopulatedObjects.map((populate) => populate._id.toString());
|
||||||
|
|
||||||
|
for (const populated of oldPopulatedObjects) {
|
||||||
|
if (!newPopulatedObjectIds.includes(populated._id.toString())) {
|
||||||
|
logger.debug(
|
||||||
|
`Distributing child update for ${populated.ref}s.${populated._id}.events.childUpdate`
|
||||||
|
);
|
||||||
|
await natsServer.publish(`${populated.ref}s.${populated._id}.events.childUpdate`, {
|
||||||
|
type: 'childUpdate',
|
||||||
|
data: { parentId: id, parentType: model.modelName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const populated of newPopulatedObjects) {
|
||||||
|
if (!oldPopulatedObjectIds.includes(populated._id.toString())) {
|
||||||
|
logger.debug(
|
||||||
|
`Distributing child update for ${populated.ref}s.${populated._id}.events.childUpdate`
|
||||||
|
);
|
||||||
|
await natsServer.publish(`${populated.ref}s.${populated._id}.events.childUpdate`, {
|
||||||
|
type: 'childUpdate',
|
||||||
|
data: { parentId: id, parentType: model.modelName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function distributeChildDelete(value, id, model) {
|
||||||
|
const populatedObjects = populateObjects(value, model) || [];
|
||||||
|
for (const populated of populatedObjects) {
|
||||||
|
logger.debug(
|
||||||
|
`Distributing child delete for ${populated.ref}s.${populated._id}.events.childDelete`
|
||||||
|
);
|
||||||
|
await natsServer.publish(`${populated.ref}s.${populated._id}.events.childDelete`, {
|
||||||
|
type: 'childDelete',
|
||||||
|
data: { parentId: id, parentType: model.modelName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function distributeChildNew(value, id, model) {
|
||||||
|
const populatedObjects = populateObjects(value, model) || [];
|
||||||
|
for (const populated of populatedObjects) {
|
||||||
|
logger.debug(`Distributing child new for ${populated.ref}s.${populated._id}.events.childNew`);
|
||||||
|
await natsServer.publish(`${populated.ref}s.${populated._id}.events.childNew`, {
|
||||||
|
type: 'childNew',
|
||||||
|
data: { parentId: id, parentType: model.modelName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function flatternObjectIds(object) {
|
function flatternObjectIds(object) {
|
||||||
if (!object || typeof object !== 'object') {
|
if (!object || typeof object !== 'object') {
|
||||||
return object;
|
return object;
|
||||||
@ -464,6 +545,8 @@ function getFilter(query, allowedFilters, parse = true) {
|
|||||||
let filter = {};
|
let filter = {};
|
||||||
for (const [key, value] of Object.entries(query)) {
|
for (const [key, value] of Object.entries(query)) {
|
||||||
if (allowedFilters.includes(key)) {
|
if (allowedFilters.includes(key)) {
|
||||||
|
console.log('key', key);
|
||||||
|
console.log('value', value);
|
||||||
const parsedFilter = parse ? parseFilter(key, value) : { [key]: value };
|
const parsedFilter = parse ? parseFilter(key, value) : { [key]: value };
|
||||||
filter = { ...filter, ...parsedFilter };
|
filter = { ...filter, ...parsedFilter };
|
||||||
}
|
}
|
||||||
@ -546,6 +629,51 @@ function getFieldsByRef(model, refName) {
|
|||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a nested populate specification by walking the schema graph,
|
||||||
|
// instead of recursing over already-populated documents.
|
||||||
|
function buildDeepPopulateSpec(object, model, populated = new Set()) {
|
||||||
|
// prevent infinite recursion across cyclic model relationships
|
||||||
|
if (populated.has(model.modelName)) return [];
|
||||||
|
populated.add(model.modelName);
|
||||||
|
|
||||||
|
const schema = model.schema;
|
||||||
|
const populateSpec = [];
|
||||||
|
|
||||||
|
schema.eachPath((pathname, schemaType) => {
|
||||||
|
const directRef = schemaType.options?.ref;
|
||||||
|
const arrayRef = schemaType.caster?.options?.ref;
|
||||||
|
const ref = directRef || arrayRef;
|
||||||
|
if (!ref) return;
|
||||||
|
|
||||||
|
const refModel = model.db.model(ref);
|
||||||
|
const childPopulate = buildDeepPopulateSpec(object, refModel, populated);
|
||||||
|
|
||||||
|
const id = object[pathname]?._id || object[pathname];
|
||||||
|
|
||||||
|
if (id == null || !id) return;
|
||||||
|
|
||||||
|
if (childPopulate.length > 0) {
|
||||||
|
populateSpec.push({ path: pathname, populate: childPopulate, ref: ref, _id: id });
|
||||||
|
} else {
|
||||||
|
populateSpec.push({ path: pathname, ref: ref, _id: id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return populateSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateObjects(object, model, populated = new Set()) {
|
||||||
|
const populateSpec = buildDeepPopulateSpec(object, model, populated);
|
||||||
|
|
||||||
|
return populateSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonToCacheKey(obj) {
|
||||||
|
const normalized = canonicalize(obj);
|
||||||
|
const hash = crypto.createHash('sha256').update(normalized).digest('hex');
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
parseFilter,
|
parseFilter,
|
||||||
convertToCamelCase,
|
convertToCamelCase,
|
||||||
@ -558,9 +686,14 @@ export {
|
|||||||
distributeUpdate,
|
distributeUpdate,
|
||||||
distributeNew,
|
distributeNew,
|
||||||
distributeDelete,
|
distributeDelete,
|
||||||
|
distributeChildUpdate,
|
||||||
|
distributeChildDelete,
|
||||||
|
distributeChildNew,
|
||||||
getFilter, // <-- add here
|
getFilter, // <-- add here
|
||||||
convertPropertiesString,
|
convertPropertiesString,
|
||||||
getFileMeta,
|
getFileMeta,
|
||||||
modelHasRef,
|
modelHasRef,
|
||||||
getFieldsByRef,
|
getFieldsByRef,
|
||||||
|
jsonToCacheKey,
|
||||||
|
generateId,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user