@tiledesk/tiledesk-server
Version:
The Tiledesk server module
326 lines (268 loc) • 7.53 kB
JavaScript
'use strict';
const Subscription = require('../models/subscription');
const Faq_kb = require('../models/faq_kb');
function systemActor() {
return { type: 'system', id: 'system', name: 'System' };
}
function userDisplayName(user) {
if (!user) {
return undefined;
}
const fullName = user.fullName || user.fullname;
if (fullName && String(fullName).trim()) {
return String(fullName).trim();
}
const name = ((user.firstname || '') + ' ' + (user.lastname || '')).trim();
if (name) {
return name;
}
return undefined;
}
function resolveUserId(userRef) {
if (userRef === undefined || userRef === null) {
return '';
}
if (typeof userRef === 'string' || typeof userRef === 'number') {
return String(userRef);
}
if (userRef._id) {
return String(userRef._id);
}
if (userRef.id) {
return String(userRef.id);
}
return String(userRef);
}
function isBotActor(user) {
if (!user) {
return false;
}
if (user instanceof Faq_kb) {
return true;
}
if (user.constructor && user.constructor.modelName === 'faq_kb') {
return true;
}
if (user.sub === 'bot' || (typeof user.sub === 'string' && user.sub.endsWith('/bot'))) {
return true;
}
if (typeof user.aud === 'string' && user.aud.indexOf('/bots/') > -1) {
return true;
}
if (user.type === 'tilebot' || user.subtype === 'chatbot') {
return true;
}
// Cached/plain faq_kb payloads may miss type/subtype but still carry bot identity fields.
if (
user.slug &&
user.name &&
user.id_project &&
user.createdBy &&
!user.email &&
!user.firstname &&
!user.lastname
) {
return true;
}
return false;
}
function botDisplayName(user) {
if (!user || !user.name) {
return undefined;
}
const name = String(user.name).trim();
return name || undefined;
}
function actorFromPrincipal(user) {
if (!user) {
return systemActor();
}
if (isSubscriptionActor(user)) {
return systemActor();
}
if (isBotActor(user)) {
const id = resolveUserId(user);
return {
type: 'bot',
id: id,
name: botDisplayName(user) || id
};
}
return {
type: 'user',
id: resolveUserId(user),
name: user.fullName || user.fullname || userDisplayName(user) || undefined
};
}
function isSubscriptionActor(user) {
if (!user) {
return false;
}
if (user instanceof Subscription) {
return true;
}
if (user.constructor && user.constructor.modelName === 'subscription') {
return true;
}
// Webhook subscription document (no user profile fields)
return Boolean(
user.event &&
user.target &&
user.id_project &&
user.createdBy &&
!user.email &&
!user.firstname &&
!user.lastname
);
}
function isSystemAvailabilityInitiator(req) {
if (!req || !req.body) {
return false;
}
return req.body.availabilityInitiator === 'system';
}
function isAvailabilityUpdate(body) {
if (!body) {
return false;
}
return body.user_available !== undefined || body.profileStatus !== undefined;
}
function buildProjectUserUpdateContext(req, previousUserAvailable, previousProfileStatus, targetUserId) {
const context = {
previousUserAvailable,
previousProfileStatus,
source: 'api',
updateType: 'admin'
};
if (!req || !req.user) {
context.source = 'system';
context.updateType = 'system';
return context;
}
if (isSubscriptionActor(req.user) || isSystemAvailabilityInitiator(req)) {
context.source = isSubscriptionActor(req.user) ? 'subscription' : 'api';
context.updateType = 'system';
return context;
}
const actorUserId = resolveUserId(req.user);
const targetId = resolveUserId(targetUserId);
const isSelfRoute = !req.params || !req.params.project_userid;
if (isSelfRoute) {
context.updateType = 'self';
return context;
}
if (actorUserId && targetId && actorUserId === targetId) {
context.updateType = 'self';
}
return context;
}
function actorFromUpdateContext(req, updateContext) {
if (
!req ||
!req.user ||
(updateContext && updateContext.updateType === 'system') ||
isSubscriptionActor(req.user) ||
isSystemAvailabilityInitiator(req)
) {
return systemActor();
}
return {
type: 'user',
id: resolveUserId(req.user),
name: userDisplayName(req.user)
};
}
function actorFromProjectUserUpdate(event, verb, updateContext) {
const actor = actorFromUpdateContext(event.req, updateContext);
if (actor.name) {
return actor;
}
const project_user = event.updatedProject_userPopulated;
const targetUser = project_user && project_user.id_user;
const targetName = userDisplayName(targetUser);
if (
targetName &&
(verb === 'PROJECT_USER_AVAILABILITY_SELF' ||
resolveUserId(targetUser) === actor.id)
) {
return Object.assign({}, actor, { name: targetName });
}
return actor;
}
function verbForProjectUserUpdate(body, updateContext) {
if (!isAvailabilityUpdate(body)) {
return 'PROJECT_USER_UPDATE';
}
if (updateContext && updateContext.updateType === 'system') {
return 'PROJECT_USER_AVAILABILITY_SYSTEM';
}
if (updateContext && updateContext.updateType === 'self') {
return 'PROJECT_USER_AVAILABILITY_SELF';
}
return 'PROJECT_USER_UPDATE';
}
function availabilityStatusLabel({ user_available, profileStatus }) {
if (profileStatus !== undefined && profileStatus !== null && profileStatus !== '') {
return String(profileStatus);
}
if (user_available === true) {
return 'available';
}
if (user_available === false) {
return 'unavailable';
}
return 'unknown';
}
function reconcileAvailabilityVerb(event, project_user, verb, updateContext) {
if (!isAvailabilityUpdate(event.req && event.req.body)) {
return { verb: verb, updateContext: updateContext, actor: null };
}
const context = Object.assign({}, updateContext);
let resolvedVerb = verb;
const targetUserId = resolveUserId(project_user && project_user.id_user);
const actorUserId = resolveUserId(event.req && event.req.user);
if (isSubscriptionActor(event.req && event.req.user) || isSystemAvailabilityInitiator(event.req)) {
context.updateType = 'system';
context.source = isSubscriptionActor(event.req.user) ? 'subscription' : (context.source || 'api');
resolvedVerb = 'PROJECT_USER_AVAILABILITY_SYSTEM';
return {
verb: resolvedVerb,
updateContext: context,
actor: systemActor()
};
}
if (resolvedVerb === 'PROJECT_USER_AVAILABILITY_SELF' && targetUserId && actorUserId && actorUserId !== targetUserId) {
context.updateType = 'admin';
resolvedVerb = 'PROJECT_USER_UPDATE';
return { verb: resolvedVerb, updateContext: context, actor: null };
}
return { verb: resolvedVerb, updateContext: context, actor: null };
}
function verbFromAvailabilityUpdateType(updateType) {
switch (updateType) {
case 'system':
return 'PROJECT_USER_AVAILABILITY_SYSTEM';
case 'self':
return 'PROJECT_USER_AVAILABILITY_SELF';
default:
return 'PROJECT_USER_UPDATE';
}
}
module.exports = {
systemActor,
userDisplayName,
resolveUserId,
isBotActor,
botDisplayName,
actorFromPrincipal,
isSubscriptionActor,
isSystemAvailabilityInitiator,
isAvailabilityUpdate,
buildProjectUserUpdateContext,
actorFromUpdateContext,
actorFromProjectUserUpdate,
verbForProjectUserUpdate,
availabilityStatusLabel,
reconcileAvailabilityVerb,
verbFromAvailabilityUpdateType
};