Skip to content

Commit

Permalink
✨ bring your own inbound interface - it is now possible to add new in…
Browse files Browse the repository at this point in the history
…bound interfaces dynamically during runtime!

✨ to support the new inbound interface feature it is now possible to create custom global settings using the custom fields feature
🐛 fixed a bug in the docker-compose-auto-update.yml file preventing it to work properly
⬆️ updated package dependencies
  • Loading branch information
faburem committed Mar 16, 2023
1 parent 1a02982 commit 698c9e2
Show file tree
Hide file tree
Showing 16 changed files with 406 additions and 154 deletions.
2 changes: 1 addition & 1 deletion .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ [email protected]
[email protected]
[email protected]
[email protected]
mdg:validated-method@1.2.0
mdg:validated-method@1.3.0
[email protected]
[email protected]
[email protected]
Expand Down
4 changes: 2 additions & 2 deletions docker-compose-auto-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ services:
command: --cleanup --schedule "0 0 0 * * *" --label-enable
volumes:
- /var/run/docker.sock:/var/run/docker.sock
volumes:
titra_db_volume:
volumes:
titra_db_volume:

34 changes: 29 additions & 5 deletions imports/api/customfields/server/methods.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method'
import CustomFields from '../customfields.js'
import { Globalsettings } from '../../globalsettings/globalsettings.js'
import { adminAuthenticationMixin, transactionLogMixin } from '../../../utils/server_method_helpers'

