DTP Base provides a scalable and secure Node.js application development harness ready for production service.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

329 lines
8.3 KiB

// task.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
import mongoose from 'mongoose';
const Task = mongoose.model('Task');
const TaskSession = mongoose.model('TaskSession');
import dayjs from 'dayjs';
import { SiteService, SiteError } from '../../lib/site-lib.js';
export default class TaskService extends SiteService {
static get name ( ) { return 'TaskService'; }
static get slug () { return 'task'; }
constructor (dtp) {
super(dtp, TaskService);
}
async start ( ) {
const { client: clientService, user: userService } = this.dtp.services;
// const { jobQueue: jobQueueService } = this.dtp.services;
// this.linksQueue = jobQueueService.getJobQueue('links', this.dtp.config.jobQueues.links);
this.populateTask = [
{
path: 'user',
select: userService.USER_SELECT,
},
{
path: 'client',
populate: clientService.populateClient,
},
{
path: 'project',
populate: clientService.populateClientProject,
},
];
this.populateTaskSession = [
{
path: 'user',
select: userService.USER_SELECT,
},
{
path: 'client',
populate: clientService.populateClient,
},
{
path: 'project',
populate: clientService.populateClientProject,
},
{
path: 'task',
populate: this.populateTask,
},
{
path: 'screenshots.image',
}
];
}
async createTask (user, taskDefinition) {
const { text: textService } = this.dtp.services;
const NOW = new Date();
const task = new Task();
task.created = NOW;
task.user = user._id;
task.client = mongoose.Types.ObjectId.createFromHexString(taskDefinition.clientId);
task.project = mongoose.Types.ObjectId.createFromHexString(taskDefinition.projectId);
task.status = 'pending';
task.note = textService.filter(taskDefinition.note);
await task.save();
return task.toObject();
}
async startTask (task) {
if (task.status !== 'pending') {
throw new SiteError(400, 'The task is not in the pending state');
}
await Task.updateOne(
{ _id: task._id },
{ $set: { status: 'active' } },
);
}
async closeTask (task) {
await TaskSession
.find({ task: task._id, status: 'active' })
.cursor()
.eachAsync(async (session) => {
await this.closeTaskSession(session);
});
await Task.updateOne(
{ _id: task._id },
{ $set: { status: 'finished' } },
);
}
async getTasksForUser (user, options, pagination) {
const search = { user: user._id };
options = options || { };
if (options.pending) {
search.status = 'pending';
} else if (options.active) {
search.status = 'active';
} else if (options.finished) {
search.status = 'finished';
}
const tasks = await Task
.find(search)
.sort({ created: -1 })
.skip(pagination.skip)
.limit(pagination.cpp)
.populate(this.populateTask)
.lean();
return tasks.reverse();
}
async getTasksForProject (project, options, pagination) {
const search = { project: project._id };
options = options || { };
if (options.pending) {
search.status = 'pending';
} else if (options.active) {
search.status = 'active';
} else if (options.finished) {
search.status = 'finished';
}
const tasks = await Task
.find(search)
.sort({ created: -1 })
.skip(pagination.skip)
.limit(pagination.cpp)
.populate(this.populateTask)
.lean();
return tasks.reverse();
}
async getTaskGridForUser (user) {
const pagination = { skip: 0, cpp: 10 };
const pendingTasks = await this.getTasksForUser(user, { pending: true }, pagination);
const activeTasks = await this.getTasksForUser(user, { active: true }, pagination);
const finishedTasks = await this.getTasksForUser(user, { finished: true }, pagination);
return { pendingTasks, activeTasks, finishedTasks };
}
async getTaskGridForProject (project) {
const pagination = { skip: 0, cpp: 10 };
const pendingTasks = await this.getTasksForProject(project, { pending: true }, pagination);
const activeTasks = await this.getTasksForProject(project, { active: true }, pagination);
const finishedTasks = await this.getTasksForProject(project, { finished: true }, pagination);
return { pendingTasks, activeTasks, finishedTasks };
}
async getTaskById (taskId) {
const task = await Task
.findOne({ _id: taskId })
.populate(this.populateTask)
.lean();
return task;
}
async createTaskSession (task) {
const NOW = new Date();
if (await TaskSession.findOne({ user: task.user._id, status: 'active' })) {
throw new SiteError(401, "Can't start new session with a currently active session.");
}
let session = new TaskSession();
session.created = NOW;
session.lastUpdated = NOW;
session.hourlyRate = task.project.hourlyRate;
session.user = task.user._id;
session.client = task.client._id;
session.project = task.project._id;
session.task = task._id;
session.status = 'active';
await session.save();
session = await TaskSession.populate(session, this.populateTaskSession);
this.log.info('task session created', {
user: {
_id: task.user._id,
username: task.user.username,
},
session: {
_id: session._id,
},
});
return session.toObject();
}
async addTaskSessionScreenshot (session, file) {
const NOW = new Date();
const { image: imageService } = this.dtp.services;
const image = await imageService.create(session.user._id, { }, file);
await TaskSession.updateOne(
{ _id: session._id },
{
$push: {
screenshots: {
created: NOW,
image: image._id,
},
},
},
);
const displayList = this.createDisplayList('screenshot-accepted');
displayList.showNotification(
'Screenshot accepted',
'success',
'bottom-center',
3000,
);
this.dtp.emitter
.to(session.task._id.toString())
.emit('session-control', {
displayList,
});
}
async setTaskSessionStatus (session, status) {
if (status === session.status) {
return; // do nothing
}
if (!['active', 'reconnecting'].includes(status)) {
throw new SiteError(400, 'Can only set status to active or reconnecting');
}
this.log.info('updating task session status', {
user: {
_id: session.user._id,
username: session.user.username,
},
session: { _id: session._id },
status,
});
await TaskSession.updateOne({ _id: session._id }, { $set: { status } });
}
async closeTaskSession (session, status = 'finished') {
const { client: clientService } = this.dtp.services;
const NOW = new Date();
if (session.status !== 'active') {
throw new SiteError(400, 'The session is not currently active');
}
const duration = dayjs(NOW).diff(session.created, 'second');
await TaskSession.updateOne(
{ _id: session._id },
{
$set: {
finished: NOW,
status,
duration,
},
},
);
await Task.updateOne(
{ _id: session.task._id },
{
$inc: { duration },
},
);
await clientService.addTimeWorked(session.client, duration);
this.log.info('task session closed', {
user: {
_id: session.user._id,
username: session.user.username,
},
duration,
});
}
async getSessionsForTask (task, pagination) {
const sessions = TaskSession
.find({ task: task._id })
.sort({ created: 1 })
.skip(pagination.skip)
.limit(pagination.cpp)
.populate(this.populateTaskSession)
.lean();
return sessions;
}
async getTaskSessionById (sessionId) {
const session = await TaskSession
.findOne({ _id: sessionId })
.populate(this.populateTaskSession)
.lean();
return session;
}
async closeTaskSessionForUser (user) {
await TaskSession
.find({ user: user._id, status: 'active' })
.populate(this.populateTaskSession)
.cursor()
.eachAsync(async (session) => {
await this.closeTaskSession(session);
});
}
}