diff --git a/apps/api/src/assistant-chat/assistant-chat.controller.ts b/apps/api/src/assistant-chat/assistant-chat.controller.ts index 746d33e40..78e1da826 100644 --- a/apps/api/src/assistant-chat/assistant-chat.controller.ts +++ b/apps/api/src/assistant-chat/assistant-chat.controller.ts @@ -194,6 +194,7 @@ Important: } @Put('history') + @SkipAuditLog() @ApiOperation({ summary: 'Save assistant chat history', description: @@ -214,6 +215,7 @@ Important: } @Delete('history') + @SkipAuditLog() @ApiOperation({ summary: 'Clear assistant chat history', description: 'Deletes the current user-scoped assistant chat history.', diff --git a/apps/api/src/people/people.service.ts b/apps/api/src/people/people.service.ts index 3c86e5f7b..cbaf0a5da 100644 --- a/apps/api/src/people/people.service.ts +++ b/apps/api/src/people/people.service.ts @@ -350,6 +350,9 @@ export class PeopleService { data: { deactivated: true, isActive: false }, }); + // Direct DB session deletion is correct here — the API server IS the auth server, + // and better-auth's own revokeUserSessions internally calls the same deleteSessions operation. + // The admin endpoint wrapper requires an authenticated admin session context we don't have. await db.session.deleteMany({ where: { userId: member.userId } }); if (member.fleetDmLabelId) { diff --git a/apps/api/src/tasks/tasks.controller.ts b/apps/api/src/tasks/tasks.controller.ts index 0fc3fc268..1f33cb65c 100644 --- a/apps/api/src/tasks/tasks.controller.ts +++ b/apps/api/src/tasks/tasks.controller.ts @@ -129,6 +129,20 @@ export class TasksController { }); } + @Get('templates') + @UseGuards(PermissionGuard) + @RequirePermission('task', 'read') + @ApiOperation({ + summary: 'Get task templates', + description: 'Retrieve all available task templates, optionally filtered by framework.', + }) + @ApiQuery({ name: 'frameworkId', required: false, description: 'Filter templates by framework ID' }) + async getTaskTemplates( + @Query('frameworkId') frameworkId?: string, + ) { + return await this.tasksService.getTaskTemplates(frameworkId); + } + @Post() @UseGuards(PermissionGuard) @RequirePermission('task', 'create') diff --git a/apps/api/src/tasks/tasks.service.ts b/apps/api/src/tasks/tasks.service.ts index 3667aaeac..9c3abc942 100644 --- a/apps/api/src/tasks/tasks.service.ts +++ b/apps/api/src/tasks/tasks.service.ts @@ -129,6 +129,21 @@ export class TasksService { } } + async getTaskTemplates(frameworkId?: string) { + const templates = await db.frameworkEditorTaskTemplate.findMany({ + orderBy: { name: 'asc' }, + where: frameworkId + ? { + controlTemplates: { + some: { requirements: { some: { frameworkId } } }, + }, + } + : undefined, + }); + + return templates; + } + /** * Get a single task by ID */ diff --git a/apps/app/src/actions/organization/remove-employee.ts b/apps/app/src/actions/organization/remove-employee.ts index f27b1e539..1e267426d 100644 --- a/apps/app/src/actions/organization/remove-employee.ts +++ b/apps/app/src/actions/organization/remove-employee.ts @@ -1,5 +1,6 @@ 'use server'; +import { serverApi } from '@/lib/api-server'; import { db } from '@db'; import { revalidatePath, revalidateTag } from 'next/cache'; import { z } from 'zod'; @@ -14,7 +15,7 @@ export const removeEmployeeRoleOrMember = authActionClient .metadata({ name: 'remove-employee-role-or-member', track: { - event: 'remove_employee', // Changed event name + event: 'remove_employee', channel: 'organization', }, }) @@ -37,22 +38,7 @@ export const removeEmployeeRoleOrMember = authActionClient const { memberId } = parsedInput; try { - // 1. Permission Check: Ensure current user is admin or owner - const currentUserMember = await db.member.findFirst({ - where: { - organizationId: organizationId, - userId: currentUserId, - }, - }); - - if (!currentUserMember || !['admin', 'owner'].includes(currentUserMember.role)) { - return { - success: false, - error: 'Permission denied: Only admins or owners can remove employees.', - }; - } - - // 2. Fetch Target Member + // Fetch target member to determine action const targetMember = await db.member.findFirst({ where: { id: memberId, @@ -67,8 +53,7 @@ export const removeEmployeeRoleOrMember = authActionClient }; } - // 3. Check if target has 'employee' or 'contractor' role - const roles = targetMember.role.split(',').filter(Boolean); // Handle empty strings/commas + const roles = targetMember.role.split(',').filter(Boolean); if (!roles.includes('employee') && !roles.includes('contractor')) { return { success: false, @@ -76,47 +61,37 @@ export const removeEmployeeRoleOrMember = authActionClient }; } - // 4. Logic: Remove role or delete member if (roles.length === 1 && (roles[0] === 'employee' || roles[0] === 'contractor')) { - // Only has employee or contractor role - delete member fully + // Only has employee/contractor role — deactivate via API + // The API handles session cleanup, assignment clearing, and notifications + const result = await serverApi.delete(`/v1/people/${memberId}`); - // Cannot remove owner (shouldn't happen if only role is employee, but safety check) - if (targetMember.role === 'owner') { + if (result.error) { return { success: false, - error: 'Cannot remove the organization owner.', + error: result.error, }; } - // Cannot remove self - if (targetMember.userId === currentUserId) { - return { - success: false, - error: 'You cannot remove yourself.', - }; - } - - await db.$transaction([ - db.member.delete({ where: { id: memberId } }), - db.session.deleteMany({ - where: { userId: targetMember.userId }, - }), - ]); - // Revalidate revalidatePath(`/${organizationId}/people/all`); revalidateTag(`user_${currentUserId}`, 'max'); return { success: true, data: { removed: true } }; } else { - // Has other roles - just remove 'employee' role + // Has other roles — just remove the employee role via API const updatedRoles = roles.filter((role) => role !== 'employee').join(','); - await db.member.update({ - where: { id: memberId }, - data: { role: updatedRoles }, + const result = await serverApi.patch(`/v1/people/${memberId}`, { + role: updatedRoles, }); - // Revalidate + if (result.error) { + return { + success: false, + error: result.error, + }; + } + revalidatePath(`/${organizationId}/people/all`); revalidateTag(`user_${currentUserId}`, 'max'); diff --git a/apps/app/src/hooks/use-task-template-api.ts b/apps/app/src/hooks/use-task-template-api.ts index 921043a6e..294ebceb0 100644 --- a/apps/app/src/hooks/use-task-template-api.ts +++ b/apps/app/src/hooks/use-task-template-api.ts @@ -13,5 +13,5 @@ export interface TaskTemplate { } export function useTaskTemplates(options: UseApiSWROptions = {}) { - return useApiSWR('/v1/framework-editor/task-template', options); + return useApiSWR('/v1/tasks/templates', options); }