/**
Expand All @@ -22,25 +23,37 @@ const addCustomField = new ValidatedMethod({
type: String,
desc: String,
possibleValues: Match.Maybe([String]),
category: Match.Maybe(String),
})
},
mixins: [adminAuthenticationMixin, transactionLogMixin],
async run({
classname, name, desc, type, possibleValues,
classname, name, desc, type, possibleValues, category,
}) {
if (await CustomFields.findOneAsync({ name })) {
throw new Meteor.Error('error-custom-field-exists', 'Custom field already exists', { method: 'addCustomField' })
}
if (type === 'global_setting' && await Globalsettings.findOneAsync({ name })) {
throw new Meteor.Error('error-global-setting-exists', 'Global setting already exists', { method: 'addCustomField' })
}
const customField = {
classname,
name,
desc,
type,
possibleValues,
category,
createdAt: new Date(),
updatedAt: new Date(),
}
await CustomFields.insertAsync(customField)
const globalsetting = {
name,
description: desc,
category,
type,
}
await Globalsettings.insertAsync(globalsetting)
return customField
},
})
Expand All @@ -61,9 +74,11 @@ const removeCustomField = new ValidatedMethod({
},
mixins: [adminAuthenticationMixin, transactionLogMixin],
async run({ _id }) {
if (!await CustomFields.findOneAsync({ _id })) {
const customfield = await CustomFields.findOneAsync({ _id })
if (!customfield) {
throw new Meteor.Error('error-custom-field-not-found', 'Custom field not found', { method: 'removeCustomField' })
}
await Globalsettings.removeAsync({ name: customfield.name })
await CustomFields.removeAsync({ _id })
return true
},
Expand All @@ -87,23 +102,32 @@ const updateCustomField = new ValidatedMethod({
type: String,
desc: String,
possibleValues: Match.Maybe([String]),
category: Match.Maybe(String),
})
},
mixins: [adminAuthenticationMixin, transactionLogMixin],
async run({
_id, desc, type, possibleValues,
_id, desc, type, possibleValues, category,
}) {
if (!await CustomFields.findOneAsync({ _id })) {
const customfield = await CustomFields.findOneAsync({ _id })
if (!customfield) {
throw new Meteor.Error('error-custom-field-not-found', 'Custom field not found', { method: 'removeCustomField' })
}
await CustomFields.updateAsync(
{ _id },
{
$set: {
desc, type, possibleValues, updatedAt: new Date(),
desc, type, possibleValues, category, updatedAt: new Date(),
},
},
)
await Globalsettings.updateAsync({ name: customfield.name }, {
$set: {
description: desc,
type,
category,
},
})
return true
},
})
Expand Down
91 changes: 67 additions & 24 deletions imports/api/inboundinterfaces/server/methods.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import { check, Match } from 'meteor/check'
import { NodeVM } from 'vm2'
import fetch from 'node-fetch'
import { ValidatedMethod } from 'meteor/mdg:validated-method'
import { adminAuthenticationMixin, transactionLogMixin } from '../../../utils/server_method_helpers'
import { adminAuthenticationMixin, authenticationMixin, transactionLogMixin } from '../../../utils/server_method_helpers'
import InboundInterfaces from '../inboundinterfaces.js'
import Projects from '../../projects/projects.js'
import { getGlobalSetting } from '../../../utils/frontend_helpers'

const inboundinterfacesinsert = new ValidatedMethod({
name: 'inboundinterfaces.insert',
validate({
name, description, globalinterfacesettings, localinterfacesettings, prepareRequest, processData,
name, description, processData, active,
}) {
check(name, String)
check(description, String)
check(globalinterfacesettings, Match.Maybe(Array))
check(localinterfacesettings, Match.Maybe(Array))
check(prepareRequest, Match.Maybe(String))
check(processData, Match.Maybe(String))
check(active, Boolean)
},
mixins: [adminAuthenticationMixin, transactionLogMixin],
async run({
name, description, globalinterfacesettings, localinterfacesettings, prepareRequest, processData,
name, description, processData, active,
}) {
await InboundInterfaces.insertAsync({
name,
description,
globalinterfacesettings,
localinterfacesettings,
prepareRequest,
processData,
active,
})
return 'notifications.success'
},
Expand All @@ -36,51 +36,94 @@ const inboundinterfacesupdate = new ValidatedMethod({
_id,
name,
description,
globalinterfacesettings,
localinterfacesettings,
prepareRequest,
processData,
active,
}) {
check(_id, String)
check(name, String)
check(description, String)
check(globalinterfacesettings, Array)
check(localinterfacesettings, Array)
check(prepareRequest, String)
check(processData, String)
check(active, Boolean)
},
mixins: [adminAuthenticationMixin, transactionLogMixin],
async run({
_id,
name,
description,
globalinterfacesettings,
localinterfacesettings,
prepareRequest,
processData,
active,
}) {
await InboundInterfaces.updateAsync({ _id }, {
$set: {
name,
description,
globalinterfacesettings,
localinterfacesettings,
prepareRequest,
processData,
active,
},
})
return 'notifications.success'
},
})
const inboundinterfacesremove = new ValidatedMethod({
name: 'inboundinterfaces.remove',
validate(_id) {
validate({ _id }) {
check(_id, String)
},
mixins: [adminAuthenticationMixin, transactionLogMixin],
async run(_id) {
async run({ _id }) {
await InboundInterfaces.removeAsync({ _id })
return 'notifications.success'
},
})
export { inboundinterfacesinsert, inboundinterfacesupdate, inboundinterfacesremove }
const getInboundInterfaces = new ValidatedMethod({
name: 'inboundinterfaces.get',
validate: null,
mixins: [authenticationMixin],
async run() {
return InboundInterfaces
.find({ active: true }, { fields: { processData: 0, prepareRequest: 0 } }).fetch()
},
})
const getTasksFromInboundInterface = new ValidatedMethod({
name: 'inboundinterfaces.getTasks',
validate({ _id, projectId }) {
check(_id, String)
check(projectId, Match.Maybe(String))
},
mixins: [authenticationMixin],
async run({ _id, projectId }) {
const inboundInterface = await InboundInterfaces.findOneAsync({ _id })
const meteorUser = await Meteor.users.findOneAsync({ _id: this.userId })
const project = await Projects.findOneAsync({ _id: projectId })
const vm = new NodeVM({
wrapper: 'none',
timeout: 1000,
sandbox: {
user: meteorUser.profile,
project,
fetch,
getGlobalSetting,
},
require: {
external: true,
builtin: ['*'],
},
})
try {
const result = await vm.run(inboundInterface.processData)
if (!result || !(result instanceof Array)) {
throw new Meteor.Error('notifications.inboundInterfaceError')
}
return result
} catch (error) {
throw new Meteor.Error(error.message)
}
},
})
export {
inboundinterfacesinsert,
inboundinterfacesupdate,
inboundinterfacesremove,
getInboundInterfaces,
getTasksFromInboundInterface,
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<select class="form-control" id="customfieldClassname" required placeholder="{{t 'globals.class'}}">
<option value="project">{{t "globals.project"}}</option>
<option value="time_entry">{{t "globals.time_entry"}}</option>
<option value="global_setting">{{t "globals.global_setting"}}</option>
</select>
<label for="customfieldClassname" class="form-label">{{t "globals.class"}}</label>
</div>
Expand All @@ -27,6 +28,7 @@
<option value="text" selected>String</option>
<option value="email">E-Mail</option>
<option value="color">Color</option>
<option value="checkbox">Boolean</option>
</select>
<label for="customfieldType" class="form-label">{{t "globals.type"}}</label>
</div>
Expand All @@ -35,6 +37,11 @@
<label for="customfieldPossibleValues" class="form-label">{{t "administration.possible_values"}}</label>
<span class="form-text">{{t "administration.possible_values_hint"}}</span>
</div>
<div class="mb-3 form-floating">
<input class="form-control" id="customfieldCategory" type="text" placeholder="{{t 'administration.category'}}"/>
<label for="customfieldCategory" class="form-label">{{t "administration.category"}}</label>
<span class="form-text">{{t "administration.category_hint"}}</span>
</div>
<div class="col-lg-1">
<button type="button" class="btn btn-primary js-create-customfield">{{t "administration.create"}}</button>
</div>
Expand All @@ -49,6 +56,7 @@
<th>{{t "globals.name"}}</th>
<th>{{t "globals.description"}}</th>
<th>{{t "globals.type"}}</th>
<th>{{t "administration.category"}}</th>
<th class="text-center">{{t "tracktime.actions"}}</th>
</tr>
</thead>
Expand All @@ -65,6 +73,7 @@
{{customfield.type}}
{{/if}}
</td>
<td>{{customfield.category}}</td>
<td class="text-center">
<a href="#" aria-label="Edit custom field" class="js-edit-customfield" data-customfield-id="{{customfield._id}}" data-bs-toggle="modal" data-bs-target=".js-edit-customfield-modal"><i class="fas fa-edit"></i></a>
<a href="#" aria-label="Remove custom field" class="js-remove-customfield" data-customfield-id="{{customfield._id}}"><i class="fas fa-trash"></i></a>
Expand All @@ -89,6 +98,7 @@ <h5 class="modal-title">{{t "administration.edit_custom_field"}}</h5>
<select class="form-control" id="editCustomfieldClassname" readonly placeholder="{{t 'globals.class'}}">
<option value="project">{{t "globals.project"}}</option>
<option value="time_entry">{{t "globals.time_entry"}}</option>
<option value="global_setting">{{t "globals.global_setting"}}</option>
</select>
<label for="editCustomfieldClassname" class="form-label">{{t "globals.class"}}</label>
</div>
Expand All @@ -115,6 +125,11 @@ <h5 class="modal-title">{{t "administration.edit_custom_field"}}</h5>
<label for="editCustomfieldPossibleValues" class="form-label">{{t "administration.possible_values"}}</label>
<span class="form-text">{{t "administration.possible_values_hint"}}</span>
</div>
<div class="mb-3 form-floating">
<input class="form-control" id="editCustomfieldCategory" type="text" placeholder="{{t 'administration.category'}}"/>
<label for="editCustomfieldCategory" class="form-label">{{t "administration.category"}}</label>
<span class="form-text">{{t "administration.category_hint"}}</span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{t "navigation.close"}}</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Template.customfieldscomponent.events({
const type = templateInstance.$('#customfieldType').val()
const classname = templateInstance.$('#customfieldClassname').val()
const possibleValues = templateInstance.$('#customfieldPossibleValues').val() !== '' ? templateInstance.$('#customfieldPossibleValues').val().split(',') : undefined
const category = templateInstance.$('#customfieldCategory').val()
if (!name) {
templateInstance.$('#customfieldName').addClass('is-invalid')
return
Expand All @@ -39,6 +40,7 @@ Template.customfieldscomponent.events({
type,
classname,
possibleValues,
category
}, (error) => {
if (error) {
console.error(error)
Expand Down Expand Up @@ -72,6 +74,7 @@ Template.customfieldscomponent.events({
templateInstance.$('#editCustomfieldDesc').val(customField.desc)
templateInstance.$('#editCustomfieldType').val(customField.type)
templateInstance.$('#editCustomfieldPossibleValues').val(customField.possibleValues)
templateInstance.$('#editCustomfieldCategory').val(customField.category)
}
},
'click .js-update-customfield': (event, templateInstance) => {
Expand All @@ -80,6 +83,7 @@ Template.customfieldscomponent.events({
_id: templateInstance.editCustomFieldId.get(),
desc: templateInstance.$('#editCustomfieldDesc').val(),
type: templateInstance.$('#editCustomfieldType').val(),
category: templateInstance.$('#editCustomfieldCategory').val(),
possibleValues: templateInstance.$('#editCustomfieldPossibleValues').val() !== '' ? templateInstance.$('#editCustomfieldPossibleValues').val().split(',') : undefined,
}, (error) => {
if (error) {
Expand Down
Loading

0 comments on commit 698c9e2

Please sign in to comment.