tududi/backend/models/task_event.js
Antonis Anastasiadis e594d1075b
Linting cleanup (#99)
* Add eslint and prettier dependencies and configs

* Lint project.
2025-07-01 11:40:09 +03:00

282 lines
8.6 KiB
JavaScript

const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const TaskEvent = sequelize.define(
'TaskEvent',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
task_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'tasks',
key: 'id',
},
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'users',
key: 'id',
},
},
event_type: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isIn: [
[
'created',
'status_changed',
'priority_changed',
'due_date_changed',
'project_changed',
'name_changed',
'description_changed',
'note_changed',
'completed',
'archived',
'deleted',
'restored',
'today_changed',
'tags_changed',
'recurrence_changed',
'recurrence_type_changed',
'completion_based_changed',
'recurrence_end_date_changed',
],
],
},
},
old_value: {
type: DataTypes.TEXT,
allowNull: true,
get() {
const rawValue = this.getDataValue('old_value');
return rawValue ? JSON.parse(rawValue) : null;
},
set(value) {
this.setDataValue(
'old_value',
value ? JSON.stringify(value) : null
);
},
},
new_value: {
type: DataTypes.TEXT,
allowNull: true,
get() {
const rawValue = this.getDataValue('new_value');
return rawValue ? JSON.parse(rawValue) : null;
},
set(value) {
this.setDataValue(
'new_value',
value ? JSON.stringify(value) : null
);
},
},
field_name: {
type: DataTypes.STRING,
allowNull: true,
validate: {
isIn: [
[
'status',
'priority',
'due_date',
'project_id',
'name',
'description',
'note',
'today',
'tags',
'recurrence_type',
'recurrence_interval',
'recurrence_end_date',
'recurrence_weekday',
'recurrence_month_day',
'recurrence_week_of_month',
'completion_based',
],
],
},
},
metadata: {
type: DataTypes.TEXT,
allowNull: true,
get() {
const rawValue = this.getDataValue('metadata');
return rawValue ? JSON.parse(rawValue) : null;
},
set(value) {
this.setDataValue(
'metadata',
value ? JSON.stringify(value) : null
);
},
},
},
{
tableName: 'task_events',
timestamps: true,
createdAt: 'created_at',
updatedAt: false, // We don't need updated_at for events (they're immutable)
indexes: [
{
fields: ['task_id'],
},
{
fields: ['user_id'],
},
{
fields: ['event_type'],
},
{
fields: ['created_at'],
},
{
fields: ['task_id', 'event_type'],
},
{
fields: ['task_id', 'created_at'],
},
],
}
);
// Define associations
TaskEvent.associate = function (models) {
// TaskEvent belongs to Task
TaskEvent.belongsTo(models.Task, {
foreignKey: 'task_id',
as: 'Task',
});
// TaskEvent belongs to User
TaskEvent.belongsTo(models.User, {
foreignKey: 'user_id',
as: 'User',
});
};
// Helper methods for common event types
TaskEvent.createStatusChangeEvent = async function (
taskId,
userId,
oldStatus,
newStatus,
metadata = {}
) {
return await TaskEvent.create({
task_id: taskId,
user_id: userId,
event_type: 'status_changed',
field_name: 'status',
old_value: { status: oldStatus },
new_value: { status: newStatus },
metadata: metadata,
});
};
TaskEvent.createTaskCreatedEvent = async function (
taskId,
userId,
taskData,
metadata = {}
) {
return await TaskEvent.create({
task_id: taskId,
user_id: userId,
event_type: 'created',
field_name: null,
old_value: null,
new_value: taskData,
metadata: metadata,
});
};
TaskEvent.createFieldChangeEvent = async function (
taskId,
userId,
fieldName,
oldValue,
newValue,
metadata = {}
) {
const eventType =
fieldName === 'status' && newValue === 2
? 'completed'
: fieldName === 'status' && newValue === 3
? 'archived'
: `${fieldName}_changed`;
return await TaskEvent.create({
task_id: taskId,
user_id: userId,
event_type: eventType,
field_name: fieldName,
old_value: { [fieldName]: oldValue },
new_value: { [fieldName]: newValue },
metadata: metadata,
});
};
// Query helpers
TaskEvent.getTaskTimeline = async function (taskId) {
return await TaskEvent.findAll({
where: { task_id: taskId },
order: [['created_at', 'ASC']],
include: [
{
model: sequelize.models.User,
as: 'User',
attributes: ['id', 'name', 'email'],
},
],
});
};
TaskEvent.getCompletionTime = async function (taskId) {
const events = await TaskEvent.findAll({
where: {
task_id: taskId,
event_type: ['status_changed', 'created', 'completed'],
},
order: [['created_at', 'ASC']],
});
if (events.length === 0) return null;
const startEvent = events.find(
(e) =>
e.event_type === 'created' ||
(e.event_type === 'status_changed' && e.new_value?.status === 1) // in_progress
);
const completedEvent = events.find(
(e) =>
e.event_type === 'completed' ||
(e.event_type === 'status_changed' && e.new_value?.status === 2) // done
);
if (!startEvent || !completedEvent) return null;
const startTime = new Date(startEvent.created_at);
const endTime = new Date(completedEvent.created_at);
return {
started_at: startTime,
completed_at: endTime,
duration_ms: endTime - startTime,
duration_hours: (endTime - startTime) / (1000 * 60 * 60),
};
};
return TaskEvent;
};