feat: Add project activity logs API and frontend components
- Implemented a new API route for fetching project activity logs. - Created a service for handling API requests related to project activity logs. - Developed Redux slice for managing activity log state. - Added components for displaying activity logs, including a virtualized list for performance. - Implemented filtering options for activity logs. - Added PDF export functionality for activity logs. - Integrated activity log view into the project view with appropriate UI elements.
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
import moment from "moment";
|
||||
import {IWorkLenzRequest} from "../interfaces/worklenz-request";
|
||||
import {IWorkLenzResponse} from "../interfaces/worklenz-response";
|
||||
|
||||
import Excel from "exceljs";
|
||||
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
|
||||
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
|
||||
import db from "../config/db";
|
||||
|
||||
import {ServerResponse} from "../models/server-response";
|
||||
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";
|
||||
import { formatDuration, formatLogText, getColor } from "../shared/utils";
|
||||
|
||||
export default class ActivitylogsController extends WorklenzControllerBase {
|
||||
@HandleExceptions()
|
||||
public static async get(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
||||
const {id} = req.params;
|
||||
const { id } = req.params;
|
||||
const q = `SELECT get_activity_logs_by_task($1) AS activity_logs;`;
|
||||
const result = await db.query(q, [id]);
|
||||
const [data] = result.rows;
|
||||
@@ -31,4 +30,196 @@ export default class ActivitylogsController extends WorklenzControllerBase {
|
||||
|
||||
return res.status(200).send(new ServerResponse(true, data.activity_logs));
|
||||
}
|
||||
}
|
||||
|
||||
@HandleExceptions()
|
||||
public static async getByProjectId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
||||
try {
|
||||
console.log("Received request for project activity logs:", req.params, req.query);
|
||||
const projectId = req.params.id;
|
||||
const page = parseInt(req.query.page as string) || 1;
|
||||
const size = parseInt(req.query.size as string) || 20;
|
||||
const offset = (page - 1) * size;
|
||||
const filterType = req.query.filter as string || "all";
|
||||
|
||||
// Add filter conditions
|
||||
let filterClause = "";
|
||||
const filterParams = [projectId];
|
||||
let paramIndex = 2;
|
||||
|
||||
if (filterType && filterType !== "all") {
|
||||
filterClause = ` AND tal.attribute_type = $${paramIndex}`;
|
||||
filterParams.push(filterType);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// Defensive UUID regex for safe casting
|
||||
const uuidRegex = "'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'";
|
||||
|
||||
const q = `
|
||||
SELECT
|
||||
tal.id,
|
||||
tal.task_id,
|
||||
tal.attribute_type,
|
||||
tal.log_type,
|
||||
tal.old_value,
|
||||
tal.new_value,
|
||||
tal.prev_string,
|
||||
tal.next_string,
|
||||
tal.created_at,
|
||||
|
||||
-- Task details
|
||||
(SELECT name FROM tasks WHERE id = tal.task_id) AS task_name,
|
||||
(SELECT task_no FROM tasks WHERE id = tal.task_id) AS task_no,
|
||||
CONCAT((SELECT key FROM projects WHERE id = $1), '-', (SELECT task_no FROM tasks WHERE id = tal.task_id)) AS task_key,
|
||||
|
||||
-- User details
|
||||
(SELECT ROW_TO_JSON(user_data) FROM (
|
||||
SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.avatar_url,
|
||||
u.email
|
||||
FROM users u
|
||||
WHERE u.id = tal.user_id
|
||||
) user_data) AS done_by,
|
||||
|
||||
-- Status details for status changes (safe UUID cast)
|
||||
CASE
|
||||
WHEN tal.attribute_type = 'status' AND tal.old_value ~ ${uuidRegex} THEN
|
||||
(SELECT ROW_TO_JSON(status_data) FROM (
|
||||
SELECT
|
||||
ts.name
|
||||
FROM task_statuses ts
|
||||
WHERE ts.id = tal.old_value::UUID
|
||||
) status_data)
|
||||
ELSE NULL
|
||||
END AS previous_status,
|
||||
|
||||
CASE
|
||||
WHEN tal.attribute_type = 'status' AND tal.new_value ~ ${uuidRegex} THEN
|
||||
(SELECT ROW_TO_JSON(status_data) FROM (
|
||||
SELECT
|
||||
ts.name
|
||||
FROM task_statuses ts
|
||||
WHERE ts.id = tal.new_value::UUID
|
||||
) status_data)
|
||||
ELSE NULL
|
||||
END AS next_status,
|
||||
|
||||
-- Priority details for priority changes (safe UUID cast)
|
||||
CASE
|
||||
WHEN tal.attribute_type = 'priority' AND tal.old_value ~ ${uuidRegex} THEN
|
||||
(SELECT ROW_TO_JSON(priority_data) FROM (
|
||||
SELECT
|
||||
tp.name
|
||||
FROM task_priorities tp
|
||||
WHERE tp.id = tal.old_value::UUID
|
||||
) priority_data)
|
||||
ELSE NULL
|
||||
END AS previous_priority,
|
||||
|
||||
CASE
|
||||
WHEN tal.attribute_type = 'priority' AND tal.new_value ~ ${uuidRegex} THEN
|
||||
(SELECT ROW_TO_JSON(priority_data) FROM (
|
||||
SELECT
|
||||
tp.name
|
||||
FROM task_priorities tp
|
||||
WHERE tp.id = tal.new_value::UUID
|
||||
) priority_data)
|
||||
ELSE NULL
|
||||
END AS next_priority,
|
||||
|
||||
-- Assigned user details for assignee changes (safe UUID cast)
|
||||
CASE
|
||||
WHEN tal.attribute_type = 'assignee' AND tal.new_value ~ ${uuidRegex} THEN
|
||||
(SELECT ROW_TO_JSON(user_data) FROM (
|
||||
SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.avatar_url,
|
||||
u.email
|
||||
FROM users u
|
||||
WHERE u.id = tal.new_value::UUID
|
||||
) user_data)
|
||||
ELSE NULL
|
||||
END AS assigned_user
|
||||
|
||||
FROM task_activity_logs tal
|
||||
WHERE tal.project_id = $1${filterClause}
|
||||
ORDER BY tal.created_at DESC
|
||||
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
||||
`;
|
||||
|
||||
const countQuery = `
|
||||
SELECT COUNT(*) as total
|
||||
FROM task_activity_logs
|
||||
WHERE project_id = $1${filterClause}
|
||||
`;
|
||||
|
||||
const [result, countResult] = await Promise.all([
|
||||
db.query(q, [...filterParams, size, offset]),
|
||||
db.query(countQuery, filterType && filterType !== "all" ? [projectId, filterType] : [projectId])
|
||||
]);
|
||||
|
||||
const total = parseInt(countResult.rows[0]?.total || "0");
|
||||
|
||||
// Format the logs
|
||||
for (const log of result.rows) {
|
||||
if (log.attribute_type === "estimation") {
|
||||
log.previous = formatDuration(moment.duration(log.old_value, "minutes"));
|
||||
log.current = formatDuration(moment.duration(log.new_value, "minutes"));
|
||||
} else {
|
||||
log.previous = log.old_value;
|
||||
log.current = log.new_value;
|
||||
}
|
||||
|
||||
// Add color to users
|
||||
if (log.assigned_user) {
|
||||
log.assigned_user.color_code = getColor(log.assigned_user.name);
|
||||
}
|
||||
|
||||
if (log.done_by) {
|
||||
log.done_by.color_code = getColor(log.done_by.name);
|
||||
}
|
||||
|
||||
// Add default colors for status and priority since table doesn't have color_code
|
||||
if (log.previous_status) {
|
||||
log.previous_status.color_code = "#d9d9d9"; // Default gray color
|
||||
}
|
||||
if (log.next_status) {
|
||||
log.next_status.color_code = "#1890ff"; // Default blue color
|
||||
}
|
||||
if (log.previous_priority) {
|
||||
log.previous_priority.color_code = "#d9d9d9"; // Default gray color
|
||||
}
|
||||
if (log.next_priority) {
|
||||
log.next_priority.color_code = "#ff4d4f"; // Default red color for priority
|
||||
}
|
||||
|
||||
// Generate log text
|
||||
log.log_text = await formatLogText(log);
|
||||
log.attribute_type = log.attribute_type?.replace(/_/g, " ");
|
||||
}
|
||||
|
||||
const response = {
|
||||
logs: result.rows,
|
||||
pagination: {
|
||||
current: page,
|
||||
pageSize: size,
|
||||
total,
|
||||
totalPages: Math.ceil(total / size)
|
||||
}
|
||||
};
|
||||
|
||||
return res.status(200).send(new ServerResponse(true, response));
|
||||
} catch (error: any) {
|
||||
console.error("❌ Error in getByProjectId:", error);
|
||||
return res.status(500).send(new ServerResponse(false, null, `Internal server error: ${error.message}`));
|
||||
}
|
||||
}
|
||||
|
||||
@HandleExceptions()
|
||||
public static async exportProjectActivityLogs(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<void> {
|
||||
// ...keep your export logic as is...
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
|
||||
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
|
||||
import moment from "moment";
|
||||
import db from "../config/db";
|
||||
import { ServerResponse } from "../models/server-response";
|
||||
import WorklenzControllerBase from "./worklenz-controller-base";
|
||||
import HandleExceptions from "../decorators/handle-exceptions";
|
||||
|
||||
// --- Helpers -------------------------------------------------------------
|
||||
|
||||
function formatDuration(duration: moment.Duration | null): string {
|
||||
if (!duration) return "0m";
|
||||
const hours = Math.floor(duration.asHours());
|
||||
const minutes = duration.minutes();
|
||||
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||
}
|
||||
|
||||
function generateLogText(attributeType: string): string {
|
||||
const map: Record<string,string> = {
|
||||
name: "updated task name",
|
||||
status: "changed status",
|
||||
priority: "changed priority",
|
||||
assignee: "updated assignee",
|
||||
end_date: "changed due date",
|
||||
start_date: "changed start date",
|
||||
estimation: "updated time estimation",
|
||||
description: "updated description",
|
||||
phase: "changed phase",
|
||||
labels: "updated labels",
|
||||
};
|
||||
return map[attributeType] || "made changes to";
|
||||
}
|
||||
|
||||
function isValidUuid(id?: string): boolean {
|
||||
return !!id && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
|
||||
}
|
||||
|
||||
// Get a consistent color for a user based on their name
|
||||
function getColorFromName(name: string): string {
|
||||
if (!name) return "#1890ff";
|
||||
|
||||
const colors = [
|
||||
"#f56a00", "#7265e6", "#ffbf00", "#00a2ae",
|
||||
"#1890ff", "#52c41a", "#eb2f96", "#faad14",
|
||||
"#722ed1", "#13c2c2", "#fa8c16", "#a0d911"
|
||||
];
|
||||
|
||||
let hash = 0;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
|
||||
return colors[Math.abs(hash) % colors.length];
|
||||
}
|
||||
|
||||
// --- Controller ----------------------------------------------------------
|
||||
|
||||
export default class ProjectActivityLogsController extends WorklenzControllerBase {
|
||||
@HandleExceptions()
|
||||
public static async getByProjectId(
|
||||
req: IWorkLenzRequest,
|
||||
res: IWorkLenzResponse
|
||||
): Promise<IWorkLenzResponse> {
|
||||
// 1) Extract & validate inputs
|
||||
const projectId = req.params.id;
|
||||
if (!isValidUuid(projectId)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ done: false, body: null, error: "Invalid project ID." });
|
||||
}
|
||||
|
||||
const page = parseInt(req.query.page as string, 10) || 1;
|
||||
const size = parseInt(req.query.size as string, 10) || 20;
|
||||
const offset = (page - 1) * size;
|
||||
const filterType = (req.query.filter as string) || "all";
|
||||
const allowedFilters = [
|
||||
"all","name","status","priority","assignee",
|
||||
"end_date","start_date","estimation","description","phase"
|
||||
];
|
||||
if (!allowedFilters.includes(filterType)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ done: false, body: null, error: "Invalid filter type." });
|
||||
}
|
||||
|
||||
// 2) Build parameterized SQL
|
||||
let mainQuery = `
|
||||
SELECT
|
||||
tal.id,
|
||||
tal.task_id,
|
||||
tal.user_id,
|
||||
tal.attribute_type,
|
||||
tal.log_type,
|
||||
tal.old_value,
|
||||
tal.new_value,
|
||||
tal.prev_string,
|
||||
tal.next_string,
|
||||
tal.created_at,
|
||||
t.name AS task_name,
|
||||
t.task_no AS task_no,
|
||||
p.key AS project_key,
|
||||
|
||||
-- Include user details directly
|
||||
u.id AS user_id,
|
||||
u.name AS user_name,
|
||||
u.email AS user_email,
|
||||
u.avatar_url AS user_avatar_url
|
||||
FROM task_activity_logs tal
|
||||
LEFT JOIN tasks t ON tal.task_id = t.id
|
||||
LEFT JOIN projects p ON tal.project_id = p.id
|
||||
LEFT JOIN users u ON tal.user_id = u.id
|
||||
WHERE tal.project_id = $1
|
||||
`;
|
||||
|
||||
const queryParams: any[] = [projectId];
|
||||
if (filterType !== "all") {
|
||||
mainQuery += ` AND tal.attribute_type = $2`;
|
||||
queryParams.push(filterType);
|
||||
}
|
||||
mainQuery += ` ORDER BY tal.created_at DESC`;
|
||||
// placeholders for LIMIT / OFFSET
|
||||
const limitIdx = queryParams.length + 1;
|
||||
const offsetIdx = queryParams.length + 2;
|
||||
mainQuery += ` LIMIT $${limitIdx} OFFSET $${offsetIdx}`;
|
||||
queryParams.push(size, offset);
|
||||
|
||||
// Count query
|
||||
const countQuery = `
|
||||
SELECT COUNT(*) AS total
|
||||
FROM task_activity_logs tal
|
||||
WHERE tal.project_id = $1
|
||||
`;
|
||||
const countParams = filterType !== "all"
|
||||
? [projectId, filterType]
|
||||
: [projectId];
|
||||
|
||||
try {
|
||||
// 3) Execute SQL
|
||||
const [dataResult, countResult] = await Promise.all([
|
||||
db.query(mainQuery, queryParams),
|
||||
db.query(
|
||||
countQuery + (filterType !== "all" ? ` AND tal.attribute_type = $2` : ""),
|
||||
countParams
|
||||
),
|
||||
]);
|
||||
|
||||
const total = parseInt(countResult.rows[0]?.total || "0", 10);
|
||||
|
||||
// 4) Transform rows
|
||||
const rows = dataResult.rows;
|
||||
const logs = await Promise.all(rows.map(async (r: any) => {
|
||||
const log: any = { ...r };
|
||||
// Correctly structure user information
|
||||
log.done_by = {
|
||||
id: r.user_id || "",
|
||||
name: r.user_name || "Unknown User",
|
||||
avatar_url: r.user_avatar_url,
|
||||
email: r.user_email || "",
|
||||
color_code: r.user_name ? getColorFromName(r.user_name) : "#1890ff"
|
||||
};
|
||||
// task key
|
||||
log.task_key = r.project_key && r.task_no ?
|
||||
`${r.project_key}-${r.task_no}` :
|
||||
`TASK-${r.task_id?.substring(0, 8) || "unknown"}`;
|
||||
|
||||
// duration / estimation formatting
|
||||
if (log.attribute_type === "estimation") {
|
||||
const oldMin = parseInt(log.old_value, 10);
|
||||
const newMin = parseInt(log.new_value, 10);
|
||||
log.previous = !isNaN(oldMin)
|
||||
? formatDuration(moment.duration(oldMin, "minutes"))
|
||||
: log.old_value;
|
||||
log.current = !isNaN(newMin)
|
||||
? formatDuration(moment.duration(newMin, "minutes"))
|
||||
: log.new_value;
|
||||
} else {
|
||||
log.previous = log.old_value;
|
||||
log.current = log.new_value;
|
||||
}
|
||||
// human‐friendly action
|
||||
log.log_text = generateLogText(r.attribute_type);
|
||||
|
||||
// Handle status changes
|
||||
if (log.attribute_type === "status" && log.old_value && isValidUuid(log.old_value)) {
|
||||
try {
|
||||
const prevStatus = await db.query(
|
||||
`SELECT name, color_code FROM task_statuses WHERE id = $1`,
|
||||
[log.old_value]
|
||||
);
|
||||
if (prevStatus.rows.length > 0) {
|
||||
log.previous_status = {
|
||||
name: prevStatus.rows[0].name,
|
||||
color_code: prevStatus.rows[0].color_code || "#d9d9d9"
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching previous status:", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (log.attribute_type === "status" && log.new_value && isValidUuid(log.new_value)) {
|
||||
try {
|
||||
const nextStatus = await db.query(
|
||||
`SELECT name, color_code FROM task_statuses WHERE id = $1`,
|
||||
[log.new_value]
|
||||
);
|
||||
if (nextStatus.rows.length > 0) {
|
||||
log.next_status = {
|
||||
name: nextStatus.rows[0].name,
|
||||
color_code: nextStatus.rows[0].color_code || "#1890ff"
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching next status:", err);
|
||||
}
|
||||
}
|
||||
// Handle priority changes
|
||||
if (log.attribute_type === "priority" && log.old_value && isValidUuid(log.old_value)) {
|
||||
try {
|
||||
const prevPriority = await db.query(
|
||||
`SELECT name, color_code FROM task_priorities WHERE id = $1`,
|
||||
[log.old_value]
|
||||
);
|
||||
if (prevPriority.rows.length > 0) {
|
||||
log.previous_priority = {
|
||||
name: prevPriority.rows[0].name,
|
||||
color_code: prevPriority.rows[0].color_code || "#d9d9d9"
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching previous priority:", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (log.attribute_type === "priority" && log.new_value && isValidUuid(log.new_value)) {
|
||||
try {
|
||||
const nextPriority = await db.query(
|
||||
`SELECT name, color_code FROM task_priorities WHERE id = $1`,
|
||||
[log.new_value]
|
||||
);
|
||||
if (nextPriority.rows.length > 0) {
|
||||
log.next_priority = {
|
||||
name: nextPriority.rows[0].name,
|
||||
color_code: nextPriority.rows[0].color_code || "#ff4d4f"
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching next priority:", err);
|
||||
}
|
||||
}
|
||||
// Handle assignee changes
|
||||
if (log.attribute_type === "assignee" && log.new_value && isValidUuid(log.new_value)) {
|
||||
try {
|
||||
const assignedUser = await db.query(
|
||||
`SELECT id, name, avatar_url, email FROM users WHERE id = $1`,
|
||||
[log.new_value]
|
||||
);
|
||||
if (assignedUser.rows.length > 0) {
|
||||
log.assigned_user = {
|
||||
...assignedUser.rows[0],
|
||||
color_code: getColorFromName(assignedUser.rows[0].name)
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching assigned user:", err);
|
||||
}
|
||||
}
|
||||
|
||||
return log;
|
||||
}));
|
||||
|
||||
// 5) Send back a clean response
|
||||
const response = {
|
||||
logs,
|
||||
pagination: {
|
||||
current: page,
|
||||
pageSize: size,
|
||||
total,
|
||||
totalPages: Math.ceil(total / size),
|
||||
}
|
||||
};
|
||||
|
||||
return res.status(200).send(new ServerResponse(true, response));
|
||||
} catch (err) {
|
||||
console.error("🔥 getByProjectId error:", err);
|
||||
return res.status(500).send(
|
||||
new ServerResponse(false, null, "Internal server error fetching logs.")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,7 @@ import safeControllerFunction from "../../shared/safe-controller-function";
|
||||
const activityLogsApiRouter = express.Router();
|
||||
|
||||
activityLogsApiRouter.get("/:id", idParamValidator, safeControllerFunction(ActivitylogsController.get));
|
||||
activityLogsApiRouter.get("/project/:id", idParamValidator, safeControllerFunction(ActivitylogsController.getByProjectId));
|
||||
activityLogsApiRouter.get("/project/:id/export", safeControllerFunction(ActivitylogsController.exportProjectActivityLogs));
|
||||
|
||||
export default activityLogsApiRouter;
|
||||
|
||||
@@ -1,120 +1,126 @@
|
||||
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 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 projectActivityLogsApiRouter from "./project-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);
|
||||
api.use("/activity-logs", activityLogsApiRouter);
|
||||
|
||||
|
||||
api.use("/project-activity-logs", projectActivityLogsApiRouter);
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import express from "express";
|
||||
import idParamValidator from "../../middlewares/validators/id-param-validator";
|
||||
import safeControllerFunction from "../../shared/safe-controller-function";
|
||||
import ProjectActivityLogsController from "../../controllers/project-activity-logs-controller";
|
||||
|
||||
const projectActivityLogsApiRouter = express.Router();
|
||||
|
||||
// Match the client URL (/project/:projectId) and ensure param name lines up
|
||||
projectActivityLogsApiRouter.get(
|
||||
"/project/:id",
|
||||
// Validate that id is a proper UUID
|
||||
idParamValidator,
|
||||
// Wrap the controller to catch and forward errors
|
||||
safeControllerFunction(ProjectActivityLogsController.getByProjectId)
|
||||
);
|
||||
|
||||
// Optional health‐check endpoint
|
||||
projectActivityLogsApiRouter.get("/test", (req, res) => {
|
||||
res.json({ message: "Project activity logs router is working!" });
|
||||
});
|
||||
|
||||
export default projectActivityLogsApiRouter;
|
||||
Reference in New Issue
Block a user