Compare commits
No commits in common. "7f17f5f20528403a504b43db196aa36b6de5b680" and "6be53349b5a2f9f5452cf0cfb7923a2fbe9bf0c0" have entirely different histories.
7f17f5f205
...
6be53349b5
99
config.json
99
config.json
@ -1,54 +1,53 @@
|
|||||||
{
|
{
|
||||||
"development": {
|
"development": {
|
||||||
"server": {
|
"server": {
|
||||||
"port": 9090,
|
"port": 9090,
|
||||||
"logLevel": "debug"
|
"logLevel": "trace"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"enabled": true,
|
||||||
|
"keycloak": {
|
||||||
|
"url": "https://auth.tombutcher.work",
|
||||||
|
"realm": "master",
|
||||||
|
"clientId": "farmcontrol-client",
|
||||||
|
"clientSecret": "GPyh59xctRX83yfKWb83ShK6VEwHIvLF"
|
||||||
|
},
|
||||||
|
"requiredRoles": []
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"etcd": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 2379
|
||||||
|
},
|
||||||
|
"mongo": {
|
||||||
|
"url": "mongodb://192.168.68.53:27017/farmcontrol"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"otpExpiryMins": 0.5
|
||||||
},
|
},
|
||||||
"auth": {
|
"production": {
|
||||||
"enabled": true,
|
"server": {
|
||||||
"keycloak": {
|
"port": 8081,
|
||||||
"url": "https://auth.tombutcher.work",
|
"logLevel": "info"
|
||||||
"realm": "master",
|
},
|
||||||
"clientId": "farmcontrol-client",
|
"auth": {
|
||||||
"clientSecret": "GPyh59xctRX83yfKWb83ShK6VEwHIvLF"
|
"enabled": true,
|
||||||
},
|
"keycloak": {
|
||||||
"requiredRoles": []
|
"url": "https://auth.tombutcher.work",
|
||||||
},
|
"realm": "master",
|
||||||
"database": {
|
"clientId": "farmcontrol-client",
|
||||||
"etcd": {
|
"clientSecret": "GPyh59xctRX83yfKWb83ShK6VEwHIvLF"
|
||||||
"host": "localhost",
|
},
|
||||||
"port": 2379
|
"requiredRoles": []
|
||||||
},
|
},
|
||||||
"mongo": {
|
"database": {
|
||||||
"url": "mongodb://127.0.0.1:27017/farmcontrol"
|
"etcd": {
|
||||||
},
|
"host": "localhost",
|
||||||
"redis": { "host": "localhost", "port": 6379, "password": "" }
|
"port": 2379
|
||||||
},
|
},
|
||||||
"otpExpiryMins": 0.5
|
"mongo": {
|
||||||
},
|
"url": "mongodb://farmcontrol.tombutcher.local:27017/farmcontrol"
|
||||||
"production": {
|
}
|
||||||
"server": {
|
}
|
||||||
"port": 8081,
|
|
||||||
"logLevel": "info"
|
|
||||||
},
|
|
||||||
"auth": {
|
|
||||||
"enabled": true,
|
|
||||||
"keycloak": {
|
|
||||||
"url": "https://auth.tombutcher.work",
|
|
||||||
"realm": "master",
|
|
||||||
"clientId": "farmcontrol-client",
|
|
||||||
"clientSecret": "GPyh59xctRX83yfKWb83ShK6VEwHIvLF"
|
|
||||||
},
|
|
||||||
"requiredRoles": []
|
|
||||||
},
|
|
||||||
"database": {
|
|
||||||
"etcd": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 2379
|
|
||||||
},
|
|
||||||
"mongo": {
|
|
||||||
"url": "mongodb://farmcontrol.tombutcher.local:27017/farmcontrol"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
2850
package-lock.json
generated
2850
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@ -17,13 +17,13 @@
|
|||||||
"author": "Tom Butcher",
|
"author": "Tom Butcher",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nats-io/nats-core": "^3.2.0",
|
"@nats-io/nats-core": "^3.1.0",
|
||||||
"@nats-io/transport-node": "^3.2.0",
|
"@nats-io/transport-node": "^3.1.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.11.0",
|
||||||
"canonical-json": "^0.2.0",
|
"canonical-json": "^0.2.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.1",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"etcd3": "^1.1.2",
|
"etcd3": "^1.1.2",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
@ -31,23 +31,20 @@
|
|||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
"mongodb": "6",
|
"mongodb": "^6.18.0",
|
||||||
"mongoose": "^8.19.4",
|
"mongoose": "^8.17.1",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.5",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"posthtml": "^0.16.7",
|
"posthtml": "^0.16.6",
|
||||||
"puppeteer": "^24.31.0",
|
|
||||||
"redis": "^4.6.14",
|
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-adapter-mongo": "^2.0.5",
|
"socket.io-adapter-mongo": "^2.0.5",
|
||||||
"socketio-jwt": "^4.6.2"
|
"socketio-jwt": "^4.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.33.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
"nodemon": "^3.1.11",
|
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"standard": "^17.1.2"
|
"standard": "^17.1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,13 +16,11 @@ export class ActionManager {
|
|||||||
constructor(socketClient) {
|
constructor(socketClient) {
|
||||||
this.socketClient = socketClient;
|
this.socketClient = socketClient;
|
||||||
this.callbacks = new Map();
|
this.callbacks = new Map();
|
||||||
this.subscriptions = new Set();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async subscribeToObjectActions(id, objectType) {
|
async subscribeToObjectActions(id, objectType) {
|
||||||
logger.debug('Subscribing to object actions...', id, objectType);
|
logger.debug('Subscribing to object actions...', id, objectType);
|
||||||
const subject = `${objectType}s.${id}.actions`;
|
const subject = `${objectType}s.${id}.actions`;
|
||||||
const subscriptionKey = `${subject}:${this.socketClient.id}`;
|
|
||||||
|
|
||||||
await natsServer.subscribe(
|
await natsServer.subscribe(
|
||||||
subject,
|
subject,
|
||||||
@ -49,18 +47,12 @@ export class ActionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.subscriptions.add(subscriptionKey);
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeObjectActionsListener(id, objectType) {
|
async removeObjectActionsListener(id, objectType) {
|
||||||
const subject = `${objectType}s.${id}.actions`;
|
const subject = `${objectType}s.${id}.actions`;
|
||||||
const subscriptionKey = `${subject}:${this.socketClient.id}`;
|
|
||||||
|
|
||||||
await natsServer.removeSubscription(subject, this.socketClient.id);
|
await natsServer.removeSubscription(subject, this.socketClient.id);
|
||||||
|
|
||||||
this.subscriptions.delete(subscriptionKey);
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,28 +68,22 @@ export class ActionManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Subscribe to the response subject
|
// Subscribe to the response subject
|
||||||
const responseSubscriptionKey = `${subject}:${this.socketClient.socketId}`;
|
|
||||||
await natsServer.subscribe(
|
await natsServer.subscribe(
|
||||||
subject,
|
subject,
|
||||||
this.socketClient.socketId,
|
this.socketClient.socketId,
|
||||||
async (subject, value) => {
|
async (subject, value) => {
|
||||||
if (value.result) {
|
if (value.result) {
|
||||||
logger.trace('Calling result callback...');
|
logger.trace('Calling result callback...');
|
||||||
const storedCallback = this.callbacks.get(actionId) || undefined;
|
const storedCallback = this.callbacks.get(actionId);
|
||||||
await natsServer.removeSubscription(
|
await natsServer.removeSubscription(
|
||||||
subject,
|
subject,
|
||||||
this.socketClient.socketId
|
this.socketClient.socketId
|
||||||
);
|
);
|
||||||
this.subscriptions.delete(responseSubscriptionKey);
|
storedCallback(value.result);
|
||||||
if (storedCallback) {
|
|
||||||
storedCallback(value.result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.subscriptions.add(responseSubscriptionKey);
|
|
||||||
|
|
||||||
// Publish the action
|
// Publish the action
|
||||||
await natsServer.publish(`${objectType}s.${id}.actions`, {
|
await natsServer.publish(`${objectType}s.${id}.actions`, {
|
||||||
...action,
|
...action,
|
||||||
@ -113,20 +99,4 @@ export class ActionManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeAllListeners() {
|
|
||||||
logger.debug('Removing all action listeners...');
|
|
||||||
const removePromises = Array.from(this.subscriptions).map(
|
|
||||||
subscriptionKey => {
|
|
||||||
const [subject, socketId] = subscriptionKey.split(':');
|
|
||||||
return natsServer.removeSubscription(subject, socketId);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(removePromises);
|
|
||||||
this.subscriptions.clear();
|
|
||||||
this.callbacks.clear();
|
|
||||||
logger.debug(`Removed ${removePromises.length} action listener(s)`);
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import NodeCache from 'node-cache';
|
||||||
import {
|
import {
|
||||||
deleteAuditLog,
|
deleteAuditLog,
|
||||||
expandObjectIds,
|
expandObjectIds,
|
||||||
@ -9,8 +10,8 @@ import {
|
|||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import { loadConfig } from '../config.js';
|
import { loadConfig } from '../config.js';
|
||||||
|
import { userModel } from './schemas/management/user.schema.js';
|
||||||
import { jsonToCacheKey } from '../utils.js';
|
import { jsonToCacheKey } from '../utils.js';
|
||||||
import { redisServer } from './redis.js';
|
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
|
||||||
@ -19,40 +20,43 @@ const cacheLogger = log4js.getLogger('Local Cache');
|
|||||||
logger.level = config.server.logLevel;
|
logger.level = config.server.logLevel;
|
||||||
cacheLogger.level = config.server.logLevel;
|
cacheLogger.level = config.server.logLevel;
|
||||||
|
|
||||||
// Default cache TTL in seconds (similar to previous in-memory cache)
|
const objectCache = new NodeCache({
|
||||||
const CACHE_TTL_SECONDS = config.database?.redis?.ttlSeconds || 5;
|
stdTTL: 30, // 30 sec expiration
|
||||||
|
checkperiod: 600, // 30 sec periodic cleanup
|
||||||
|
useClones: false // Don't clone objects for better performance
|
||||||
|
});
|
||||||
|
const listCache = new NodeCache({
|
||||||
|
stdTTL: 30, // 30 sec expiration
|
||||||
|
checkperiod: 600, // 30 sec periodic cleanup
|
||||||
|
useClones: false // Don't clone objects for better performance
|
||||||
|
});
|
||||||
|
|
||||||
export const retrieveObjectCache = async ({ model, id, populate = [] }) => {
|
export const retrieveObjectCache = ({ model, id, populate = [] }) => {
|
||||||
const cacheKeyObject = {
|
const cacheKeyObject = {
|
||||||
model: model.modelName,
|
model: model.modelName,
|
||||||
id: id?.toString()
|
id,
|
||||||
|
populate
|
||||||
};
|
};
|
||||||
|
|
||||||
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
||||||
|
|
||||||
cacheLogger.trace('Retrieving:', cacheKeyObject);
|
cacheLogger.trace('Retrieving:');
|
||||||
|
const cachedObject = objectCache.get(cacheKey);
|
||||||
|
|
||||||
try {
|
if (cachedObject == undefined) {
|
||||||
const cachedObject = await redisServer.getKey(cacheKey);
|
cacheLogger.trace('Miss:', cacheKeyObject);
|
||||||
|
|
||||||
if (cachedObject == null) {
|
|
||||||
cacheLogger.trace('Miss:', cacheKeyObject);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheLogger.trace('Hit:', {
|
|
||||||
model: model.modelName,
|
|
||||||
id: cacheKeyObject.id
|
|
||||||
});
|
|
||||||
|
|
||||||
return cachedObject;
|
|
||||||
} catch (err) {
|
|
||||||
cacheLogger.error('Error retrieving object from Redis cache:', err);
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheLogger.trace('Hit:', {
|
||||||
|
model: model.modelName,
|
||||||
|
id
|
||||||
|
});
|
||||||
|
|
||||||
|
return cachedObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const retrieveListCache = async ({
|
export const retrieveListCache = ({
|
||||||
model,
|
model,
|
||||||
populate = [],
|
populate = [],
|
||||||
filter = {},
|
filter = {},
|
||||||
@ -62,6 +66,7 @@ export const retrieveListCache = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const cacheKeyObject = {
|
const cacheKeyObject = {
|
||||||
model: model.modelName,
|
model: model.modelName,
|
||||||
|
id,
|
||||||
populate,
|
populate,
|
||||||
filter,
|
filter,
|
||||||
sort,
|
sort,
|
||||||
@ -69,77 +74,61 @@ export const retrieveListCache = async ({
|
|||||||
order
|
order
|
||||||
};
|
};
|
||||||
|
|
||||||
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
|
||||||
|
|
||||||
cacheLogger.trace('Retrieving:', cacheKeyObject);
|
cacheLogger.trace('Retrieving:', cacheKeyObject);
|
||||||
|
|
||||||
try {
|
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
||||||
const cachedList = await redisServer.getKey(cacheKey);
|
|
||||||
|
|
||||||
if (cachedList != null) {
|
const cachedList = listCache.get(cacheKey);
|
||||||
cacheLogger.trace('Hit:', {
|
|
||||||
...cacheKeyObject,
|
|
||||||
length: cachedList.length
|
|
||||||
});
|
|
||||||
return cachedList;
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheLogger.trace('Miss:', {
|
if (cachedList != undefined) {
|
||||||
model: model.modelName
|
cacheLogger.trace('Hit:', {
|
||||||
|
...cacheKeyObject,
|
||||||
|
length: cachedList.length
|
||||||
});
|
});
|
||||||
return undefined;
|
return cachedList;
|
||||||
} catch (err) {
|
|
||||||
cacheLogger.error('Error retrieving list from Redis cache:', err);
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheLogger.trace('Miss:', {
|
||||||
|
model: model.modelName
|
||||||
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateObjectCache = async ({ model, id, object }) => {
|
export const updateObjectCache = ({ model, id, object, populate = [] }) => {
|
||||||
const cacheKeyObject = {
|
const cacheKeyObject = {
|
||||||
model: model.modelName,
|
model: model.modelName,
|
||||||
id: id?.toString()
|
id,
|
||||||
|
populate
|
||||||
};
|
};
|
||||||
|
|
||||||
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
||||||
|
|
||||||
cacheLogger.trace('Updating:', cacheKeyObject);
|
cacheLogger.trace('Updating:', cacheKeyObject);
|
||||||
|
|
||||||
try {
|
const cachedObject = objectCache.get(cacheKey) || {};
|
||||||
const cachedObject = (await redisServer.getKey(cacheKey)) || {};
|
const mergedObject = _.merge(cachedObject, object);
|
||||||
const mergedObject = _.merge(cachedObject, object);
|
|
||||||
|
|
||||||
await redisServer.setKey(cacheKey, mergedObject, CACHE_TTL_SECONDS);
|
objectCache.set(cacheKey, mergedObject);
|
||||||
cacheLogger.trace('Updated:', { ...cacheKeyObject });
|
|
||||||
|
|
||||||
return mergedObject;
|
cacheLogger.trace('Updated:', { ...cacheKeyObject });
|
||||||
} catch (err) {
|
|
||||||
cacheLogger.error('Error updating object in Redis cache:', err);
|
return mergedObject;
|
||||||
// Fallback to returning the provided object if cache fails
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteObjectCache = async ({ model, id }) => {
|
export const deleteObjectCache = ({ model, id }) => {
|
||||||
const cacheKeyObject = {
|
|
||||||
model: model.modelName,
|
|
||||||
id: id?.toString()
|
|
||||||
};
|
|
||||||
|
|
||||||
cacheLogger.trace('Deleting:', {
|
cacheLogger.trace('Deleting:', {
|
||||||
...cacheKeyObject
|
model: model.modelName,
|
||||||
|
id
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
modelCache.del(id);
|
||||||
// Note: we currently delete the non-populated key; populated variants will expire via TTL.
|
|
||||||
const cacheKey = jsonToCacheKey({ ...cacheKeyObject, populate: [] });
|
|
||||||
await redisServer.deleteKey(cacheKey);
|
|
||||||
|
|
||||||
cacheLogger.trace('Deleted:', {
|
cacheLogger.trace('Deleted:', {
|
||||||
...cacheKeyObject
|
model: model.modelName,
|
||||||
});
|
id
|
||||||
} catch (err) {
|
});
|
||||||
cacheLogger.error('Error deleting object from Redis cache:', err);
|
|
||||||
}
|
return mergedObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateListCache = ({
|
export const updateListCache = ({
|
||||||
@ -167,20 +156,14 @@ export const updateListCache = ({
|
|||||||
|
|
||||||
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
const cacheKey = jsonToCacheKey(cacheKeyObject);
|
||||||
|
|
||||||
return (async () => {
|
listCache.set(cacheKey, objects);
|
||||||
try {
|
|
||||||
await redisServer.setKey(cacheKey, objects, CACHE_TTL_SECONDS);
|
|
||||||
|
|
||||||
cacheLogger.trace('Updated:', {
|
cacheLogger.trace('Updated:', {
|
||||||
...cacheKeyObject,
|
...cacheKeyObject,
|
||||||
length: objects.length
|
length: objects.length
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
cacheLogger.error('Error updating list in Redis cache:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return objects;
|
return objects;
|
||||||
})();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reusable function to list objects with aggregation, filtering, search, sorting, and pagination
|
// Reusable function to list objects with aggregation, filtering, search, sorting, and pagination
|
||||||
@ -205,7 +188,7 @@ export const listObjects = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (cached == true) {
|
if (cached == true) {
|
||||||
const objectsCache = await retrieveListCache({
|
const objectsCache = retrieveObjectsCache({
|
||||||
model,
|
model,
|
||||||
populate,
|
populate,
|
||||||
filter,
|
filter,
|
||||||
@ -251,7 +234,7 @@ export const listObjects = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle select (projection)
|
// Handle select (projection)
|
||||||
if (project && Object.keys(project).length > 0) {
|
if (project != {}) {
|
||||||
query = query.select(project);
|
query = query.select(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +286,7 @@ export const getObject = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (cached == true) {
|
if (cached == true) {
|
||||||
const cachedObject = await retrieveObjectCache({ model, id, populate });
|
const cachedObject = retrieveObjectCache({ model, id, populate });
|
||||||
if (cachedObject != undefined) {
|
if (cachedObject != undefined) {
|
||||||
return cachedObject;
|
return cachedObject;
|
||||||
}
|
}
|
||||||
@ -360,41 +343,37 @@ export const editObject = async ({
|
|||||||
updateData,
|
updateData,
|
||||||
owner = undefined,
|
owner = undefined,
|
||||||
ownerType = undefined,
|
ownerType = undefined,
|
||||||
populate = [],
|
populate = []
|
||||||
auditLog = true
|
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
// Determine parentType from model name
|
// Determine parentType from model name
|
||||||
const parentType = model.modelName ? model.modelName : 'unknown';
|
const parentType = model.modelName ? model.modelName : 'unknown';
|
||||||
// Fetch the and update object
|
// Fetch the and update object
|
||||||
var query = model.findByIdAndUpdate(id, updateData).lean();
|
var query = model.findByIdAndUpdate(id, updateData).lean();
|
||||||
var newQuery = model.findById(id).lean();
|
|
||||||
|
|
||||||
if (populate) {
|
if (populate) {
|
||||||
if (Array.isArray(populate)) {
|
if (Array.isArray(populate)) {
|
||||||
for (const pop of populate) {
|
for (const pop of populate) {
|
||||||
query = query.populate(pop);
|
query = query.populate(pop);
|
||||||
newQuery = newQuery.populate(pop);
|
|
||||||
}
|
}
|
||||||
} else if (typeof populate === 'string' || typeof populate === 'object') {
|
} else if (typeof populate === 'string' || typeof populate === 'object') {
|
||||||
query = query.populate(populate);
|
query = query.populate(populate);
|
||||||
newQuery = newQuery.populate(populate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const previousObject = await query;
|
const previousObject = await query;
|
||||||
const newObject = await newQuery;
|
|
||||||
if (!previousObject || !newObject) {
|
if (!previousObject) {
|
||||||
return { error: `${parentType} not found.`, code: 404 };
|
return { error: `${parentType} not found.`, code: 404 };
|
||||||
}
|
}
|
||||||
const previousExpandedObject = expandObjectIds(previousObject);
|
|
||||||
const newExpandedObject = expandObjectIds(newObject);
|
|
||||||
|
|
||||||
if (auditLog == true && owner != undefined && ownerType != undefined) {
|
const previousExpandedObject = expandObjectIds(previousObject);
|
||||||
|
|
||||||
|
if (owner != undefined && ownerType != undefined) {
|
||||||
// Audit log before update
|
// Audit log before update
|
||||||
await editAuditLog(
|
await editAuditLog(
|
||||||
previousExpandedObject,
|
previousExpandedObject,
|
||||||
newExpandedObject,
|
{ ...previousExpandedObject, ...updateData },
|
||||||
id,
|
id,
|
||||||
parentType,
|
parentType,
|
||||||
owner,
|
owner,
|
||||||
@ -408,8 +387,7 @@ export const editObject = async ({
|
|||||||
updateObjectCache({
|
updateObjectCache({
|
||||||
model: model,
|
model: model,
|
||||||
id: id.toString(),
|
id: id.toString(),
|
||||||
object: { ...previousExpandedObject, ...updateData },
|
object: { ...previousExpandedObject, ...updateData }
|
||||||
populate
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...previousExpandedObject, ...updateData };
|
return { ...previousExpandedObject, ...updateData };
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
import { createClient } from 'redis';
|
|
||||||
import log4js from 'log4js';
|
|
||||||
import { loadConfig } from '../config.js';
|
|
||||||
|
|
||||||
const config = loadConfig();
|
|
||||||
const logger = log4js.getLogger('Redis');
|
|
||||||
logger.level = config.server.logLevel;
|
|
||||||
|
|
||||||
class RedisServer {
|
|
||||||
constructor() {
|
|
||||||
const redisConfig = config.database?.redis || {};
|
|
||||||
const host = redisConfig.host || '127.0.0.1';
|
|
||||||
const port = redisConfig.port || 6379;
|
|
||||||
const password = redisConfig.password || undefined;
|
|
||||||
const url = redisConfig.url || `redis://${host}:${port}`;
|
|
||||||
|
|
||||||
this.client = createClient({
|
|
||||||
url,
|
|
||||||
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 };
|
|
||||||
@ -1,14 +1,12 @@
|
|||||||
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 },
|
||||||
progress: { type: Number, required: false },
|
percent: { type: String, required: true },
|
||||||
},
|
},
|
||||||
startingWeight: {
|
startingWeight: {
|
||||||
net: { type: Number, required: true },
|
net: { type: Number, required: true },
|
||||||
@ -18,14 +16,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', required: true },
|
filament: { type: mongoose.Schema.Types.ObjectId, ref: 'filament' },
|
||||||
},
|
},
|
||||||
{ 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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,16 +1,12 @@
|
|||||||
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(
|
||||||
{
|
{
|
||||||
_reference: { type: String, default: () => generateId()() },
|
name: { type: String, required: true },
|
||||||
state: {
|
fileName: { type: String, required: false },
|
||||||
type: { type: String, required: true },
|
part: { type: mongoose.Schema.Types.ObjectId, ref: 'part' },
|
||||||
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 },
|
||||||
},
|
},
|
||||||
@ -19,7 +15,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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
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({
|
||||||
@ -12,7 +11,6 @@ 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,
|
||||||
@ -30,7 +28,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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
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,
|
||||||
@ -34,7 +33,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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,44 +1,60 @@
|
|||||||
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: true },
|
||||||
new: { type: Object, required: false },
|
new: { type: Object, required: true }
|
||||||
},
|
|
||||||
operation: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
parent: {
|
parent: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
refPath: 'parentType',
|
refPath: 'parentType',
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
parentType: {
|
parentType: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
enum: [
|
||||||
|
'printer',
|
||||||
|
'job',
|
||||||
|
'subJob',
|
||||||
|
'filamentStock',
|
||||||
|
'stockEvent',
|
||||||
|
'vendor',
|
||||||
|
'part',
|
||||||
|
'product',
|
||||||
|
'material',
|
||||||
|
'filament',
|
||||||
|
'gcodeFile',
|
||||||
|
'noteType',
|
||||||
|
'note',
|
||||||
|
'user',
|
||||||
|
'host'
|
||||||
|
] // Add other models as needed
|
||||||
},
|
},
|
||||||
owner: {
|
owner: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
refPath: 'ownerType',
|
refPath: 'ownerType',
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
ownerType: {
|
ownerType: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
enum: ['user', 'printer', 'host'],
|
enum: ['user', 'printer', 'host']
|
||||||
},
|
},
|
||||||
|
operation: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
enum: ['edit', 'new', 'delete']
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
auditLogSchema.virtual('id').get(function () {
|
auditLogSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
import mongoose from 'mongoose';
|
|
||||||
import { generateId } from '../../utils.js';
|
|
||||||
const { Schema } = mongoose;
|
|
||||||
|
|
||||||
const documentJobSchema = new Schema(
|
|
||||||
{
|
|
||||||
_reference: { type: String, default: () => generateId()() },
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
objectType: { type: String, required: false },
|
|
||||||
object: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
refPath: 'objectType',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
type: { type: String, required: true, default: 'queued' },
|
|
||||||
percent: { type: Number, required: false },
|
|
||||||
},
|
|
||||||
documentTemplate: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'documentTemplate',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
documentPrinter: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'documentPrinter',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ timestamps: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add virtual id getter
|
|
||||||
documentJobSchema.virtual('id').get(function () {
|
|
||||||
return this._id;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
|
||||||
documentJobSchema.set('toJSON', { virtuals: true });
|
|
||||||
|
|
||||||
export const documentJobModel = mongoose.model('documentJob', documentJobSchema);
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import mongoose from 'mongoose';
|
|
||||||
import { generateId } from '../../utils.js';
|
|
||||||
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(
|
|
||||||
{
|
|
||||||
_reference: { type: String, default: () => generateId()() },
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
connection: { type: connectionSchema, required: true },
|
|
||||||
currentDocumentSize: { type: Schema.Types.ObjectId, ref: 'documentSize', required: false },
|
|
||||||
tags: [{ type: String }],
|
|
||||||
online: { type: Boolean, required: true, default: false },
|
|
||||||
active: { type: Boolean, required: true, default: true },
|
|
||||||
state: {
|
|
||||||
type: { type: String, required: true, default: 'offline' },
|
|
||||||
message: { type: String, required: false },
|
|
||||||
progress: { type: Number, required: false },
|
|
||||||
},
|
|
||||||
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 }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add virtual id getter
|
|
||||||
documentPrinterSchema.virtual('id').get(function () {
|
|
||||||
return this._id;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
|
||||||
documentPrinterSchema.set('toJSON', { virtuals: true });
|
|
||||||
|
|
||||||
export const documentPrinterModel = mongoose.model('documentPrinter', documentPrinterSchema);
|
|
||||||
@ -1,10 +1,8 @@
|
|||||||
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,
|
||||||
@ -20,18 +18,13 @@ 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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
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,
|
||||||
@ -54,7 +52,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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
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 },
|
||||||
@ -20,7 +18,7 @@ const filamentSchema = new mongoose.Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
filamentSchema.virtual('id').get(function () {
|
filamentSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
filamentSchema.set('toJSON', { virtuals: true });
|
filamentSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
import mongoose from 'mongoose';
|
|
||||||
import { generateId } from '../../utils.js';
|
|
||||||
|
|
||||||
const fileSchema = new mongoose.Schema(
|
|
||||||
{
|
|
||||||
_reference: { type: String, default: () => generateId()() },
|
|
||||||
name: { required: true, type: String },
|
|
||||||
type: { required: true, type: String },
|
|
||||||
extension: { required: true, type: String },
|
|
||||||
size: { required: false, type: Number },
|
|
||||||
metaData: { required: false, type: Object },
|
|
||||||
},
|
|
||||||
{ timestamps: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
fileSchema.virtual('id').get(function () {
|
|
||||||
return this._id;
|
|
||||||
});
|
|
||||||
|
|
||||||
fileSchema.set('toJSON', { virtuals: true });
|
|
||||||
|
|
||||||
export const fileModel = mongoose.model('file', fileSchema);
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
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(
|
||||||
@ -10,56 +9,56 @@ const deviceInfoSchema = new mongoose.Schema(
|
|||||||
release: { type: String },
|
release: { type: String },
|
||||||
arch: { type: String },
|
arch: { type: String },
|
||||||
hostname: { type: String },
|
hostname: { type: String },
|
||||||
uptime: { type: Number },
|
uptime: { type: Number }
|
||||||
},
|
},
|
||||||
cpu: {
|
cpu: {
|
||||||
cores: { type: Number },
|
cores: { type: Number },
|
||||||
model: { type: String },
|
model: { type: String },
|
||||||
speedMHz: { type: Number },
|
speedMHz: { type: Number }
|
||||||
},
|
},
|
||||||
memory: {
|
memory: {
|
||||||
totalGB: { type: String }, // stored as string from .toFixed(2), could also use Number
|
totalGB: { type: String }, // stored as string from .toFixed(2), could also use Number
|
||||||
freeGB: { type: String },
|
freeGB: { type: String }
|
||||||
},
|
},
|
||||||
network: {
|
network: {
|
||||||
type: mongoose.Schema.Types.Mixed, // since it's an object with dynamic interface names
|
type: mongoose.Schema.Types.Mixed // since it's an object with dynamic interface names
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
uid: { type: Number },
|
uid: { type: Number },
|
||||||
gid: { type: Number },
|
gid: { type: Number },
|
||||||
username: { type: String },
|
username: { type: String },
|
||||||
homedir: { type: String },
|
homedir: { type: String },
|
||||||
shell: { type: String },
|
shell: { type: String }
|
||||||
},
|
},
|
||||||
process: {
|
process: {
|
||||||
nodeVersion: { type: String },
|
nodeVersion: { type: String },
|
||||||
pid: { type: Number },
|
pid: { type: Number },
|
||||||
cwd: { type: String },
|
cwd: { type: String },
|
||||||
execPath: { type: String },
|
execPath: { type: String }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
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 },
|
||||||
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 },
|
percent: { type: Number, required: false }
|
||||||
},
|
},
|
||||||
active: { required: true, type: Boolean, default: true },
|
active: { required: true, type: Boolean, default: true },
|
||||||
connectedAt: { required: false, type: Date },
|
connectedAt: { required: false, type: Date },
|
||||||
authCode: { type: { required: false, type: String } },
|
authCode: { required: false, type: String },
|
||||||
deviceInfo: { deviceInfoSchema },
|
otp: { required: false, type: String },
|
||||||
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }],
|
otpExpiresAt: { required: false, type: Date },
|
||||||
|
deviceInfo: deviceInfoSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
hostSchema.virtual('id').get(function () {
|
hostSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
hostSchema.set('toJSON', { virtuals: true });
|
hostSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
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 },
|
||||||
@ -10,7 +8,7 @@ const materialSchema = new mongoose.Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
materialSchema.virtual('id').get(function () {
|
materialSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
materialSchema.set('toJSON', { virtuals: true });
|
materialSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
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,
|
||||||
@ -25,7 +23,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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,26 +1,23 @@
|
|||||||
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 },
|
||||||
margin: { type: Number, required: false },
|
margin: { type: Number, required: false },
|
||||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
|
|
||||||
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
partSchema.virtual('id').get(function () {
|
partSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,16 +1,9 @@
|
|||||||
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 },
|
||||||
@ -18,13 +11,12 @@ 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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,21 +1,18 @@
|
|||||||
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 },
|
||||||
lastName: { required: false, type: String },
|
lastName: { required: false, type: String },
|
||||||
email: { required: true, type: String },
|
email: { required: true, type: String },
|
||||||
profileImage: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
userSchema.virtual('id').get(function () {
|
userSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
userSchema.set('toJSON', { virtuals: true });
|
userSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
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 },
|
||||||
@ -15,7 +13,7 @@ const vendorSchema = new mongoose.Schema(
|
|||||||
);
|
);
|
||||||
|
|
||||||
vendorSchema.virtual('id').get(function () {
|
vendorSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
vendorSchema.set('toJSON', { virtuals: true });
|
vendorSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,16 +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',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
parentType: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
@ -40,7 +33,7 @@ const noteSchema = new mongoose.Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
noteSchema.virtual('id').get(function () {
|
noteSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
noteSchema.set('toJSON', { virtuals: true });
|
noteSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,30 +1,22 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { generateId } from '../../utils.js';
|
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const partSchema = new mongoose.Schema({
|
const gcodeFileSchema = new mongoose.Schema({
|
||||||
part: { type: Schema.Types.ObjectId, ref: 'part', required: true },
|
name: { required: true, type: String },
|
||||||
quantity: { type: Number, required: true },
|
gcodeFileName: { required: false, type: String },
|
||||||
|
gcodeFileInfo: { required: true, type: Object },
|
||||||
|
size: { type: Number, required: false },
|
||||||
|
filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true },
|
||||||
|
parts: [{ type: Schema.Types.ObjectId, ref: 'part', required: true }],
|
||||||
|
cost: { type: Number, required: false },
|
||||||
|
createdAt: { type: Date },
|
||||||
|
updatedAt: { type: Date },
|
||||||
});
|
});
|
||||||
|
|
||||||
const gcodeFileSchema = new mongoose.Schema(
|
|
||||||
{
|
|
||||||
_reference: { type: String, default: () => generateId()() },
|
|
||||||
name: { required: true, type: String },
|
|
||||||
gcodeFileName: { required: false, type: String },
|
|
||||||
size: { type: Number, required: false },
|
|
||||||
filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true },
|
|
||||||
parts: [partSchema],
|
|
||||||
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
|
||||||
cost: { type: Number, required: false },
|
|
||||||
},
|
|
||||||
{ 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;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
gcodeFileSchema.set('toJSON', { virtuals: true });
|
gcodeFileSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,38 +1,32 @@
|
|||||||
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({
|
||||||
{
|
state: {
|
||||||
_reference: { type: String, default: () => generateId()() },
|
type: { required: true, type: String },
|
||||||
state: {
|
|
||||||
type: { required: true, type: String },
|
|
||||||
progress: { type: Number, required: false },
|
|
||||||
},
|
|
||||||
printers: [{ type: Schema.Types.ObjectId, ref: 'printer', required: false }],
|
|
||||||
createdAt: { required: true, type: Date },
|
|
||||||
updatedAt: { required: true, type: Date },
|
|
||||||
startedAt: { required: false, type: Date },
|
|
||||||
finishedAt: { required: false, type: Date },
|
|
||||||
gcodeFile: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'gcodeFile',
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
quantity: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
default: 1,
|
|
||||||
min: 1,
|
|
||||||
},
|
|
||||||
subJobs: [{ type: Schema.Types.ObjectId, ref: 'subJob', required: false }],
|
|
||||||
notes: [{ type: Schema.Types.ObjectId, ref: 'note', required: false }],
|
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
printers: [{ type: Schema.Types.ObjectId, ref: 'printer', required: false }],
|
||||||
);
|
createdAt: { required: true, type: Date },
|
||||||
|
updatedAt: { required: true, type: Date },
|
||||||
|
startedAt: { required: false, type: Date },
|
||||||
|
finishedAt: { required: false, type: Date },
|
||||||
|
gcodeFile: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'gcodeFile',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
default: 1,
|
||||||
|
min: 1,
|
||||||
|
},
|
||||||
|
subJobs: [{ type: Schema.Types.ObjectId, ref: 'subJob', required: false }],
|
||||||
|
notes: [{ type: Schema.Types.ObjectId, ref: 'note', required: false }],
|
||||||
|
});
|
||||||
|
|
||||||
jobSchema.virtual('id').get(function () {
|
jobSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
jobSchema.set('toJSON', { virtuals: true });
|
jobSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
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
|
||||||
@ -8,39 +7,35 @@ const moonrakerSchema = new Schema(
|
|||||||
host: { type: String, required: true },
|
host: { type: String, required: true },
|
||||||
port: { type: Number, required: true },
|
port: { type: Number, required: true },
|
||||||
protocol: { type: String, required: true },
|
protocol: { type: String, required: true },
|
||||||
apiKey: { type: String, default: null, required: false },
|
apiKey: { type: String, default: null, required: false }
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Define the alert schema
|
||||||
const alertSchema = new Schema(
|
const alertSchema = new Schema(
|
||||||
{
|
{
|
||||||
type: { type: String, required: true }, // error, info, message
|
priority: { type: String, required: true }, // order to show
|
||||||
message: { type: String, required: false },
|
type: { type: String, required: true }, // selectFilament, error, info, message
|
||||||
actions: [{ type: String, required: false, default: [] }],
|
message: { type: String, required: false }
|
||||||
_id: { type: String, required: true },
|
|
||||||
canDismiss: { type: Boolean, required: true, default: true },
|
|
||||||
},
|
},
|
||||||
{ timestamps: true, _id: false }
|
{ timestamps: true, _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Define the main printer schema
|
// Define the main FDM 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' },
|
||||||
message: { type: String, required: false },
|
progress: { type: Number, required: false, default: 0 }
|
||||||
progress: { type: Number, required: false },
|
|
||||||
},
|
},
|
||||||
connectedAt: { type: Date, default: null },
|
connectedAt: { type: Date, default: null },
|
||||||
loadedFilament: {
|
loadedFilament: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'filament',
|
ref: 'filament',
|
||||||
default: null,
|
default: null
|
||||||
},
|
},
|
||||||
moonraker: { type: moonrakerSchema, required: true },
|
moonraker: { type: moonrakerSchema, required: true },
|
||||||
tags: [{ type: String }],
|
tags: [{ type: String }],
|
||||||
@ -48,17 +43,27 @@ 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' },
|
||||||
queue: [{ type: Schema.Types.ObjectId, ref: 'subJob' }],
|
subJobs: [{ type: Schema.Types.ObjectId, ref: 'subJob' }],
|
||||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', default: null },
|
vendor: {
|
||||||
host: { type: Schema.Types.ObjectId, ref: 'host', default: null },
|
type: Schema.Types.ObjectId,
|
||||||
alerts: [alertSchema],
|
ref: 'vendor',
|
||||||
|
default: null,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'host',
|
||||||
|
default: null,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
alerts: [alertSchema]
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add virtual id getter
|
// Add virtual id getter
|
||||||
printerSchema.virtual('id').get(function () {
|
printerSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
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',
|
||||||
@ -14,9 +12,9 @@ const subJobSchema = new mongoose.Schema({
|
|||||||
ref: 'job',
|
ref: 'job',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
moonrakerJobId: {
|
subJobId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: true,
|
||||||
},
|
},
|
||||||
gcodeFile: {
|
gcodeFile: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
@ -25,7 +23,7 @@ const subJobSchema = new mongoose.Schema({
|
|||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
type: { required: true, type: String },
|
type: { required: true, type: String },
|
||||||
progress: { required: false, type: Number },
|
percent: { required: false, type: Number },
|
||||||
},
|
},
|
||||||
number: {
|
number: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -44,7 +42,7 @@ const subJobSchema = new mongoose.Schema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
subJobSchema.virtual('id').get(function () {
|
subJobSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id.toHexString();
|
||||||
});
|
});
|
||||||
|
|
||||||
subJobSchema.set('toJSON', { virtuals: true });
|
subJobSchema.set('toJSON', { virtuals: true });
|
||||||
|
|||||||
@ -2,14 +2,6 @@ import { ObjectId } from 'mongodb';
|
|||||||
import { auditLogModel } from './schemas/management/auditlog.schema.js';
|
import { auditLogModel } from './schemas/management/auditlog.schema.js';
|
||||||
import { natsServer } from './nats.js';
|
import { natsServer } from './nats.js';
|
||||||
|
|
||||||
import { customAlphabet } from 'nanoid';
|
|
||||||
|
|
||||||
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
||||||
export const generateId = () => {
|
|
||||||
// 10 characters
|
|
||||||
return customAlphabet(ALPHABET, 12);
|
|
||||||
};
|
|
||||||
|
|
||||||
function parseFilter(property, value) {
|
function parseFilter(property, value) {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
var trimmed = value.trim();
|
var trimmed = value.trim();
|
||||||
@ -317,18 +309,7 @@ function getChangedValues(oldObj, newObj, old = false) {
|
|||||||
} else {
|
} else {
|
||||||
const nestedChanges = getChangedValues(oldVal, newVal, old);
|
const nestedChanges = getChangedValues(oldVal, newVal, old);
|
||||||
if (Object.keys(nestedChanges).length > 0) {
|
if (Object.keys(nestedChanges).length > 0) {
|
||||||
// Exclude progress and currentWeight from nested changes
|
changes[key] = nestedChanges;
|
||||||
const excludeKeys = ['progress', 'currentWeight', 'net', 'gross'];
|
|
||||||
const filteredChanges = Object.keys(nestedChanges)
|
|
||||||
.filter(nestedKey => !excludeKeys.includes(nestedKey))
|
|
||||||
.reduce((acc, nestedKey) => {
|
|
||||||
acc[nestedKey] = nestedChanges[nestedKey];
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
if (Object.keys(filteredChanges).length > 0) {
|
|
||||||
changes[key] = filteredChanges;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
|
} else if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
|
||||||
@ -369,9 +350,6 @@ async function editAuditLog(
|
|||||||
owner,
|
owner,
|
||||||
ownerType
|
ownerType
|
||||||
) {
|
) {
|
||||||
if (parentType === 'stockEvent') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Get only the changed values
|
// Get only the changed values
|
||||||
const changedOldValues = getChangedValues(oldValue, newValue, true);
|
const changedOldValues = getChangedValues(oldValue, newValue, true);
|
||||||
const changedNewValues = getChangedValues(oldValue, newValue, false);
|
const changedNewValues = getChangedValues(oldValue, newValue, false);
|
||||||
|
|||||||
@ -14,16 +14,12 @@ logger.level = config.server.logLevel;
|
|||||||
export class EventManager {
|
export class EventManager {
|
||||||
constructor(socketClient) {
|
constructor(socketClient) {
|
||||||
this.socketClient = socketClient;
|
this.socketClient = socketClient;
|
||||||
this.subscriptions = new Set();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async subscribeToObjectEvent(id, objectType, eventType) {
|
async subscribeToObjectEvent(id, objectType, eventType) {
|
||||||
logger.debug('Subscribing to object event:', eventType, id, objectType);
|
logger.debug('Subscribing to object event:', eventType, id, objectType);
|
||||||
const subject = `${objectType}s.${id}.events.${eventType}`;
|
|
||||||
const subscriptionKey = `${subject}:${this.socketClient.socketId}`;
|
|
||||||
|
|
||||||
await natsServer.subscribe(
|
await natsServer.subscribe(
|
||||||
subject,
|
`${objectType}s.${id}.events.${eventType}`,
|
||||||
this.socketClient.socketId,
|
this.socketClient.socketId,
|
||||||
(key, value) => {
|
(key, value) => {
|
||||||
if (!value?.result) {
|
if (!value?.result) {
|
||||||
@ -36,19 +32,15 @@ export class EventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.subscriptions.add(subscriptionKey);
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeObjectEventsListener(id, objectType, eventType) {
|
async removeObjectEventsListener(id, objectType, eventType) {
|
||||||
// Remove specific event subscription for this object
|
// Remove specific event subscription for this object
|
||||||
const subject = `${objectType}s.${id}.events.${eventType}`;
|
await natsServer.removeSubscription(
|
||||||
const subscriptionKey = `${subject}:${this.socketClient.socketId}`;
|
`${objectType}s.${id}.events.${eventType}`,
|
||||||
|
this.socketClient.socketId
|
||||||
await natsServer.removeSubscription(subject, this.socketClient.socketId);
|
);
|
||||||
|
|
||||||
this.subscriptions.delete(subscriptionKey);
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,19 +68,4 @@ export class EventManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeAllListeners() {
|
|
||||||
logger.debug('Removing all event listeners...');
|
|
||||||
const removePromises = Array.from(this.subscriptions).map(
|
|
||||||
subscriptionKey => {
|
|
||||||
const [subject, socketId] = subscriptionKey.split(':');
|
|
||||||
return natsServer.removeSubscription(subject, socketId);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(removePromises);
|
|
||||||
this.subscriptions.clear();
|
|
||||||
logger.debug(`Removed ${removePromises.length} event listener(s)`);
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,8 +23,8 @@ import { mongoServer } from './database/mongo.js';
|
|||||||
|
|
||||||
// Connect to Etcd (await)
|
// Connect to Etcd (await)
|
||||||
try {
|
try {
|
||||||
//await etcdServer.connect();
|
await etcdServer.connect();
|
||||||
// logger.info('Connected to Etcd');
|
logger.info('Connected to Etcd');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Failed to connect to Etcd:', err);
|
logger.error('Failed to connect to Etcd:', err);
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { natsServer } from '../database/nats.js';
|
import { etcdServer } from '../database/etcd.js';
|
||||||
import { redisServer } from '../database/redis.js';
|
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import { loadConfig } from '../config.js';
|
import { loadConfig } from '../config.js';
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
@ -18,43 +17,33 @@ export class LockManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async lockObject(object) {
|
async lockObject(object) {
|
||||||
// Persist lock in Redis and publish via NATS
|
// Add a 'lock' event to the 'locks' stream
|
||||||
logger.debug('Locking object:', object._id);
|
logger.debug('Locking object:', object._id);
|
||||||
try {
|
try {
|
||||||
const redisKey = `locks:${object.type}s:${object._id}`;
|
await etcdServer.setKey(`/locks/${object.type}s/${object._id}`, {
|
||||||
const lockPayload = {
|
|
||||||
...object,
|
...object,
|
||||||
locked: true
|
locked: true
|
||||||
};
|
});
|
||||||
await redisServer.setKey(redisKey, lockPayload);
|
logger.info(`Lock event to id: ${object._id}`);
|
||||||
const subject = `locks.${object.type}s.${object._id}`;
|
|
||||||
await natsServer.publish(subject, lockPayload);
|
|
||||||
logger.info(`Lock event published for id: ${object._id}`);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Error locking object ${object._id}:`, err);
|
logger.error(`Error adding lock event to: ${object._id}:`, err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async unlockObject(object) {
|
async unlockObject(object) {
|
||||||
// Remove lock from Redis (if owned by user) and publish via NATS
|
// Add an 'unlock' event to the 'locks' stream
|
||||||
const redisKey = `locks:${object.type}s:${object._id}`;
|
const key = `/locks/${object.type}s/${object._id}`;
|
||||||
try {
|
try {
|
||||||
logger.debug('Checking user can unlock:', object._id);
|
logger.debug('Checking user can unlock:', object._id);
|
||||||
|
|
||||||
const lockEvent = await redisServer.getKey(redisKey);
|
const lockEvent = await etcdServer.getKey(key);
|
||||||
|
|
||||||
if (lockEvent?.user === object.user) {
|
if (lockEvent?.user === object.user) {
|
||||||
logger.debug('Unlocking object:', object._id);
|
logger.debug('Unlocking object:', object._id);
|
||||||
await redisServer.deleteKey(redisKey);
|
await etcdServer.deleteKey(key);
|
||||||
const subject = `locks.${object.type}s.${object._id}`;
|
logger.info(`Unlocked object: ${object._id}`);
|
||||||
await natsServer.publish(subject, {
|
|
||||||
_id: object._id,
|
|
||||||
type: object.type,
|
|
||||||
locked: false
|
|
||||||
});
|
|
||||||
logger.info(`Unlocked object and published event: ${object._id}`);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -64,11 +53,11 @@ export class LockManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getObjectLock(object) {
|
async getObjectLock(object) {
|
||||||
// Get the current lock status of an object
|
// Get the current lock status of an object and broadcast it
|
||||||
logger.info('Getting lock status for object:', object._id);
|
logger.info('Getting lock status for object:', object._id);
|
||||||
try {
|
try {
|
||||||
const lockKey = `locks:${object.type}s:${object._id}`;
|
const lockKey = `/locks/${object.type}s/${object._id}`;
|
||||||
const lockValue = await redisServer.getKey(lockKey);
|
const lockValue = await etcdServer.getKey(lockKey);
|
||||||
|
|
||||||
if (lockValue) {
|
if (lockValue) {
|
||||||
// Object is locked
|
// Object is locked
|
||||||
@ -92,26 +81,26 @@ export class LockManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupLocksListeners() {
|
setupLocksListeners() {
|
||||||
// Subscribe to NATS subject for lock changes and emit via socket
|
etcdServer.onPrefixPutEvent(
|
||||||
const subject = 'locks.>';
|
'/locks',
|
||||||
natsServer
|
this.socketClient.id,
|
||||||
.subscribe(subject, this.socketClient.id, (_subject, value) => {
|
(key, value) => {
|
||||||
// Expected subjects: locks.{type}s.{id}
|
const id = key.split('/').pop();
|
||||||
const parts = _subject.split('.');
|
logger.debug('Lock object event:', id);
|
||||||
const last = parts[parts.length - 1];
|
this.socketClient.socket.emit('lockUpdate', {
|
||||||
const id = last;
|
...value,
|
||||||
const payload =
|
locked: true
|
||||||
typeof value === 'object'
|
});
|
||||||
? value
|
}
|
||||||
: { _id: id, locked: !!value?.locked };
|
);
|
||||||
logger.debug('Lock event received:', _subject);
|
etcdServer.onPrefixDeleteEvent('/locks', this.socketClient.id, key => {
|
||||||
this.socketClient.socket.emit('lockUpdate', payload);
|
const id = key.split('/').pop();
|
||||||
})
|
logger.debug('Unlock object event:', id);
|
||||||
.then(() => {
|
this.socketClient.socket.emit('lockUpdate', {
|
||||||
logger.info('Subscribed to NATS for lock changes.');
|
_id: id,
|
||||||
})
|
locked: false
|
||||||
.catch(err => {
|
|
||||||
logger.error('Failed to subscribe to NATS lock changes:', err);
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
logger.info('Subscribed to Etcd stream for lock changes.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,18 +2,12 @@ import log4js from 'log4js';
|
|||||||
// Load configuration
|
// Load configuration
|
||||||
import { loadConfig } from '../config.js';
|
import { loadConfig } from '../config.js';
|
||||||
import { CodeAuth, createAuthMiddleware } from '../auth/auth.js';
|
import { CodeAuth, createAuthMiddleware } from '../auth/auth.js';
|
||||||
import {
|
import { editObject, getObject, listObjects } from '../database/database.js';
|
||||||
newObject,
|
|
||||||
editObject,
|
|
||||||
getObject,
|
|
||||||
listObjects
|
|
||||||
} from '../database/database.js';
|
|
||||||
import { hostModel } from '../database/schemas/management/host.schema.js';
|
import { hostModel } from '../database/schemas/management/host.schema.js';
|
||||||
import { UpdateManager } from '../updates/updatemanager.js';
|
import { UpdateManager } from '../updates/updatemanager.js';
|
||||||
import { ActionManager } from '../actions/actionmanager.js';
|
import { ActionManager } from '../actions/actionmanager.js';
|
||||||
import { getModelByName } from '../utils.js';
|
import { getModelByName } from '../utils.js';
|
||||||
import { EventManager } from '../events/eventmanager.js';
|
import { EventManager } from '../events/eventmanager.js';
|
||||||
import { TemplateManager } from '../templates/templatemanager.js';
|
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
|
||||||
@ -31,7 +25,6 @@ export class SocketHost {
|
|||||||
this.updateManager = new UpdateManager(this);
|
this.updateManager = new UpdateManager(this);
|
||||||
this.actionManager = new ActionManager(this);
|
this.actionManager = new ActionManager(this);
|
||||||
this.eventManager = new EventManager(this);
|
this.eventManager = new EventManager(this);
|
||||||
this.templateManager = new TemplateManager(this);
|
|
||||||
this.codeAuth = new CodeAuth();
|
this.codeAuth = new CodeAuth();
|
||||||
this.setupSocketEventHandlers();
|
this.setupSocketEventHandlers();
|
||||||
}
|
}
|
||||||
@ -41,33 +34,12 @@ export class SocketHost {
|
|||||||
this.socket.on('authenticate', this.handleAuthenticate.bind(this));
|
this.socket.on('authenticate', this.handleAuthenticate.bind(this));
|
||||||
this.socket.on('updateHost', this.handleUpdateHost.bind(this));
|
this.socket.on('updateHost', this.handleUpdateHost.bind(this));
|
||||||
this.socket.on('getObject', this.handleGetObject.bind(this));
|
this.socket.on('getObject', this.handleGetObject.bind(this));
|
||||||
this.socket.on('newObject', this.handleNewObject.bind(this));
|
|
||||||
this.socket.on('editObject', this.handleEditObject.bind(this));
|
this.socket.on('editObject', this.handleEditObject.bind(this));
|
||||||
this.socket.on('listObjects', this.handleListObjects.bind(this));
|
this.socket.on('listObjects', this.handleListObjects.bind(this));
|
||||||
this.socket.on(
|
|
||||||
'subscribeToObjectUpdates',
|
|
||||||
this.handleSubscribeToObjectUpdatesEvent.bind(this)
|
|
||||||
);
|
|
||||||
this.socket.on(
|
|
||||||
'unsubscribeToObjectUpdates',
|
|
||||||
this.handleUnsubscribeToObjectUpdatesEvent.bind(this)
|
|
||||||
);
|
|
||||||
this.socket.on(
|
this.socket.on(
|
||||||
'subscribeToObjectActions',
|
'subscribeToObjectActions',
|
||||||
this.handleSubscribeToObjectActions.bind(this)
|
this.handleSubscribeToObjectActions.bind(this)
|
||||||
);
|
);
|
||||||
this.socket.on(
|
|
||||||
'subscribeToObjectEvent',
|
|
||||||
this.handleSubscribeToObjectEventEvent.bind(this)
|
|
||||||
);
|
|
||||||
this.socket.on(
|
|
||||||
'unsubscribeObjectEvent',
|
|
||||||
this.handleUnsubscribeObjectEventEvent.bind(this)
|
|
||||||
);
|
|
||||||
this.socket.on(
|
|
||||||
'renderTemplatePDF',
|
|
||||||
this.handleRenderTemplatePDFEvent.bind(this)
|
|
||||||
);
|
|
||||||
this.socket.on('objectEvent', this.handleObjectEventEvent.bind(this));
|
this.socket.on('objectEvent', this.handleObjectEventEvent.bind(this));
|
||||||
this.socket.on('disconnect', this.handleDisconnect.bind(this));
|
this.socket.on('disconnect', this.handleDisconnect.bind(this));
|
||||||
}
|
}
|
||||||
@ -135,16 +107,6 @@ export class SocketHost {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleNewObject(data, callback) {
|
|
||||||
const object = await newObject({
|
|
||||||
model: getModelByName(data.objectType),
|
|
||||||
newData: data.newData,
|
|
||||||
owner: this.host,
|
|
||||||
ownerType: 'host'
|
|
||||||
});
|
|
||||||
callback(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleEditObject(data, callback) {
|
async handleEditObject(data, callback) {
|
||||||
const object = await editObject({
|
const object = await editObject({
|
||||||
model: getModelByName(data.objectType),
|
model: getModelByName(data.objectType),
|
||||||
@ -152,8 +114,7 @@ export class SocketHost {
|
|||||||
updateData: data.updateData,
|
updateData: data.updateData,
|
||||||
populate: data.populate,
|
populate: data.populate,
|
||||||
owner: this.host,
|
owner: this.host,
|
||||||
ownerType: 'host',
|
ownerType: 'host'
|
||||||
auditLog: data?.auditLog
|
|
||||||
});
|
});
|
||||||
callback(object);
|
callback(object);
|
||||||
}
|
}
|
||||||
@ -190,13 +151,6 @@ export class SocketHost {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSubscribeToObjectUpdatesEvent(data) {
|
|
||||||
const result = await this.updateManager.subscribeToObjectUpdate(
|
|
||||||
data._id,
|
|
||||||
data.objectType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSubscribeToObjectActions(data) {
|
async handleSubscribeToObjectActions(data) {
|
||||||
await this.actionManager.subscribeToObjectActions(
|
await this.actionManager.subscribeToObjectActions(
|
||||||
data._id,
|
data._id,
|
||||||
@ -204,83 +158,11 @@ export class SocketHost {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSubscribeToObjectEventEvent(data) {
|
|
||||||
await this.eventManager.subscribeToObjectEvent(
|
|
||||||
data._id,
|
|
||||||
data.objectType,
|
|
||||||
data.eventType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleUnsubscribeObjectEventEvent(data) {
|
|
||||||
await this.eventManager.removeObjectEventsListener(
|
|
||||||
data._id,
|
|
||||||
data.objectType,
|
|
||||||
data.eventType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleUnsubscribeToObjectUpdatesEvent(data) {
|
|
||||||
await this.updateManager.unsubscribeToObjectUpdate(
|
|
||||||
data._id,
|
|
||||||
data.objectType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleRenderTemplatePDFEvent(data, callback) {
|
|
||||||
const result = await this.templateManager.renderPDF(
|
|
||||||
data._id,
|
|
||||||
data.content,
|
|
||||||
data.object,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDevicesState(state, online, connectedAt) {
|
|
||||||
logger.info('Setting devices state to', state, 'and online to', online);
|
|
||||||
|
|
||||||
const documentPrinters = await listObjects({
|
|
||||||
model: getModelByName('documentPrinter'),
|
|
||||||
filter: { host: this.host._id }
|
|
||||||
});
|
|
||||||
const printers = await listObjects({
|
|
||||||
model: getModelByName('printer'),
|
|
||||||
filter: { host: this.host._id }
|
|
||||||
});
|
|
||||||
logger.debug(
|
|
||||||
'Retrieved',
|
|
||||||
documentPrinters.length,
|
|
||||||
'document printers and',
|
|
||||||
printers.length,
|
|
||||||
'printers'
|
|
||||||
);
|
|
||||||
for (const documentPrinter of documentPrinters) {
|
|
||||||
await editObject({
|
|
||||||
model: getModelByName('documentPrinter'),
|
|
||||||
id: documentPrinter._id,
|
|
||||||
updateData: { state: state, online: online, connectedAt: connectedAt },
|
|
||||||
owner: this.host,
|
|
||||||
ownerType: 'host'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (const printer of printers) {
|
|
||||||
await editObject({
|
|
||||||
model: getModelByName('printer'),
|
|
||||||
id: printer._id,
|
|
||||||
updateData: { state: state, online: online, connectedAt: connectedAt },
|
|
||||||
owner: this.host,
|
|
||||||
ownerType: 'host'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
logger.info('Devices state set to', state, 'and online to', online);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleDisconnect() {
|
async handleDisconnect() {
|
||||||
if (this.authenticated) {
|
if (this.authenticated) {
|
||||||
await editObject({
|
await editObject({
|
||||||
model: hostModel,
|
model: hostModel,
|
||||||
id: this.id,
|
id: this.host._id,
|
||||||
updateData: {
|
updateData: {
|
||||||
online: false,
|
online: false,
|
||||||
state: { type: 'offline' },
|
state: { type: 'offline' },
|
||||||
@ -291,13 +173,6 @@ export class SocketHost {
|
|||||||
});
|
});
|
||||||
this.authenticated = false;
|
this.authenticated = false;
|
||||||
}
|
}
|
||||||
await this.actionManager.removeAllListeners();
|
|
||||||
await this.eventManager.removeAllListeners();
|
|
||||||
await this.setDevicesState(
|
|
||||||
{ type: 'offline', message: 'Host disconnected.' },
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
logger.info('External host disconnected. Socket ID:', this.id);
|
logger.info('External host disconnected. Socket ID:', this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,16 +64,11 @@ export class SocketUser {
|
|||||||
'previewTemplate',
|
'previewTemplate',
|
||||||
this.handlePreviewTemplateEvent.bind(this)
|
this.handlePreviewTemplateEvent.bind(this)
|
||||||
);
|
);
|
||||||
this.socket.on(
|
|
||||||
'renderTemplatePDF',
|
|
||||||
this.handleRenderTemplatePDFEvent.bind(this)
|
|
||||||
);
|
|
||||||
this.socket.on(
|
this.socket.on(
|
||||||
'generateHostOtp',
|
'generateHostOtp',
|
||||||
this.handleGenerateHostOtpEvent.bind(this)
|
this.handleGenerateHostOtpEvent.bind(this)
|
||||||
);
|
);
|
||||||
this.socket.on('objectAction', this.handleObjectActionEvent.bind(this));
|
this.socket.on('objectAction', this.handleObjectActionEvent.bind(this));
|
||||||
this.socket.on('disconnect', this.handleDisconnect.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAuthenticateEvent(data, callback) {
|
async handleAuthenticateEvent(data, callback) {
|
||||||
@ -201,15 +196,6 @@ export class SocketUser {
|
|||||||
callback(result);
|
callback(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleRenderTemplatePDFEvent(data, callback) {
|
|
||||||
const result = await this.templateManager.renderPDF(
|
|
||||||
data._id,
|
|
||||||
data.content,
|
|
||||||
data.object,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
async handleGenerateHostOtpEvent(data, callback) {
|
async handleGenerateHostOtpEvent(data, callback) {
|
||||||
const result = await generateHostOTP(data._id);
|
const result = await generateHostOTP(data._id);
|
||||||
callback(result);
|
callback(result);
|
||||||
@ -224,9 +210,7 @@ export class SocketUser {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDisconnect() {
|
handleDisconnect() {
|
||||||
await this.actionManager.removeAllListeners();
|
|
||||||
await this.eventManager.removeAllListeners();
|
|
||||||
logger.info('External user disconnected:', this.socket.user?.username);
|
logger.info('External user disconnected:', this.socket.user?.username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,21 +15,26 @@
|
|||||||
</style>
|
</style>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
min-width: calc((<%= width || '50mm' %> * <%= scale || '1' %>) + 100px);
|
||||||
|
min-height: calc(
|
||||||
|
(<%= height || '50mm' %> * <%= scale || '1' %>) + 100px
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.previewWrapper {
|
.previewContainer {
|
||||||
width: <%= (width * scale) + 'mm' || '50mm' %>;
|
transform: scale(<%= scale || '1' %>);
|
||||||
height: <%= (height * scale) + 'mm' || '50mm' %>;
|
min-width: calc((<%= width || '50mm' %> + 100px) * <%= scale || '1' %>);
|
||||||
|
min-height: calc(
|
||||||
|
(<%= height || '50mm' %> + 100px) * <%= scale || '1' %>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
.previewDocument {
|
.previewDocument {
|
||||||
width: <%= (width) + 'mm' || '50mm' %>;
|
width: <%= width || '50mm' %>;
|
||||||
height: <%= (height) + 'mm' || '50mm' %>;
|
height: <%= height || '50mm' %>;
|
||||||
transform: scale(<%= scale || '1' %>);
|
|
||||||
transform-origin: top left;
|
|
||||||
}
|
}
|
||||||
.renderDocument {
|
.renderDocument {
|
||||||
width: <%= (width * scale) + 'mm' || '50mm' %>;
|
width: <%= width || '50mm' %>;
|
||||||
height: <%= (height * scale) + 'mm' || '50mm' %>;
|
height: <%= height || '50mm' %>;
|
||||||
transform: scale(<%= scale || '1' %>);
|
transform: scale(<%= scale || '1' %>);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -40,10 +45,5 @@
|
|||||||
<script>
|
<script>
|
||||||
JsBarcode('.documentBarcode').init();
|
JsBarcode('.documentBarcode').init();
|
||||||
</script>
|
</script>
|
||||||
<% if (typeof previewPaginationScript !== 'undefined' && previewPaginationScript) { %>
|
|
||||||
<script>
|
|
||||||
<%- previewPaginationScript %>
|
|
||||||
</script>
|
|
||||||
<% } %>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
<div class="previewContainer">
|
<div class="previewContainer">
|
||||||
<div class="previewWrapper">
|
<div class="previewDocument"><%- content %></div>
|
||||||
<div class="previewDocument" id="content"><%- content %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,26 +11,15 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center; /* Horizontal center */
|
justify-content: center; /* Horizontal center */
|
||||||
align-items: center; /* Vertical center */
|
align-items: center; /* Vertical center */
|
||||||
padding: 60px;
|
transform-origin: center center;
|
||||||
box-sizing: border-box;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
.previewWrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.previewDocument {
|
.previewDocument {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||||
transform-origin: top left;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.documentText {
|
.documentText {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
}
|
||||||
.documentTitle {
|
.documentTitle {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -38,7 +27,7 @@ body {
|
|||||||
|
|
||||||
h1.documentTitle {
|
h1.documentTitle {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 34px;
|
font-size: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2.documentTitle {
|
h2.documentTitle {
|
||||||
@ -85,47 +74,6 @@ h4.documentTitle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.documentBarcode {
|
.documentBarcode {
|
||||||
width: auto !important;
|
width: 100% !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.documentProgressBar {
|
|
||||||
height: 8px;
|
|
||||||
width: 260px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #000000;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentProgressBarInner {
|
|
||||||
height: 100%;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentTable {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border: 1px solid #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentTableRow {
|
|
||||||
border: 1px solid #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentTableRow td,
|
|
||||||
.documentTableRowHeader th,
|
|
||||||
.documentTableRowFooter td {
|
|
||||||
padding: 4px;
|
|
||||||
border: 1px solid #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentTableRowHeader {
|
|
||||||
background: #0000002e;
|
|
||||||
text-align: left;
|
|
||||||
border: 1px solid #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentTableRowFooter {
|
|
||||||
background: #0000002e;
|
|
||||||
border: 1px solid #000000;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
import log4js from 'log4js';
|
|
||||||
import { loadConfig } from '../config.js';
|
|
||||||
|
|
||||||
const config = loadConfig();
|
|
||||||
const logger = log4js.getLogger('PDF Factory');
|
|
||||||
logger.level = config.server.logLevel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a PDF from HTML content using Puppeteer
|
|
||||||
* @param {string} html - The HTML content to convert to PDF
|
|
||||||
* @param {Object} options - PDF generation options
|
|
||||||
* @param {number} options.width - Document width in mm
|
|
||||||
* @param {number} options.height - Document height in mm
|
|
||||||
* @returns {Promise<Buffer>} The PDF buffer
|
|
||||||
*/
|
|
||||||
export async function generatePDF(html, options = {}) {
|
|
||||||
try {
|
|
||||||
// Dynamically import puppeteer to handle cases where it might not be installed
|
|
||||||
const puppeteer = await import('puppeteer');
|
|
||||||
|
|
||||||
const browser = await puppeteer.default.launch({
|
|
||||||
headless: true,
|
|
||||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
||||||
});
|
|
||||||
|
|
||||||
const page = await browser.newPage();
|
|
||||||
|
|
||||||
// Set content with HTML
|
|
||||||
await page.setContent(html, {
|
|
||||||
waitUntil: 'networkidle0'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generate PDF with specified dimensions
|
|
||||||
const pdfBuffer = await page.pdf({
|
|
||||||
format: options.format || undefined,
|
|
||||||
width: options.width ? `${options.width}mm` : undefined,
|
|
||||||
height: options.height ? `${options.height}mm` : undefined,
|
|
||||||
printBackground: true,
|
|
||||||
preferCSSPageSize: true,
|
|
||||||
margin: {
|
|
||||||
top: '0mm',
|
|
||||||
right: '0mm',
|
|
||||||
bottom: '0mm',
|
|
||||||
left: '0mm'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await browser.close();
|
|
||||||
|
|
||||||
return pdfBuffer;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error generating PDF:', error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -12,9 +12,7 @@ import utc from 'dayjs/plugin/utc.js';
|
|||||||
import timezone from 'dayjs/plugin/timezone.js';
|
import timezone from 'dayjs/plugin/timezone.js';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { getObject, listObjects } from '../database/database.js';
|
import { getObject } from '../database/database.js';
|
||||||
import { getModelByName } from '../utils.js';
|
|
||||||
import { generatePDF } from './pdffactory.js';
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
@ -30,9 +28,7 @@ logger.level = config.server.logLevel;
|
|||||||
let baseTemplate;
|
let baseTemplate;
|
||||||
let baseCSS;
|
let baseCSS;
|
||||||
let previewTemplate;
|
let previewTemplate;
|
||||||
let renderTemplateEjs;
|
|
||||||
let contentPlaceholder;
|
let contentPlaceholder;
|
||||||
let previewPaginationScript;
|
|
||||||
|
|
||||||
async function loadTemplates() {
|
async function loadTemplates() {
|
||||||
// Synchronously load files
|
// Synchronously load files
|
||||||
@ -45,18 +41,10 @@ async function loadTemplates() {
|
|||||||
join(__dirname, '/assets/previewtemplate.ejs'),
|
join(__dirname, '/assets/previewtemplate.ejs'),
|
||||||
'utf8'
|
'utf8'
|
||||||
);
|
);
|
||||||
renderTemplateEjs = fs.readFileSync(
|
|
||||||
join(__dirname, '/assets/rendertemplate.ejs'),
|
|
||||||
'utf8'
|
|
||||||
);
|
|
||||||
contentPlaceholder = fs.readFileSync(
|
contentPlaceholder = fs.readFileSync(
|
||||||
join(__dirname, '/assets/contentplaceholder.ejs'),
|
join(__dirname, '/assets/contentplaceholder.ejs'),
|
||||||
'utf8'
|
'utf8'
|
||||||
);
|
);
|
||||||
previewPaginationScript = fs.readFileSync(
|
|
||||||
join(__dirname, '/assets/previewpagination.js'),
|
|
||||||
'utf8'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTemplates();
|
loadTemplates();
|
||||||
@ -72,12 +60,6 @@ function getNodeStyles(attributes) {
|
|||||||
if (attributes?.height) {
|
if (attributes?.height) {
|
||||||
styles += `height: ${attributes.height};`;
|
styles += `height: ${attributes.height};`;
|
||||||
}
|
}
|
||||||
if (attributes?.maxWidth) {
|
|
||||||
styles += `max-width: ${attributes.maxWidth};`;
|
|
||||||
}
|
|
||||||
if (attributes?.maxHeight) {
|
|
||||||
styles += `max-height: ${attributes.maxHeight};`;
|
|
||||||
}
|
|
||||||
if (attributes?.gap && attributes?.vertical != 'true') {
|
if (attributes?.gap && attributes?.vertical != 'true') {
|
||||||
styles += `column-gap: ${attributes.gap};`;
|
styles += `column-gap: ${attributes.gap};`;
|
||||||
}
|
}
|
||||||
@ -114,15 +96,6 @@ function getNodeStyles(attributes) {
|
|||||||
if (attributes?.scale) {
|
if (attributes?.scale) {
|
||||||
styles += `transform: scale(${attributes.scale});`;
|
styles += `transform: scale(${attributes.scale});`;
|
||||||
}
|
}
|
||||||
if (attributes?.textAlign) {
|
|
||||||
styles += `text-align: ${attributes.textAlign};`;
|
|
||||||
}
|
|
||||||
if (attributes?.textSize) {
|
|
||||||
styles += `font-size: ${attributes.textSize};`;
|
|
||||||
}
|
|
||||||
if (attributes?.wordWrap) {
|
|
||||||
styles += `word-wrap: ${attributes.wordWrap};`;
|
|
||||||
}
|
|
||||||
return styles;
|
return styles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,9 +152,7 @@ async function transformCustomElements(content) {
|
|||||||
class: 'documentBarcode',
|
class: 'documentBarcode',
|
||||||
'jsbarcode-displayValue': 'false',
|
'jsbarcode-displayValue': 'false',
|
||||||
'jsbarcode-value': node.content[0],
|
'jsbarcode-value': node.content[0],
|
||||||
'jsbarcode-format': node.attrs.format,
|
'jsbarcode-format': node.attrs.format
|
||||||
'jsbarcode-width': node.attrs.barcodeWidth,
|
|
||||||
'jsbarcode-margin': 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -222,27 +193,6 @@ async function transformCustomElements(content) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
tree =>
|
|
||||||
tree.match({ tag: 'ProgressBar' }, node => {
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
tag: 'div',
|
|
||||||
attrs: {
|
|
||||||
class: 'documentProgressBar',
|
|
||||||
style: getNodeStyles(node.attrs)
|
|
||||||
},
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
tag: 'div',
|
|
||||||
attrs: {
|
|
||||||
class: 'documentProgressBarInner',
|
|
||||||
style: `width: ${Math.round((node.content[0] || 0) * 100)}%`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
tree =>
|
tree =>
|
||||||
tree.match({ tag: 'DateTime' }, node => {
|
tree.match({ tag: 'DateTime' }, node => {
|
||||||
const dateTime = dayjs.utc(node.content[0]);
|
const dateTime = dayjs.utc(node.content[0]);
|
||||||
@ -254,91 +204,6 @@ async function transformCustomElements(content) {
|
|||||||
style: getNodeStyles(node.attrs)
|
style: getNodeStyles(node.attrs)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}),
|
|
||||||
tree =>
|
|
||||||
tree.match({ tag: 'Table' }, node => {
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
tag: 'table',
|
|
||||||
attrs: {
|
|
||||||
class: 'documentTable',
|
|
||||||
style: getNodeStyles(node.attrs)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
tree =>
|
|
||||||
tree.match({ tag: 'Row' }, node => {
|
|
||||||
const rowType = node.attrs?.type?.toLowerCase() || '';
|
|
||||||
|
|
||||||
// Transform Col children based on the row type (header/footer/body)
|
|
||||||
const transformCols = content => {
|
|
||||||
if (!Array.isArray(content)) return content;
|
|
||||||
return content.map(child => {
|
|
||||||
if (typeof child === 'string' || child == null) {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
if (child.tag !== 'Col') {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseAttrs = {
|
|
||||||
...child.attrs,
|
|
||||||
style: getNodeStyles(child.attrs)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (rowType === 'header') {
|
|
||||||
// Header row columns become table headers
|
|
||||||
return {
|
|
||||||
...child,
|
|
||||||
tag: 'th',
|
|
||||||
attrs: baseAttrs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Footer and body rows both use <td>; footer is distinguished by the row class
|
|
||||||
return {
|
|
||||||
...child,
|
|
||||||
tag: 'td',
|
|
||||||
attrs: baseAttrs
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const content = transformCols(node.content);
|
|
||||||
|
|
||||||
if (rowType === 'header') {
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
tag: 'tr',
|
|
||||||
content,
|
|
||||||
attrs: {
|
|
||||||
class: 'documentTableRowHeader',
|
|
||||||
style: getNodeStyles(node.attrs)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rowType === 'footer') {
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
tag: 'tr',
|
|
||||||
content,
|
|
||||||
attrs: {
|
|
||||||
class: 'documentTableRowFooter',
|
|
||||||
style: getNodeStyles(node.attrs)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
tag: 'tr',
|
|
||||||
content,
|
|
||||||
attrs: {
|
|
||||||
class: 'documentTableRow',
|
|
||||||
style: getNodeStyles(node.attrs)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
]).process(content);
|
]).process(content);
|
||||||
|
|
||||||
@ -346,13 +211,6 @@ async function transformCustomElements(content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TemplateManager {
|
export class TemplateManager {
|
||||||
constructor() {
|
|
||||||
this.fc = {
|
|
||||||
listObjects: this.listObjects.bind(this),
|
|
||||||
getObject: this.getObject.bind(this),
|
|
||||||
formatDate: this.formatDate.bind(this)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Previews an EJS template by rendering it with provided data
|
* Previews an EJS template by rendering it with provided data
|
||||||
* @param {string} templateString - The EJS template as a string
|
* @param {string} templateString - The EJS template as a string
|
||||||
@ -360,14 +218,7 @@ export class TemplateManager {
|
|||||||
* @param {Object} options - EJS rendering options
|
* @param {Object} options - EJS rendering options
|
||||||
* @returns {Promise<string>} The rendered HTML string
|
* @returns {Promise<string>} The rendered HTML string
|
||||||
*/
|
*/
|
||||||
async renderTemplate(
|
async renderTemplate(id, content, data = {}, scale, options = {}) {
|
||||||
id,
|
|
||||||
content,
|
|
||||||
data = {},
|
|
||||||
scale = 1,
|
|
||||||
options = {},
|
|
||||||
preview = true
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
// Set default options for EJS rendering
|
// Set default options for EJS rendering
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
@ -391,28 +242,11 @@ export class TemplateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const documentSize = documentTemplate.documentSize;
|
const documentSize = documentTemplate.documentSize;
|
||||||
if (documentSize == null) {
|
|
||||||
return { error: 'Document template size not found.' };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate content parameter
|
var templateData = data;
|
||||||
if (content == null || typeof content !== 'string') {
|
|
||||||
return { error: 'Template content is required and must be a string.' };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure data has default undefefined values and then merge with data
|
|
||||||
var templateData = {};
|
|
||||||
if (documentTemplate.global == true) {
|
if (documentTemplate.global == true) {
|
||||||
templateData = { content: contentPlaceholder, fc: this.fc };
|
templateData = { content: contentPlaceholder };
|
||||||
} else {
|
|
||||||
const objectType = documentTemplate?.objectType;
|
|
||||||
const model = getModelByName(objectType);
|
|
||||||
const defaultKeys = Object.keys(model.schema.obj);
|
|
||||||
const defaultValues = {};
|
|
||||||
for (const key of defaultKeys) {
|
|
||||||
defaultValues[key] = null;
|
|
||||||
}
|
|
||||||
templateData = { ...defaultValues, ...data, fc: this.fc };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the template
|
// Render the template
|
||||||
@ -425,79 +259,39 @@ export class TemplateManager {
|
|||||||
var templateWithParentContent;
|
var templateWithParentContent;
|
||||||
|
|
||||||
if (documentTemplate.parent != undefined) {
|
if (documentTemplate.parent != undefined) {
|
||||||
// Validate parent content
|
|
||||||
if (
|
|
||||||
documentTemplate.parent.content == null ||
|
|
||||||
typeof documentTemplate.parent.content !== 'string'
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
error: 'Parent template content is required and must be a string.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
templateWithParentContent = await ejs.render(
|
templateWithParentContent = await ejs.render(
|
||||||
documentTemplate.parent.content,
|
documentTemplate.parent.content,
|
||||||
{ content: templateContent, fc: this.fc },
|
{ content: templateContent },
|
||||||
defaultOptions
|
defaultOptions
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
templateWithParentContent = templateContent;
|
templateWithParentContent = templateContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate rendered content before transformation
|
|
||||||
if (
|
|
||||||
templateWithParentContent == null ||
|
|
||||||
typeof templateWithParentContent !== 'string'
|
|
||||||
) {
|
|
||||||
return { error: 'Failed to render template content.' };
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateHtml = await transformCustomElements(
|
const templateHtml = await transformCustomElements(
|
||||||
templateWithParentContent
|
templateWithParentContent
|
||||||
);
|
);
|
||||||
|
|
||||||
// Validate transformed HTML
|
const previewHtml = await ejs.render(
|
||||||
if (templateHtml == null || typeof templateHtml !== 'string') {
|
previewTemplate,
|
||||||
return { error: 'Failed to transform template content.' };
|
{ content: templateHtml },
|
||||||
}
|
defaultOptions
|
||||||
|
);
|
||||||
var innerHtml = null;
|
|
||||||
|
|
||||||
if (preview == true) {
|
|
||||||
innerHtml = await ejs.render(
|
|
||||||
previewTemplate,
|
|
||||||
{ content: templateHtml },
|
|
||||||
defaultOptions
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
innerHtml = await ejs.render(
|
|
||||||
renderTemplateEjs,
|
|
||||||
{ content: templateHtml },
|
|
||||||
defaultOptions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate inner HTML
|
|
||||||
if (innerHtml == null || typeof innerHtml !== 'string') {
|
|
||||||
return { error: 'Failed to render inner template content.' };
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseHtml = await ejs.render(
|
const baseHtml = await ejs.render(
|
||||||
baseTemplate,
|
baseTemplate,
|
||||||
{
|
{
|
||||||
content: innerHtml,
|
content: previewHtml,
|
||||||
width: documentSize.width,
|
width: `${documentSize.width}mm`,
|
||||||
height: documentSize.height,
|
height: `${documentSize.height}mm`,
|
||||||
scale: `${scale}`,
|
scale: `${scale}`,
|
||||||
baseCSS: baseCSS,
|
baseCSS: baseCSS
|
||||||
previewPaginationScript: preview ? previewPaginationScript : ''
|
|
||||||
},
|
},
|
||||||
defaultOptions
|
defaultOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
const previewObject = {
|
const previewObject = {
|
||||||
html: baseHtml,
|
html: baseHtml
|
||||||
width: documentSize.width,
|
|
||||||
height: documentSize.height
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return previewObject;
|
return previewObject;
|
||||||
@ -521,73 +315,4 @@ export class TemplateManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a template to PDF format
|
|
||||||
* @param {string} id - The document template ID
|
|
||||||
* @param {string} content - The template content
|
|
||||||
* @param {Object} data - Data object to pass to the template
|
|
||||||
* @param {number} scale - Scale factor for rendering
|
|
||||||
* @param {Object} options - EJS rendering options
|
|
||||||
* @returns {Promise<Object>} Object containing PDF buffer or error
|
|
||||||
*/
|
|
||||||
async renderPDF(id, content, data = {}, options = {}) {
|
|
||||||
try {
|
|
||||||
logger.debug('Rendering PDF for template:', id);
|
|
||||||
|
|
||||||
const renderedTemplate = await this.renderTemplate(
|
|
||||||
id,
|
|
||||||
content,
|
|
||||||
data,
|
|
||||||
1,
|
|
||||||
options,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (renderedTemplate.error != undefined) {
|
|
||||||
return { error: renderedTemplate.error };
|
|
||||||
}
|
|
||||||
const baseHtml = renderedTemplate.html;
|
|
||||||
|
|
||||||
// Generate PDF using PDF factory
|
|
||||||
const pdfBuffer = await generatePDF(baseHtml, {
|
|
||||||
width: renderedTemplate.width,
|
|
||||||
height: renderedTemplate.height
|
|
||||||
});
|
|
||||||
|
|
||||||
const pdfObject = {
|
|
||||||
pdf: pdfBuffer
|
|
||||||
};
|
|
||||||
return pdfObject;
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn('Error whilst rendering PDF:', error.message);
|
|
||||||
return { error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async listObjects(objectType, filter = {}, populate = []) {
|
|
||||||
const model = getModelByName(objectType);
|
|
||||||
if (model == undefined) {
|
|
||||||
throw new Error('Farm Control: Object type not found.');
|
|
||||||
}
|
|
||||||
const objects = await listObjects({
|
|
||||||
model,
|
|
||||||
filter,
|
|
||||||
populate
|
|
||||||
});
|
|
||||||
return objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDate(date, format) {
|
|
||||||
return dayjs(date).format(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getObject(objectType, id) {
|
|
||||||
const model = getModelByName(objectType);
|
|
||||||
if (model == undefined) {
|
|
||||||
throw new Error('Farm Control: Object type not found.');
|
|
||||||
}
|
|
||||||
const object = await getObject({ model, id, cached: true });
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,6 @@ export class UpdateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async subscribeToObjectUpdate(id, objectType) {
|
async subscribeToObjectUpdate(id, objectType) {
|
||||||
logger.debug('Subscribing to object update...', id, objectType);
|
|
||||||
await natsServer.subscribe(
|
await natsServer.subscribe(
|
||||||
`${objectType}s.${id}.object`,
|
`${objectType}s.${id}.object`,
|
||||||
this.socketClient.socketId,
|
this.socketClient.socketId,
|
||||||
|
|||||||
14
src/utils.js
14
src/utils.js
@ -7,17 +7,11 @@ import canonicalize from 'canonical-json';
|
|||||||
import { loadConfig } from './config.js';
|
import { loadConfig } from './config.js';
|
||||||
import { userModel } from './database/schemas/management/user.schema.js';
|
import { userModel } from './database/schemas/management/user.schema.js';
|
||||||
import { documentSizeModel } from './database/schemas/management/documentsize.schema.js';
|
import { documentSizeModel } from './database/schemas/management/documentsize.schema.js';
|
||||||
import { documentJobModel } from './database/schemas/management/documentjob.schema.js';
|
|
||||||
import { documentTemplateModel } from './database/schemas/management/documenttemplate.schema.js';
|
import { documentTemplateModel } from './database/schemas/management/documenttemplate.schema.js';
|
||||||
import { documentPrinterModel } from './database/schemas/management/documentprinter.schema.js';
|
|
||||||
import { printerModel } from './database/schemas/production/printer.schema.js';
|
import { printerModel } from './database/schemas/production/printer.schema.js';
|
||||||
import { subJobModel } from './database/schemas/production/subjob.schema.js';
|
import { subJobModel } from './database/schemas/production/subjob.schema.js';
|
||||||
import { jobModel } from './database/schemas/production/job.schema.js';
|
import { jobModel } from './database/schemas/production/job.schema.js';
|
||||||
import { filamentStockModel } from './database/schemas/inventory/filamentstock.schema.js';
|
import { filamentStockModel } from './database/schemas/inventory/filamentstock.schema.js';
|
||||||
import { fileModel } from './database/schemas/management/file.schema.js';
|
|
||||||
import { gcodeFileModel } from './database/schemas/production/gcodefile.schema.js';
|
|
||||||
import { stockEventModel } from './database/schemas/inventory/stockevent.schema.js';
|
|
||||||
import { filamentModel } from './database/schemas/management/filament.schema.js';
|
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
|
|
||||||
@ -27,17 +21,11 @@ const modelList = [
|
|||||||
hostModel,
|
hostModel,
|
||||||
userModel,
|
userModel,
|
||||||
documentSizeModel,
|
documentSizeModel,
|
||||||
documentJobModel,
|
|
||||||
documentTemplateModel,
|
documentTemplateModel,
|
||||||
documentPrinterModel,
|
|
||||||
printerModel,
|
printerModel,
|
||||||
jobModel,
|
jobModel,
|
||||||
subJobModel,
|
subJobModel,
|
||||||
fileModel,
|
filamentStockModel
|
||||||
gcodeFileModel,
|
|
||||||
filamentStockModel,
|
|
||||||
stockEventModel,
|
|
||||||
filamentModel
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function generateHostOTP(id) {
|
export async function generateHostOTP(id) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user