From 2587b8afd99354cb7e70b5c72d2abaa130b567c5 Mon Sep 17 00:00:00 2001 From: Omindu Hirushka <102536488+OminduHirushka@users.noreply.github.com> Date: Wed, 9 Jul 2025 07:43:44 +0530 Subject: [PATCH] user activity feed - backend --- .../user-activity-logs-controller.ts | 93 +++++++ worklenz-backend/src/routes/apis/index.ts | 239 +++++++++--------- .../apis/user-activity-logs-api-router.ts | 11 + 3 files changed, 226 insertions(+), 117 deletions(-) create mode 100644 worklenz-backend/src/controllers/user-activity-logs-controller.ts create mode 100644 worklenz-backend/src/routes/apis/user-activity-logs-api-router.ts diff --git a/worklenz-backend/src/controllers/user-activity-logs-controller.ts b/worklenz-backend/src/controllers/user-activity-logs-controller.ts new file mode 100644 index 00000000..c1d859d2 --- /dev/null +++ b/worklenz-backend/src/controllers/user-activity-logs-controller.ts @@ -0,0 +1,93 @@ +import moment from "moment"; +import { IWorkLenzRequest } from "../interfaces/worklenz-request"; +import { IWorkLenzResponse } from "../interfaces/worklenz-response"; + +import db from "../config/db"; + +import { ServerResponse } from "../models/server-response"; +import WorklenzControllerBase from "./worklenz-controller-base"; +import HandleExceptions from "../decorators/handle-exceptions"; +import { formatDuration, formatLogText, getColor } from "../shared/utils"; + +interface IUserRecentTask { + task_id: string; + task_name: string; + project_id: string; + project_name: string; + last_activity_at: string; + activity_count: number; +} + +interface IUserTimeLoggedTask { + task_id: string; + task_name: string; + project_id: string; + project_name: string; + total_time_logged: number; + total_time_logged_string: string; + last_logged_at: string; + logged_by_timer: boolean; +} + +export default class UserActivityLogsController extends WorklenzControllerBase { + @HandleExceptions() + public static async getRecentTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + if (!req.user) { + return res.status(401).send(new ServerResponse(false, null, "Unauthorized")); + } + + const { id: userId } = req.user; + const { offset = 0, limit = 10 } = req.query; + + const q = ` + SELECT tal.id, tal.task_id, t.name AS task_name, tal.project_id, p.name AS project_name, + tal.attribute_type, tal.log_type, tal.old_value, tal.new_value, + tal.prev_string, tal.next_string, tal.created_at AS last_activity_at, + (SELECT COUNT(*) FROM task_activity_logs WHERE task_id = tal.task_id AND user_id = $1) AS activity_count + FROM task_activity_logs tal + JOIN tasks t ON tal.task_id = t.id + JOIN projects p ON tal.project_id = p.id + WHERE tal.user_id = $1 + ORDER BY tal.created_at DESC + LIMIT $2 OFFSET $3; + `; + + const result = await db.query(q, [userId, limit, offset]); + const tasks: IUserRecentTask[] = result.rows; + + return res.status(200).send(new ServerResponse(true, tasks)); + } + + @HandleExceptions() + public static async getTimeLoggedTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { + if (!req.user) { + return res.status(401).send(new ServerResponse(false, null, "Unauthorized")); + } + + const { id: userId } = req.user; + const { offset = 0, limit = 10 } = req.query; + + const q = ` + SELECT twl.task_id, t.name AS task_name, t.project_id, p.name AS project_name, + SUM(twl.time_spent) AS total_time_logged, + MAX(twl.created_at) AS last_logged_at, + MAX(twl.logged_by_timer) AS logged_by_timer + FROM task_work_log twl + JOIN tasks t ON twl.task_id = t.id + JOIN projects p ON t.project_id = p.id + WHERE twl.user_id = $1 + GROUP BY twl.task_id, t.name, t.project_id, p.name + ORDER BY MAX(twl.created_at) DESC + LIMIT $2 OFFSET $3; + `; + + const result = await db.query(q, [userId, limit, offset]); + const tasks: IUserTimeLoggedTask[] = result.rows.map(task => ({ + ...task, + total_time_logged_string: formatDuration(moment.duration(task.total_time_logged, "seconds")), + }) + ); + + return res.status(200).send(new ServerResponse(true, tasks)); + } +} diff --git a/worklenz-backend/src/routes/apis/index.ts b/worklenz-backend/src/routes/apis/index.ts index 5a2019c8..1b0f01bc 100644 --- a/worklenz-backend/src/routes/apis/index.ts +++ b/worklenz-backend/src/routes/apis/index.ts @@ -1,120 +1,125 @@ -import express from "express"; - -import AccessControlsController from "../../controllers/access-controls-controller"; -import AuthController from "../../controllers/auth-controller"; -import LogsController from "../../controllers/logs-controller"; -import OverviewController from "../../controllers/overview-controller"; -import TaskPrioritiesController from "../../controllers/task-priorities-controller"; - -import attachmentsApiRouter from "./attachments-api-router"; -import clientsApiRouter from "./clients-api-router"; -import jobTitlesApiRouter from "./job-titles-api-router"; -import notificationsApiRouter from "./notifications-api-router"; -import personalOverviewApiRouter from "./personal-overview-api-router"; -import projectMembersApiRouter from "./project-members-api-router"; -import projectsApiRouter from "./projects-api-router"; -import settingsApiRouter from "./settings-api-router"; -import statusesApiRouter from "./statuses-api-router"; -import subTasksApiRouter from "./sub-tasks-api-router"; -import taskCommentsApiRouter from "./task-comments-api-router"; -import taskWorkLogApiRouter from "./task-work-log-api-router"; -import tasksApiRouter from "./tasks-api-router"; -import teamMembersApiRouter from "./team-members-api-router"; -import teamsApiRouter from "./teams-api-router"; -import timezonesApiRouter from "./timezones-api-router"; -import todoListApiRouter from "./todo-list-api-router"; -import projectStatusesApiRouter from "./project-statuses-api-router"; -import labelsApiRouter from "./labels-api-router"; -import sharedProjectsApiRouter from "./shared-projects-api-router"; -import resourceAllocationApiRouter from "./resource-allocation-api-router"; -import taskTemplatesApiRouter from "./task-templates-api-router"; -import projectInsightsApiRouter from "./project-insights-api-router"; -import passwordValidator from "../../middlewares/validators/password-validator"; -import adminCenterApiRouter from "./admin-center-api-router"; -import reportingApiRouter from "./reporting-api-router"; -import activityLogsApiRouter from "./activity-logs-api-router"; -import safeControllerFunction from "../../shared/safe-controller-function"; -import projectFoldersApiRouter from "./project-folders-api-router"; -import taskPhasesApiRouter from "./task-phases-api-router"; -import projectCategoriesApiRouter from "./project-categories-api-router"; -import homePageApiRouter from "./home-page-api-router"; -import ganttApiRouter from "./gantt-api-router"; -import projectCommentsApiRouter from "./project-comments-api-router"; -import reportingExportApiRouter from "./reporting-export-api-router"; -import projectHealthsApiRouter from "./project-healths-api-router"; -import ptTasksApiRouter from "./pt-tasks-api-router"; -import projectTemplatesApiRouter from "./project-templates-api"; -import ptTaskPhasesApiRouter from "./pt_task-phases-api-router"; -import ptStatusesApiRouter from "./pt-statuses-api-router"; -import workloadApiRouter from "./gannt-apis/workload-api-router"; -import roadmapApiRouter from "./gannt-apis/roadmap-api-router"; -import scheduleApiRouter from "./gannt-apis/schedule-api-router"; -import scheduleApiV2Router from "./gannt-apis/schedule-api-v2-router"; -import projectManagerApiRouter from "./project-managers-api-router"; - -import billingApiRouter from "./billing-api-router"; -import taskDependenciesApiRouter from "./task-dependencies-api-router"; - -import taskRecurringApiRouter from "./task-recurring-api-router"; - import customColumnsApiRouter from "./custom-columns-api-router"; +import express from "express"; + +import AccessControlsController from "../../controllers/access-controls-controller"; +import AuthController from "../../controllers/auth-controller"; +import LogsController from "../../controllers/logs-controller"; +import OverviewController from "../../controllers/overview-controller"; +import TaskPrioritiesController from "../../controllers/task-priorities-controller"; + +import attachmentsApiRouter from "./attachments-api-router"; +import clientsApiRouter from "./clients-api-router"; +import jobTitlesApiRouter from "./job-titles-api-router"; +import notificationsApiRouter from "./notifications-api-router"; +import personalOverviewApiRouter from "./personal-overview-api-router"; +import projectMembersApiRouter from "./project-members-api-router"; +import projectsApiRouter from "./projects-api-router"; +import settingsApiRouter from "./settings-api-router"; +import statusesApiRouter from "./statuses-api-router"; +import subTasksApiRouter from "./sub-tasks-api-router"; +import taskCommentsApiRouter from "./task-comments-api-router"; +import taskWorkLogApiRouter from "./task-work-log-api-router"; +import tasksApiRouter from "./tasks-api-router"; +import teamMembersApiRouter from "./team-members-api-router"; +import teamsApiRouter from "./teams-api-router"; +import timezonesApiRouter from "./timezones-api-router"; +import todoListApiRouter from "./todo-list-api-router"; +import projectStatusesApiRouter from "./project-statuses-api-router"; +import labelsApiRouter from "./labels-api-router"; +import sharedProjectsApiRouter from "./shared-projects-api-router"; +import resourceAllocationApiRouter from "./resource-allocation-api-router"; +import taskTemplatesApiRouter from "./task-templates-api-router"; +import projectInsightsApiRouter from "./project-insights-api-router"; +import passwordValidator from "../../middlewares/validators/password-validator"; +import adminCenterApiRouter from "./admin-center-api-router"; +import reportingApiRouter from "./reporting-api-router"; +import activityLogsApiRouter from "./activity-logs-api-router"; +import safeControllerFunction from "../../shared/safe-controller-function"; +import projectFoldersApiRouter from "./project-folders-api-router"; +import taskPhasesApiRouter from "./task-phases-api-router"; +import projectCategoriesApiRouter from "./project-categories-api-router"; +import homePageApiRouter from "./home-page-api-router"; +import ganttApiRouter from "./gantt-api-router"; +import projectCommentsApiRouter from "./project-comments-api-router"; +import reportingExportApiRouter from "./reporting-export-api-router"; +import projectHealthsApiRouter from "./project-healths-api-router"; +import ptTasksApiRouter from "./pt-tasks-api-router"; +import projectTemplatesApiRouter from "./project-templates-api"; +import ptTaskPhasesApiRouter from "./pt_task-phases-api-router"; +import ptStatusesApiRouter from "./pt-statuses-api-router"; +import workloadApiRouter from "./gannt-apis/workload-api-router"; +import roadmapApiRouter from "./gannt-apis/roadmap-api-router"; +import scheduleApiRouter from "./gannt-apis/schedule-api-router"; +import scheduleApiV2Router from "./gannt-apis/schedule-api-v2-router"; +import projectManagerApiRouter from "./project-managers-api-router"; + +import billingApiRouter from "./billing-api-router"; +import taskDependenciesApiRouter from "./task-dependencies-api-router"; + +import taskRecurringApiRouter from "./task-recurring-api-router"; + +import customColumnsApiRouter from "./custom-columns-api-router"; + +import userActivityLogsApiRouter from "./user-activity-logs-api-router"; + +const api = express.Router(); + +api.use("/projects", projectsApiRouter); +api.use("/team-members", teamMembersApiRouter); +api.use("/job-titles", jobTitlesApiRouter); +api.use("/clients", clientsApiRouter); +api.use("/teams", teamsApiRouter); +api.use("/tasks", tasksApiRouter); +api.use("/settings", settingsApiRouter); +api.use("/personal-overview", personalOverviewApiRouter); +api.use("/statuses", statusesApiRouter); +api.use("/todo-list", todoListApiRouter); +api.use("/notifications", notificationsApiRouter); +api.use("/attachments", attachmentsApiRouter); +api.use("/sub-tasks", subTasksApiRouter); +api.use("/project-members", projectMembersApiRouter); +api.use("/task-time-log", taskWorkLogApiRouter); +api.use("/task-comments", taskCommentsApiRouter); +api.use("/timezones", timezonesApiRouter); +api.use("/project-statuses", projectStatusesApiRouter); +api.use("/labels", labelsApiRouter); +api.use("/resource-allocation", resourceAllocationApiRouter); +api.use("/shared/projects", sharedProjectsApiRouter); +api.use("/task-templates", taskTemplatesApiRouter); +api.use("/project-insights", projectInsightsApiRouter); +api.use("/admin-center", adminCenterApiRouter); +api.use("/reporting", reportingApiRouter); +api.use("/activity-logs", activityLogsApiRouter); +api.use("/projects-folders", projectFoldersApiRouter); +api.use("/task-phases", taskPhasesApiRouter); +api.use("/project-categories", projectCategoriesApiRouter); +api.use("/home", homePageApiRouter); +api.use("/gantt", ganttApiRouter); +api.use("/project-comments", projectCommentsApiRouter); +api.use("/reporting-export", reportingExportApiRouter); +api.use("/project-healths", projectHealthsApiRouter); +api.use("/project-templates", projectTemplatesApiRouter); +api.use("/pt-tasks", ptTasksApiRouter); +api.use("/pt-task-phases", ptTaskPhasesApiRouter); +api.use("/pt-statuses", ptStatusesApiRouter); +api.use("/workload-gannt", workloadApiRouter); +api.use("/roadmap-gannt", roadmapApiRouter); +api.use("/schedule-gannt", scheduleApiRouter); +api.use("/schedule-gannt-v2", scheduleApiV2Router); +api.use("/project-managers", projectManagerApiRouter); + +api.get("/overview/:id", safeControllerFunction(OverviewController.getById)); +api.get("/task-priorities", safeControllerFunction(TaskPrioritiesController.get)); +api.post("/change-password", passwordValidator, safeControllerFunction(AuthController.changePassword)); +api.get("/access-controls/roles", safeControllerFunction(AccessControlsController.getRoles)); +api.get("/logs/my-dashboard", safeControllerFunction(LogsController.getActivityLog)); + +api.use("/billing", billingApiRouter); +api.use("/task-dependencies", taskDependenciesApiRouter); + +api.use("/task-recurring", taskRecurringApiRouter); -const api = express.Router(); - -api.use("/projects", projectsApiRouter); -api.use("/team-members", teamMembersApiRouter); -api.use("/job-titles", jobTitlesApiRouter); -api.use("/clients", clientsApiRouter); -api.use("/teams", teamsApiRouter); -api.use("/tasks", tasksApiRouter); -api.use("/settings", settingsApiRouter); -api.use("/personal-overview", personalOverviewApiRouter); -api.use("/statuses", statusesApiRouter); -api.use("/todo-list", todoListApiRouter); -api.use("/notifications", notificationsApiRouter); -api.use("/attachments", attachmentsApiRouter); -api.use("/sub-tasks", subTasksApiRouter); -api.use("/project-members", projectMembersApiRouter); -api.use("/task-time-log", taskWorkLogApiRouter); -api.use("/task-comments", taskCommentsApiRouter); -api.use("/timezones", timezonesApiRouter); -api.use("/project-statuses", projectStatusesApiRouter); -api.use("/labels", labelsApiRouter); -api.use("/resource-allocation", resourceAllocationApiRouter); -api.use("/shared/projects", sharedProjectsApiRouter); -api.use("/task-templates", taskTemplatesApiRouter); -api.use("/project-insights", projectInsightsApiRouter); -api.use("/admin-center", adminCenterApiRouter); -api.use("/reporting", reportingApiRouter); -api.use("/activity-logs", activityLogsApiRouter); -api.use("/projects-folders", projectFoldersApiRouter); -api.use("/task-phases", taskPhasesApiRouter); -api.use("/project-categories", projectCategoriesApiRouter); -api.use("/home", homePageApiRouter); -api.use("/gantt", ganttApiRouter); -api.use("/project-comments", projectCommentsApiRouter); -api.use("/reporting-export", reportingExportApiRouter); -api.use("/project-healths", projectHealthsApiRouter); -api.use("/project-templates", projectTemplatesApiRouter); -api.use("/pt-tasks", ptTasksApiRouter); -api.use("/pt-task-phases", ptTaskPhasesApiRouter); -api.use("/pt-statuses", ptStatusesApiRouter); -api.use("/workload-gannt", workloadApiRouter); -api.use("/roadmap-gannt", roadmapApiRouter); -api.use("/schedule-gannt", scheduleApiRouter); -api.use("/schedule-gannt-v2", scheduleApiV2Router); -api.use("/project-managers", projectManagerApiRouter); - -api.get("/overview/:id", safeControllerFunction(OverviewController.getById)); -api.get("/task-priorities", safeControllerFunction(TaskPrioritiesController.get)); -api.post("/change-password", passwordValidator, safeControllerFunction(AuthController.changePassword)); -api.get("/access-controls/roles", safeControllerFunction(AccessControlsController.getRoles)); -api.get("/logs/my-dashboard", safeControllerFunction(LogsController.getActivityLog)); - -api.use("/billing", billingApiRouter); -api.use("/task-dependencies", taskDependenciesApiRouter); - -api.use("/task-recurring", taskRecurringApiRouter); - api.use("/custom-columns", customColumnsApiRouter); -export default api; +api.use("/logs", userActivityLogsApiRouter); + +export default api; diff --git a/worklenz-backend/src/routes/apis/user-activity-logs-api-router.ts b/worklenz-backend/src/routes/apis/user-activity-logs-api-router.ts new file mode 100644 index 00000000..b6f07930 --- /dev/null +++ b/worklenz-backend/src/routes/apis/user-activity-logs-api-router.ts @@ -0,0 +1,11 @@ +import express from 'express'; + +import UserActivityLogsController from '../../controllers/user-activity-logs-controller'; +import safeControllerFunction from "../../shared/safe-controller-function"; + +const userActivityLogsApiRouter = express.Router(); + +userActivityLogsApiRouter.get('/user-recent-tasks', safeControllerFunction(UserActivityLogsController.getRecentTasks)); +userActivityLogsApiRouter.get('/user-time-logged-tasks', safeControllerFunction(UserActivityLogsController.getTimeLoggedTasks)); + +export default userActivityLogsApiRouter; \ No newline at end of file