taskflow / backend /src /controllers /adminController.js
Sughan-0077's picture
🚀 Complete Taskflow Suite: Initial Clean Release
f6c14df
Raw
History Blame Contribute Delete
7.82 kB
const { query } = require('../utils/db');
const { auditLog } = require('../utils/auditLogger');
const { v4: uuidv4 } = require('uuid');
const crypto = require('crypto');
/**
* GET /api/admin/users
* List all users in the admin's organization
*/
const listUsers = async (req, res) => {
try {
const { organization_id: orgId } = req.user;
const { rows } = await query(
`SELECT id, name, email, role, is_active, created_at, updated_at
FROM users
WHERE organization_id = $1
ORDER BY created_at ASC`,
[orgId]
);
return res.json(rows);
} catch (err) {
console.error('List users error:', err);
return res.status(500).json({ error: 'Failed to retrieve users.' });
}
};
/**
* PATCH /api/admin/users/:id/role
* Change a user's role
*/
const updateUserRole = async (req, res) => {
try {
const { organization_id: orgId, id: actorId, name: actorName, email: actorEmail } = req.user;
const { id } = req.params;
const { role } = req.body;
if (!['admin', 'member'].includes(role)) {
return res.status(400).json({ error: 'Role must be admin or member.' });
}
// Cannot change own role
if (id === actorId) {
return res.status(400).json({ error: 'You cannot change your own role.' });
}
const { rows } = await query(
`UPDATE users SET role = $1, updated_at = NOW()
WHERE id = $2 AND organization_id = $3
RETURNING id, name, email, role, is_active`,
[role, id, orgId]
);
if (!rows.length) {
return res.status(404).json({ error: 'User not found in your organization.' });
}
await auditLog({
organizationId: orgId,
actorId,
actorName,
actorEmail,
action: 'USER_ROLE_CHANGED',
entityType: 'user',
newValues: { userId: id, newRole: role },
});
return res.json(rows[0]);
} catch (err) {
console.error('Update role error:', err);
return res.status(500).json({ error: 'Failed to update role.' });
}
};
/**
* PATCH /api/admin/users/:id/deactivate
*/
const deactivateUser = async (req, res) => {
try {
const { organization_id: orgId, id: actorId, name: actorName, email: actorEmail } = req.user;
const { id } = req.params;
if (id === actorId) {
return res.status(400).json({ error: 'You cannot deactivate your own account.' });
}
const { rows } = await query(
`UPDATE users SET is_active = false, updated_at = NOW()
WHERE id = $1 AND organization_id = $2
RETURNING id, name, email, role, is_active`,
[id, orgId]
);
if (!rows.length) {
return res.status(404).json({ error: 'User not found in your organization.' });
}
await auditLog({
organizationId: orgId,
actorId,
actorName,
actorEmail,
action: 'USER_DEACTIVATED',
entityType: 'user',
newValues: { userId: id },
});
return res.json(rows[0]);
} catch (err) {
console.error('Deactivate user error:', err);
return res.status(500).json({ error: 'Failed to deactivate user.' });
}
};
/**
* POST /api/admin/invites
* Send an invite to join the organization
*/
const createInvite = async (req, res) => {
try {
const { organization_id: orgId, id: actorId, name: actorName, email: actorEmail } = req.user;
const { email, role = 'member' } = req.body;
if (!['admin', 'member'].includes(role)) {
return res.status(400).json({ error: 'Role must be admin or member.' });
}
// Check if email already a member
const existing = await query(`SELECT id FROM users WHERE email = $1`, [email.toLowerCase()]);
if (existing.rows.length) {
return res.status(409).json({ error: 'A user with this email already exists.' });
}
const token = crypto.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
const { rows } = await query(
`INSERT INTO invites (id, organization_id, email, role, token, invited_by, expires_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, email, role, token, expires_at`,
[uuidv4(), orgId, email.toLowerCase(), role, token, actorId, expiresAt]
);
await auditLog({
organizationId: orgId,
actorId,
actorName,
actorEmail,
action: 'USER_INVITED',
entityType: 'invite',
newValues: { invitedEmail: email, role },
});
return res.status(201).json({
...rows[0],
inviteUrl: `${process.env.FRONTEND_URL}/accept-invite?token=${token}`,
});
} catch (err) {
console.error('Create invite error:', err);
return res.status(500).json({ error: 'Failed to create invite.' });
}
};
/**
* GET /api/admin/invites
*/
const listInvites = async (req, res) => {
try {
const { organization_id: orgId } = req.user;
const { rows } = await query(
`SELECT i.id, i.email, i.role, i.expires_at, i.used_at, i.created_at,
u.name AS invited_by_name
FROM invites i
LEFT JOIN users u ON u.id = i.invited_by
WHERE i.organization_id = $1
ORDER BY i.created_at DESC`,
[orgId]
);
return res.json(rows);
} catch (err) {
console.error('List invites error:', err);
return res.status(500).json({ error: 'Failed to retrieve invites.' });
}
};
/**
* GET /api/admin/audit-logs
*/
const getAuditLogs = async (req, res) => {
try {
const { organization_id: orgId } = req.user;
const { page = 1, limit = 50, action, task_id } = req.query;
const pageNum = Math.max(1, parseInt(page));
const limitNum = Math.min(200, Math.max(1, parseInt(limit)));
const offset = (pageNum - 1) * limitNum;
const conditions = [`organization_id = $1`];
const params = [orgId];
let idx = 2;
if (action) {
conditions.push(`action = $${idx++}`);
params.push(action);
}
if (task_id) {
conditions.push(`task_id = $${idx++}`);
params.push(task_id);
}
const where = `WHERE ${conditions.join(' AND ')}`;
const countResult = await query(`SELECT COUNT(*) FROM audit_logs ${where}`, params);
const total = parseInt(countResult.rows[0].count);
const { rows } = await query(
`SELECT id, task_id, actor_id, actor_name, actor_email, action, entity_type,
old_values, new_values, metadata, created_at
FROM audit_logs
${where}
ORDER BY created_at DESC
LIMIT $${idx} OFFSET $${idx + 1}`,
[...params, limitNum, offset]
);
return res.json({
logs: rows,
pagination: { page: pageNum, limit: limitNum, total, totalPages: Math.ceil(total / limitNum) },
});
} catch (err) {
console.error('Audit logs error:', err);
return res.status(500).json({ error: 'Failed to retrieve audit logs.' });
}
};
/**
* GET /api/admin/org
* Get organization info
*/
const getOrg = async (req, res) => {
try {
const { organization_id: orgId } = req.user;
const { rows } = await query(`SELECT id, name, slug, created_at FROM organizations WHERE id = $1`, [orgId]);
return res.json(rows[0]);
} catch (err) {
return res.status(500).json({ error: 'Failed to retrieve organization.' });
}
};
/**
* GET /api/users - org-scoped user list for dropdowns (any authenticated user)
*/
const listOrgUsers = async (req, res) => {
try {
const { organization_id: orgId } = req.user;
const { rows } = await query(
`SELECT id, name, email, role FROM users WHERE organization_id = $1 AND is_active = true ORDER BY name`,
[orgId]
);
return res.json(rows);
} catch (err) {
return res.status(500).json({ error: 'Failed to retrieve users.' });
}
};
module.exports = { listUsers, updateUserRole, deactivateUser, createInvite, listInvites, getAuditLogs, getOrg, listOrgUsers };