Add object view functionality with CRUD operations and schema integration
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
This commit introduces a new `objectView` schema and corresponding routes for managing object views, enhancing the application's data handling capabilities. It includes CRUD operations for object views, allowing users to create, retrieve, update, and delete views based on specific object types. The implementation also updates existing services and routes to integrate the new functionality, ensuring proper authentication and data validation. Additionally, the commit modifies relevant utility functions and filters to accommodate the new object view model, improving overall application structure and maintainability.
This commit is contained in:
parent
850c721867
commit
efcf1389a5
@ -1242,7 +1242,8 @@ export const editObject = async ({ model, id, updateData, user, populate, recalc
|
||||
if (
|
||||
parentType !== 'notification' &&
|
||||
parentType !== 'auditLog' &&
|
||||
parentType !== 'userNotifier'
|
||||
parentType !== 'userNotifier' &&
|
||||
parentType !== 'objectView'
|
||||
) {
|
||||
await editNotification(previousExpandedObject, updatedObject, id, parentType, user);
|
||||
}
|
||||
@ -1600,7 +1601,8 @@ export const deleteObject = async (
|
||||
if (
|
||||
parentType !== 'notification' &&
|
||||
parentType !== 'auditLog' &&
|
||||
parentType !== 'userNotifier'
|
||||
parentType !== 'userNotifier' &&
|
||||
parentType !== 'objectView'
|
||||
) {
|
||||
await deleteNotification(deleted, id.toString(), parentType, user);
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ const connectionSchema = new Schema(
|
||||
protocol: { type: String, required: true },
|
||||
host: { type: String, required: true },
|
||||
port: { type: Number, required: false },
|
||||
username: { type: String, required: false },
|
||||
password: { type: String, required: false },
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
@ -22,6 +24,8 @@ const documentPrinterSchema = new Schema(
|
||||
},
|
||||
connection: { type: connectionSchema, required: true },
|
||||
currentDocumentSize: { type: Schema.Types.ObjectId, ref: 'documentSize', required: false },
|
||||
supportedDocumentSizes: [{ type: Schema.Types.ObjectId, ref: 'documentSize', required: false }],
|
||||
rotateOrientation: { type: Boolean, required: false, default: false },
|
||||
tags: [{ type: String }],
|
||||
online: { type: Boolean, required: true, default: false },
|
||||
active: { type: Boolean, required: true, default: true },
|
||||
@ -30,6 +34,10 @@ const documentPrinterSchema = new Schema(
|
||||
message: { type: String, required: false },
|
||||
progress: { type: Number, required: false },
|
||||
},
|
||||
paperState: {
|
||||
type: { type: String, required: true, default: 'unknown' },
|
||||
message: { type: String, required: false },
|
||||
},
|
||||
connectedAt: { type: Date, default: null },
|
||||
host: { type: Schema.Types.ObjectId, ref: 'host', required: true },
|
||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false },
|
||||
|
||||
58
src/database/schemas/misc/objectview.schema.js
Normal file
58
src/database/schemas/misc/objectview.schema.js
Normal file
@ -0,0 +1,58 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const objectViewSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user',
|
||||
required: true,
|
||||
},
|
||||
objectType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '#3498DB',
|
||||
},
|
||||
private: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: true,
|
||||
},
|
||||
filter: {
|
||||
type: Schema.Types.Mixed,
|
||||
default: () => ({}),
|
||||
},
|
||||
sort: {
|
||||
type: Schema.Types.Mixed,
|
||||
default: () => ({}),
|
||||
},
|
||||
viewMode: {
|
||||
type: Schema.Types.Mixed,
|
||||
default: null,
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
required: true,
|
||||
default: Date.now,
|
||||
},
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
required: true,
|
||||
default: Date.now,
|
||||
},
|
||||
});
|
||||
|
||||
objectViewSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
objectViewSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const objectViewModel = mongoose.model('objectView', objectViewSchema);
|
||||
@ -32,6 +32,7 @@ import { noteTypeModel } from './management/notetype.schema.js';
|
||||
import { noteModel } from './misc/note.schema.js';
|
||||
import { notificationModel } from './misc/notification.schema.js';
|
||||
import { userNotifierModel } from './misc/usernotifier.schema.js';
|
||||
import { objectViewModel } from './misc/objectview.schema.js';
|
||||
import { documentSizeModel } from './management/documentsize.schema.js';
|
||||
import { documentTemplateModel } from './management/documenttemplate.schema.js';
|
||||
import { hostModel } from './management/host.schema.js';
|
||||
@ -103,6 +104,7 @@ export const models = {
|
||||
NTE: modelEntry(() => noteModel, 'note', 'Note'),
|
||||
NTF: modelEntry(() => notificationModel, 'notification', 'Notification'),
|
||||
ONF: modelEntry(() => userNotifierModel, 'userNotifier', 'User Notifier'),
|
||||
OVW: modelEntry(() => objectViewModel, 'objectView', 'Object View'),
|
||||
DSZ: modelEntry(() => documentSizeModel, 'documentSize', 'Document Size'),
|
||||
DTP: modelEntry(() => documentTemplateModel, 'documentTemplate', 'Document Template'),
|
||||
DPR: modelEntry(() => documentPrinterModel, 'documentPrinter', 'Document Printer'),
|
||||
|
||||
@ -61,6 +61,7 @@ import {
|
||||
returnPolicyRoutes,
|
||||
paymentPolicyRoutes,
|
||||
userNotifierRoutes,
|
||||
objectViewRoutes,
|
||||
notificationRoutes,
|
||||
odataRoutes,
|
||||
rssRoutes,
|
||||
@ -230,6 +231,7 @@ app.use('/returnpolicies', returnPolicyRoutes);
|
||||
app.use('/paymentpolicies', paymentPolicyRoutes);
|
||||
app.use('/notes', noteRoutes);
|
||||
app.use('/usernotifiers', userNotifierRoutes);
|
||||
app.use('/objectviews', objectViewRoutes);
|
||||
app.use('/notifications', notificationRoutes);
|
||||
app.use('/odata', odataRoutes);
|
||||
app.use('/rss', rssRoutes);
|
||||
|
||||
@ -54,6 +54,7 @@ import returnPolicyRoutes from './sales/returnpolicies.js';
|
||||
import paymentPolicyRoutes from './finance/paymentpolicies.js';
|
||||
import noteRoutes from './misc/notes.js';
|
||||
import userNotifierRoutes from './misc/usernotifiers.js';
|
||||
import objectViewRoutes from './misc/objectviews.js';
|
||||
import notificationRoutes from './misc/notifications.js';
|
||||
import odataRoutes from './misc/odata.js';
|
||||
import rssRoutes from './misc/rss.js';
|
||||
@ -121,6 +122,7 @@ export {
|
||||
returnPolicyRoutes,
|
||||
paymentPolicyRoutes,
|
||||
userNotifierRoutes,
|
||||
objectViewRoutes,
|
||||
notificationRoutes,
|
||||
odataRoutes,
|
||||
rssRoutes,
|
||||
|
||||
34
src/routes/misc/objectviews.js
Normal file
34
src/routes/misc/objectviews.js
Normal file
@ -0,0 +1,34 @@
|
||||
import express from 'express';
|
||||
import { isAuthenticated } from '../../keycloak.js';
|
||||
import {
|
||||
listObjectViewsRouteHandler,
|
||||
getObjectViewRouteHandler,
|
||||
newObjectViewRouteHandler,
|
||||
deleteObjectViewRouteHandler,
|
||||
editObjectViewRouteHandler,
|
||||
} from '../../services/misc/objectviews.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
const { page, limit, objectType } = req.query;
|
||||
listObjectViewsRouteHandler(req, res, page, limit, objectType);
|
||||
});
|
||||
|
||||
router.post('/', isAuthenticated, async (req, res) => {
|
||||
newObjectViewRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/:id', isAuthenticated, async (req, res) => {
|
||||
getObjectViewRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||
editObjectViewRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteObjectViewRouteHandler(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -38,7 +38,7 @@ export const listDocumentPrintersRouteHandler = async (
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: ['currentDocumentSize', 'host'],
|
||||
populate: ['currentDocumentSize', 'supportedDocumentSizes', 'host'],
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
@ -65,7 +65,7 @@ export const listDocumentPrintersByPropertiesRouteHandler = async (
|
||||
properties,
|
||||
filter,
|
||||
masterFilter,
|
||||
populate: ['currentDocumentSize', 'host'],
|
||||
populate: ['currentDocumentSize', 'supportedDocumentSizes', 'host'],
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
@ -106,7 +106,7 @@ export const getDocumentPrinterRouteHandler = async (req, res) => {
|
||||
const result = await getObject({
|
||||
model: documentPrinterModel,
|
||||
id,
|
||||
populate: ['currentDocumentSize', 'host'],
|
||||
populate: ['currentDocumentSize', 'supportedDocumentSizes', 'host'],
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Document Template not found with supplied id.`);
|
||||
@ -129,6 +129,8 @@ export const editDocumentPrinterRouteHandler = async (req, res) => {
|
||||
active: req.body.active,
|
||||
connection: req.body.connection,
|
||||
currentDocumentSize: req.body.currentDocumentSize,
|
||||
supportedDocumentSizes: req.body.supportedDocumentSizes,
|
||||
rotateOrientation: req.body.rotateOrientation,
|
||||
host: req.body.host,
|
||||
vendor: req.body.vendor,
|
||||
};
|
||||
@ -138,7 +140,7 @@ export const editDocumentPrinterRouteHandler = async (req, res) => {
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
populate: ['currentDocumentSize', 'host'],
|
||||
populate: ['currentDocumentSize', 'supportedDocumentSizes', 'host'],
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
@ -160,6 +162,8 @@ export const newDocumentPrinterRouteHandler = async (req, res) => {
|
||||
active: req.body.active,
|
||||
connection: req.body.connection,
|
||||
currentDocumentSize: req.body.currentDocumentSize,
|
||||
supportedDocumentSizes: req.body.supportedDocumentSizes,
|
||||
rotateOrientation: req.body.rotateOrientation,
|
||||
host: req.body.host,
|
||||
vendor: req.body.vendor,
|
||||
};
|
||||
|
||||
@ -8,6 +8,7 @@ export const EXPORT_FILTER_BY_TYPE = {
|
||||
note: ['parent._id', 'noteType', 'user'],
|
||||
notification: ['user'],
|
||||
userNotifier: ['user', 'object', 'objectType'],
|
||||
objectView: ['user', 'objectType', 'private'],
|
||||
printer: ['host'],
|
||||
job: ['printer', 'gcodeFile'],
|
||||
subJob: ['job'],
|
||||
|
||||
186
src/services/misc/objectviews.js
Normal file
186
src/services/misc/objectviews.js
Normal file
@ -0,0 +1,186 @@
|
||||
import config from '../../config.js';
|
||||
import { objectViewModel } from '../../database/schemas/misc/objectview.schema.js';
|
||||
import log4js from 'log4js';
|
||||
import {
|
||||
deleteObject,
|
||||
editObject,
|
||||
getObject,
|
||||
newObject,
|
||||
} from '../../database/database.js';
|
||||
|
||||
const logger = log4js.getLogger('ObjectViews');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
const canModifyObjectView = (existing, user) => {
|
||||
if (existing?.private === false) {
|
||||
return true;
|
||||
}
|
||||
return String(existing?.user?._id ?? existing?.user) === String(user._id);
|
||||
};
|
||||
|
||||
export const listObjectViewsRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
page = 1,
|
||||
limit = 100,
|
||||
objectType = ''
|
||||
) => {
|
||||
if (!objectType) {
|
||||
return res.status(400).send({ error: 'objectType is required', code: 400 });
|
||||
}
|
||||
|
||||
const query = {
|
||||
objectType,
|
||||
$or: [{ private: false }, { user: req.user._id }],
|
||||
};
|
||||
|
||||
const skip = (Math.max(parseInt(page, 10) || 1, 1) - 1) * (parseInt(limit, 10) || 100);
|
||||
const parsedLimit = parseInt(limit, 10) || 100;
|
||||
|
||||
try {
|
||||
const [items, totalCount] = await Promise.all([
|
||||
objectViewModel
|
||||
.find(query)
|
||||
.populate('user')
|
||||
.sort({ createdAt: 1 })
|
||||
.skip(skip)
|
||||
.limit(parsedLimit)
|
||||
.lean({ virtuals: true }),
|
||||
objectViewModel.countDocuments(query),
|
||||
]);
|
||||
|
||||
logger.debug(
|
||||
`List of object views for ${objectType} (Page ${page}, Limit ${parsedLimit}). Count: ${items.length}`
|
||||
);
|
||||
res.set('X-Total-Count', String(totalCount));
|
||||
res.send(items);
|
||||
} catch (error) {
|
||||
logger.error('Error listing object views.', error);
|
||||
res.status(500).send({ error: error.message, code: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const getObjectViewRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const result = await getObject({
|
||||
model: objectViewModel,
|
||||
id,
|
||||
populate: ['user'],
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`Object view not found with supplied id.`);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
const isVisible =
|
||||
result.private === false ||
|
||||
String(result.user?._id ?? result.user) === String(req.user._id);
|
||||
if (!isVisible) {
|
||||
return res.status(403).send({ error: 'Forbidden', code: 403 });
|
||||
}
|
||||
|
||||
logger.debug(`Retrieved object view with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const newObjectViewRouteHandler = async (req, res) => {
|
||||
const newData = {
|
||||
user: req.user._id,
|
||||
objectType: req.body.objectType,
|
||||
name: req.body.name || 'New View',
|
||||
color: req.body.color || '#3498DB',
|
||||
private: req.body.private !== false,
|
||||
filter: req.body.filter || {},
|
||||
sort: req.body.sort || {},
|
||||
viewMode: req.body.viewMode ?? null,
|
||||
};
|
||||
|
||||
if (!newData.objectType) {
|
||||
return res.status(400).send({ error: 'objectType is required', code: 400 });
|
||||
}
|
||||
|
||||
const result = await newObject({
|
||||
model: objectViewModel,
|
||||
newData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No object view created:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`New object view with ID: ${result._id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editObjectViewRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
|
||||
const existing = await getObject({
|
||||
model: objectViewModel,
|
||||
id,
|
||||
});
|
||||
if (existing?.error) {
|
||||
return res.status(existing.code).send(existing);
|
||||
}
|
||||
if (!canModifyObjectView(existing, req.user)) {
|
||||
return res.status(403).send({ error: 'Forbidden: you cannot edit this view', code: 403 });
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
if (req.body.name !== undefined) updateData.name = req.body.name;
|
||||
if (req.body.color !== undefined) updateData.color = req.body.color;
|
||||
if (req.body.private !== undefined) updateData.private = req.body.private;
|
||||
if (req.body.filter !== undefined) updateData.filter = req.body.filter;
|
||||
if (req.body.sort !== undefined) updateData.sort = req.body.sort;
|
||||
if (req.body.viewMode !== undefined) updateData.viewMode = req.body.viewMode;
|
||||
|
||||
const result = await editObject({
|
||||
model: objectViewModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
populate: ['user'],
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing object view:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`Edited object view with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const deleteObjectViewRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
|
||||
const existing = await getObject({
|
||||
model: objectViewModel,
|
||||
id,
|
||||
});
|
||||
if (existing?.error) {
|
||||
return res.status(existing.code).send(existing);
|
||||
}
|
||||
if (!canModifyObjectView(existing, req.user)) {
|
||||
return res.status(403).send({ error: 'Forbidden: you cannot delete this view', code: 403 });
|
||||
}
|
||||
|
||||
const result = await deleteObject({
|
||||
model: objectViewModel,
|
||||
id,
|
||||
user: req.user,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('No object view deleted:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.info(`Successfully deleted object view ${id}`);
|
||||
res.send({
|
||||
status: 'ok',
|
||||
});
|
||||
};
|
||||
@ -1186,7 +1186,7 @@ function getChangedValues(oldObj, newObj, old = false) {
|
||||
return changes;
|
||||
}
|
||||
|
||||
const AUDIT_EXCLUDED_MODELS = ['notification', 'userNotifier', 'marketplaceEvent'];
|
||||
const AUDIT_EXCLUDED_MODELS = ['notification', 'userNotifier', 'objectView', 'marketplaceEvent'];
|
||||
const SENSITIVE_KEYS = ['secret'];
|
||||
|
||||
const DISTRIBUTE_KEYS = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user