@tiledesk/tiledesk-server
Version:
The Tiledesk server module
754 lines (654 loc) • 25.7 kB
JavaScript
const authEvent = require('../../event/authEvent');
const requestEvent = require('../../event/requestEvent');
const botEvent = require('../../event/botEvent');
const kbEvent = require('../../event/kbEvent');
const assignmentContextUtil = require('../../utils/assignmentContextUtil');
const projectUserUpdateContextUtil = require('../../utils/projectUserUpdateContextUtil');
const activityActorUtil = require('../../utils/activityActorUtil');
const Activity = require('./models/activity');
const winston = require('../../config/winston');
function resolveEventKey(baseKey, queueEnabled) {
return queueEnabled ? `${baseKey}.queue` : baseKey;
}
async function save(activity) {
try {
await activity.save();
winston.debug('Activity saved', activity.toObject());
} catch (err) {
winston.error('Error saving activity ', { activity: activity.toObject(), err: err });
}
}
function toObject(doc) {
if (!doc) {
return doc;
}
if (doc.toObject) {
return doc.toObject();
}
return doc;
}
function faqKbTarget(chatbot) {
const object = toObject(chatbot);
return {
type: 'faq_kb',
id: activityActorUtil.resolveId(object && object._id),
object: object
};
}
function namespaceTarget(namespaceId, namespaceObject) {
const object = namespaceObject ? toObject(namespaceObject) : { id: namespaceId };
return {
type: 'kb_namespace',
id: activityActorUtil.resolveId(namespaceId || (object && object.id)),
object: object
};
}
function kbContentTarget(kbId, kbObject) {
const object = kbObject ? toObject(kbObject) : { _id: kbId };
return {
type: 'kb_content',
id: activityActorUtil.resolveId(kbId || (object && object._id)),
object: object
};
}
function resolveInvitedProjectUser(event) {
return event.savedProject_userPopulated ||
event.updatedProject_userPopulated ||
event.updatedPuserPopulated;
}
function buildProjectUserInviteActivity(event, pending) {
if (!event || !event.req) {
return null;
}
const body = event.req.body || {};
const actionObj = {
email: body.email,
role: body.role,
user_available: body.user_available,
inviteType: pending ? 'pending' : 'registered'
};
const actor = activityActorUtil.actorFromReq(event.req);
if (pending) {
const pendingInvitation = toObject(event.savedPendingInvitation);
if (!pendingInvitation) {
return null;
}
return new Activity({
id_project: event.req.projectid,
actor: actor,
verb: 'PROJECT_USER_INVITE',
actionObj: actionObj,
target: {
type: 'pendinginvitation',
id: activityActorUtil.resolveId(pendingInvitation._id),
object: pendingInvitation
}
});
}
const projectUser = toObject(resolveInvitedProjectUser(event));
if (!projectUser) {
return null;
}
return new Activity({
id_project: projectUser.id_project || event.req.projectid,
actor: actor,
verb: 'PROJECT_USER_INVITE',
actionObj: actionObj,
target: {
type: 'project_user',
id: activityActorUtil.resolveId(projectUser._id),
object: projectUser
}
});
}
function buildProjectUserDeleteActivity(event) {
if (!event || !event.req) {
return null;
}
const projectUser = toObject(event.project_userPopulated);
if (!projectUser) {
return null;
}
const user = projectUser.id_user;
const actor = activityActorUtil.actorFromReq(event.req);
return new Activity({
id_project: projectUser.id_project || event.req.projectid,
actor: actor,
verb: 'PROJECT_USER_DELETE',
actionObj: {
deleteType: event.deleteType || 'soft',
role: projectUser.role,
email: user && user.email
},
target: {
type: 'project_user',
id: activityActorUtil.resolveId(projectUser._id),
object: projectUser
}
});
}
class ActivityArchiver {
listen() {
winston.debug('ActivityArchiver listen');
console.log("\n\nActivityArchiver listen");
const enabled = process.env.ACTIVITY_HISTORY_ENABLED || "false";
winston.debug('ActivityArchiver enabled:' + enabled);
console.log("\n\nActivityArchiver enabled:" + enabled);
if (enabled === "true") {
winston.verbose('ActivityArchiver enabled');
} else {
winston.info('ActivityArchiver disabled');
return 0;
}
if (process.env.MONGOOSE_SYNCINDEX) {
Activity.syncIndexes();
winston.info("Activity.syncIndexes called");
}
// ********** AUTH EVENTS **********
// Works only if worker is disabled
// Doesn't work if job_worker enabled because queue.worker is disabled
const authProjectUserInvitePendingKey = resolveEventKey('project_user.invite.pending', authEvent.queueEnabled);
winston.debug('ActivityArchiver authProjectUserInvitePendingKey: ' + authProjectUserInvitePendingKey);
authEvent.on(authProjectUserInvitePendingKey, function (event) {
setImmediate(() => {
try {
if (event.skipArchive) {
return 0;
}
const activity = buildProjectUserInviteActivity(event, true);
if (!activity) {
return winston.debug('ActivityArchiver skipping project_user.invite.pending: missing data');
}
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving project_user.invite.pending activity', e);
}
});
});
const authProjectUserInviteKey = resolveEventKey('project_user.invite', authEvent.queueEnabled);
winston.debug('ActivityArchiver authProjectUserInviteKey: ' + authProjectUserInviteKey);
authEvent.on(authProjectUserInviteKey, function (event) {
setImmediate(() => {
try {
if (event.skipArchive) {
return 0;
}
const activity = buildProjectUserInviteActivity(event, false);
if (!activity) {
return winston.debug('ActivityArchiver skipping project_user.invite: missing data');
}
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving project_user.invite activity', e);
}
});
});
const authProjectUserUpdateKey = resolveEventKey('project_user.update', authEvent.queueEnabled);
winston.debug('ActivityArchiver authProjectUserUpdateKey: ' + authProjectUserUpdateKey);
authEvent.on(authProjectUserUpdateKey, function (event) {
setImmediate(() => {
if (event.skipArchive) {
return 0;
}
let project_user = undefined;
if (event.updatedProject_userPopulated.toObject) {
project_user = event.updatedProject_userPopulated.toObject()
} else {
project_user = event.updatedProject_userPopulated;
}
winston.debug('ActivityArchiver authProjectUserUpdateKey event: ', event);
if (!event.req.user) {
return winston.debug('ActivityArchiver skipping archive empty user'); //from i think chat21webhook
}
const fallbackTargetUserId = projectUserUpdateContextUtil.resolveUserId(
project_user.id_user && project_user.id_user._id ? project_user.id_user : project_user.id_user
);
let updateContext = event.updateContext || projectUserUpdateContextUtil.buildProjectUserUpdateContext(
event.req,
event.previousUserAvailable,
event.previousProfileStatus,
fallbackTargetUserId
);
if (event.previousUserAvailable !== undefined && updateContext.previousUserAvailable === undefined) {
updateContext.previousUserAvailable = event.previousUserAvailable;
}
if (event.previousProfileStatus !== undefined && updateContext.previousProfileStatus === undefined) {
updateContext.previousProfileStatus = event.previousProfileStatus;
}
let verb = projectUserUpdateContextUtil.verbForProjectUserUpdate(event.req.body, updateContext);
const reconciled = projectUserUpdateContextUtil.reconcileAvailabilityVerb(
event,
project_user,
verb,
updateContext
);
verb = reconciled.verb;
updateContext = reconciled.updateContext;
const actor = reconciled.actor ||
projectUserUpdateContextUtil.actorFromProjectUserUpdate(event, verb, updateContext);
const newStatus = projectUserUpdateContextUtil.availabilityStatusLabel({
user_available: project_user.user_available,
profileStatus: project_user.profileStatus
});
var activity = new Activity({
id_project: event.updatedProject_userPopulated.id_project,
actor: actor,
verb: verb,
actionObj: Object.assign({}, event.req.body, {
newStatus: newStatus,
updateType: updateContext.updateType,
source: updateContext.source
}),
target: {
type: "project_user",
id: event.updatedProject_userPopulated._id.toString(),
object: project_user
}
});
save(activity);
});
});
const authProjectUserDeleteKey = resolveEventKey('project_user.delete', authEvent.queueEnabled);
winston.debug('ActivityArchiver authProjectUserDeleteKey: ' + authProjectUserDeleteKey);
authEvent.on(authProjectUserDeleteKey, function (event) {
setImmediate(() => {
try {
if (event.skipArchive) {
return 0;
}
const activity = buildProjectUserDeleteActivity(event);
if (!activity) {
return winston.debug('ActivityArchiver skipping project_user.delete: missing data');
}
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving project_user.delete activity', e);
}
});
});
// ********** REQUEST EVENTS **********
const requestCreateKey = resolveEventKey('request.create', requestEvent.queueEnabled);
winston.debug('ActivityArchiver requestCreateKey: ' + requestCreateKey);
requestEvent.on(requestCreateKey, function (request) {
setImmediate(() => {
winston.debug('ActivityArchiver requestCreate triggered');
try {
if (request.preflight === true) {
winston.debug("preflight request disable archiver")
return 0;
}
const actor = activityActorUtil.actorFromRequestCreate(request);
let activity = new Activity({
id_project: request.id_project,
actor: actor,
verb: "REQUEST_CREATE",
actionObj: request,
target: {
type: "request",
id: request._id,
object: request
}
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving activity', e);
}
});
});
const requestUpdatePreflightKey = resolveEventKey('request.update.preflight', requestEvent.queueEnabled);
winston.debug('ActivityArchiver requestUpdatePreflightKey: ' + requestUpdatePreflightKey);
requestEvent.on(requestUpdatePreflightKey, function (request) {
winston.debug('ActivityArchiver request.update.preflight: ');
setImmediate(() => {
try {
if (request.preflight === true) {
winston.debug("preflight request disable archiver")
return 0;
}
const actor = activityActorUtil.actorFromRequestCreate(request);
let activity = new Activity({
id_project: request.id_project,
actor: actor,
verb: "REQUEST_CREATE",
actionObj: request,
target: {
type: "request",
id: request._id,
object: request
}
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving activity', e);
}
});
});
const requestCloseKey = resolveEventKey('request.close', requestEvent.queueEnabled);
winston.debug('ActivityArchiver requestCloseKey: ' + requestCloseKey);
requestEvent.on(requestCloseKey, function (request) {
setImmediate(async () => {
try {
winston.debug('ActivityArchiver close');
const actor = await activityActorUtil.resolveActorFromClosedBy(request);
let activity = new Activity({
id_project: request.id_project,
actor: actor,
verb: "REQUEST_CLOSE",
actionObj: request,
target: {
type: "request",
id: request._id,
object: request
}
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving activity', e);
}
});
});
const requestAssignedKey = resolveEventKey('request.assigned', requestEvent.queueEnabled);
winston.debug('ActivityArchiver requestAssignedKey: ' + requestAssignedKey);
requestEvent.on(requestAssignedKey, function (data) {
setImmediate(() => {
try {
if (!data || !data.request) {
return winston.debug('ActivityArchiver skipping request.assigned: missing request');
}
const verb = assignmentContextUtil.verbFromAssignmentType(data.assignmentType);
if (!verb) {
return winston.debug('ActivityArchiver skipping request.assigned: unknown assignmentType', data.assignmentType);
}
const reconciled = assignmentContextUtil.reconcileAssignmentPayload(data);
winston.info('ActivityArchiver request.assigned actor', {
verb: verb,
assignmentType: data.assignmentType,
source: reconciled.source,
actor: reconciled.actor,
assigneeId: data.assigneeId,
assigneeType: data.assigneeType,
request_id: data.request && data.request.request_id
});
const activityFields = {
id_project: data.request.id_project,
actor: reconciled.actor,
verb: verb,
actionObj: {
assigneeId: data.assigneeId,
assigneeName: data.assigneeName,
assigneeType: data.assigneeType || 'user',
assignmentType: data.assignmentType,
source: reconciled.source,
previousAssigneeId: data.previousAssigneeId,
removedParticipants: data.removedParticipants
},
target: {
type: 'request',
id: data.request._id,
object: data.request
}
};
if (data.assigneeId) {
activityFields.related = {
role: verb === 'REQUEST_UNASSIGNED' ? 'unassigned_user' : 'assignee',
type: data.assigneeType || 'user',
id: String(data.assigneeId),
name: data.assigneeName
};
}
const activity = new Activity(activityFields);
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving request.assigned activity', e);
}
});
});
const requestParticipantsLeaveKey = resolveEventKey('request.participants.leave', requestEvent.queueEnabled);
winston.debug('ActivityArchiver requestParticipantsLeaveKey: ' + requestParticipantsLeaveKey);
requestEvent.on(requestParticipantsLeaveKey, function (data) {
setImmediate(() => {
try {
if (!data || !data.request) {
return winston.debug('ActivityArchiver skipping request.participants.leave: missing request');
}
if (!data.trackLeave) {
return winston.debug('ActivityArchiver skipping request.participants.leave: trackLeave false');
}
if (!assignmentContextUtil.isHumanParticipant(data.member)) {
return winston.debug('ActivityArchiver skipping request.participants.leave: bot participant');
}
const activity = new Activity({
id_project: data.request.id_project,
actor: data.actor || activityActorUtil.actorFromUserId(data.member),
verb: 'LEAVE_CONVERSATION',
actionObj: {
member: data.member,
source: data.source || 'api'
},
target: {
type: 'request',
id: data.request._id,
object: data.request
}
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving request.participants.leave activity', e);
}
});
});
// ********** CHATBOT / FAQ_KB EVENTS **********
const faqbotCreatedKey = resolveEventKey('faqbot.created', botEvent.queueEnabled);
winston.debug('ActivityArchiver faqbotCreatedKey: ' + faqbotCreatedKey);
botEvent.on(faqbotCreatedKey, function (data) {
setImmediate(() => {
try {
const chatbot = data && (data.chatbot || data);
if (!chatbot || !(data.id_project || chatbot.id_project)) {
return winston.debug('ActivityArchiver skipping faqbot.created: missing chatbot');
}
const activity = new Activity({
id_project: data.id_project || chatbot.id_project,
actor: activityActorUtil.actorFromReq(data.req),
verb: 'FAQ_KB_CREATE',
actionObj: {
name: chatbot.name,
type: chatbot.type,
subtype: chatbot.subtype
},
target: faqKbTarget(chatbot)
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving faqbot.created activity', e);
}
});
});
const faqbotDeletedKey = resolveEventKey('faqbot.deleted', botEvent.queueEnabled);
winston.debug('ActivityArchiver faqbotDeletedKey: ' + faqbotDeletedKey);
botEvent.on(faqbotDeletedKey, function (data) {
setImmediate(() => {
console.log("\n\nfaqbot.deleted");
try {
const chatbot = data && data.chatbot;
if (!chatbot || !chatbot.id_project) {
return winston.debug('ActivityArchiver skipping faqbot.deleted: missing chatbot');
}
const activity = new Activity({
id_project: chatbot.id_project,
actor: activityActorUtil.actorFromReq(data.req),
verb: 'FAQ_KB_DELETE',
actionObj: {
name: chatbot.name,
type: chatbot.type
},
target: faqKbTarget(chatbot)
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving faqbot.deleted activity', e);
}
});
});
const faqbotPublishKey = resolveEventKey('faqbot.publish', botEvent.queueEnabled);
winston.debug('ActivityArchiver faqbotPublishKey: ' + faqbotPublishKey);
botEvent.on(faqbotPublishKey, function (data) {
setImmediate(() => {
try {
if (!data || !data.chatbot || !data.chatbot.id_project) {
return winston.debug('ActivityArchiver skipping faqbot.publish: missing chatbot');
}
const activity = new Activity({
id_project: data.chatbot.id_project,
actor: activityActorUtil.actorFromReq(data.req),
verb: 'FAQ_KB_PUBLISH',
actionObj: {
name: data.chatbot.name,
publishedBotId: data.publishedBotId,
release_note: data.release_note
},
target: faqKbTarget(data.chatbot)
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving faqbot.publish activity', e);
}
});
});
// ********** KB / NAMESPACE EVENTS **********
const kbNamespaceCreateKey = resolveEventKey('kb.namespace.create', kbEvent.queueEnabled);
winston.debug('ActivityArchiver kbNamespaceCreateKey: ' + kbNamespaceCreateKey);
kbEvent.on(kbNamespaceCreateKey, function (data) {
setImmediate(() => {
try {
if (!data || !data.project_id || !data.savedNamespace) {
return winston.debug('ActivityArchiver skipping kb.namespace.create: missing data');
}
const activity = new Activity({
id_project: data.project_id,
actor: activityActorUtil.actorFromReq(data.req),
verb: 'KB_NAMESPACE_CREATE',
actionObj: {
namespaceName: data.savedNamespace.name,
hybrid: data.savedNamespace.hybrid,
default: data.savedNamespace.default === true
},
target: namespaceTarget(data.namespace_id || data.savedNamespace.id, data.savedNamespace)
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving kb.namespace.create activity', e);
}
});
});
const kbNamespaceDeleteKey = resolveEventKey('kb.namespace.delete', kbEvent.queueEnabled);
winston.debug('ActivityArchiver kbNamespaceDeleteKey: ' + kbNamespaceDeleteKey);
kbEvent.on(kbNamespaceDeleteKey, function (data) {
setImmediate(() => {
try {
if (!data || !data.project_id || !data.namespace_id) {
return winston.debug('ActivityArchiver skipping kb.namespace.delete: missing data');
}
const activity = new Activity({
id_project: data.project_id,
actor: activityActorUtil.actorFromReq(data.req),
verb: 'KB_NAMESPACE_DELETE',
actionObj: {
namespaceName: data.namespace_name,
deletedCount: data.deletedCount
},
target: namespaceTarget(data.namespace_id, { id: data.namespace_id, name: data.namespace_name })
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving kb.namespace.delete activity', e);
}
});
});
const kbContentsAddKey = resolveEventKey('kb.contents.add', kbEvent.queueEnabled);
winston.debug('ActivityArchiver kbContentsAddKey: ' + kbContentsAddKey);
kbEvent.on(kbContentsAddKey, function (data) {
setImmediate(() => {
try {
if (!data || !data.project_id || !data.namespace_id) {
return winston.debug('ActivityArchiver skipping kb.contents.add: missing data');
}
const activity = new Activity({
id_project: data.project_id,
actor: activityActorUtil.actorFromReq(data.req),
verb: 'KB_CONTENTS_ADD',
actionObj: {
contentAddType: data.contentAddType,
namespaceName: data.namespace_name,
count: data.count,
type: data.type,
source: data.source
},
target: namespaceTarget(data.namespace_id, { id: data.namespace_id, name: data.namespace_name })
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving kb.contents.add activity', e);
}
});
});
const kbContentsDeleteKey = resolveEventKey('kb.contents.delete', kbEvent.queueEnabled);
winston.debug('ActivityArchiver kbContentsDeleteKey: ' + kbContentsDeleteKey);
kbEvent.on(kbContentsDeleteKey, function (data) {
setImmediate(() => {
console.log("\n\nkb.contents.delete");
try {
if (!data || !data.project_id || !data.namespace_id) {
return winston.debug('ActivityArchiver skipping kb.contents.delete: missing data');
}
const activity = new Activity({
id_project: data.project_id,
actor: activityActorUtil.actorFromReq(data.req),
verb: 'KB_CONTENTS_DELETE',
actionObj: {
namespaceName: data.namespace_name,
deletedCount: data.deletedCount,
deleteMode: data.deleteMode || 'contents_only'
},
target: namespaceTarget(data.namespace_id, { id: data.namespace_id, name: data.namespace_name })
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving kb.contents.delete activity', e);
}
});
});
const kbContentDeleteKey = resolveEventKey('kb.content.delete', kbEvent.queueEnabled);
winston.debug('ActivityArchiver kbContentDeleteKey: ' + kbContentDeleteKey);
kbEvent.on(kbContentDeleteKey, function (data) {
setImmediate(() => {
console.log("\n\nkb.content.delete")
try {
if (!data || !data.project_id || !data.kb_id) {
return winston.debug('ActivityArchiver skipping kb.content.delete: missing data');
}
const kb = data.kb || {};
const activity = new Activity({
id_project: data.project_id,
actor: activityActorUtil.actorFromReq(data.req),
verb: 'KB_CONTENT_DELETE',
actionObj: {
name: kb.name,
source: kb.source,
type: kb.type,
namespaceId: data.namespace_id,
namespaceName: data.namespace_name
},
target: kbContentTarget(data.kb_id, kb)
});
save(activity);
} catch (e) {
winston.error('ActivityArchiver error saving kb.content.delete activity', e);
}
});
});
winston.info('ActivityArchiver listening');
}
}
var activityArchiver = new ActivityArchiver();
module.exports = activityArchiver;