Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/src/assistant-chat/assistant-chat.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ Important:
}

@Put('history')
@SkipAuditLog()
@ApiOperation({
summary: 'Save assistant chat history',
description:
Expand All @@ -214,6 +215,7 @@ Important:
}

@Delete('history')
@SkipAuditLog()
@ApiOperation({
summary: 'Clear assistant chat history',
description: 'Deletes the current user-scoped assistant chat history.',
Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/people/people.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/tasks/tasks.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
15 changes: 15 additions & 0 deletions apps/api/src/tasks/tasks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated task template query logic across services

Low Severity

TasksService.getTaskTemplates() duplicates the query logic from TaskTemplateService.findAll() — same table, same orderBy, same where clause structure filtering by frameworkId. The only difference is findAll additionally includes controlTemplates. Rather than duplicating, the controller could reuse TaskTemplateService (or extract a shared query method) to avoid the two implementations drifting apart over time.

Fix in Cursor Fix in Web


/**
* Get a single task by ID
*/
Expand Down
63 changes: 19 additions & 44 deletions apps/app/src/actions/organization/remove-employee.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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',
},
})
Expand All @@ -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,
Expand All @@ -67,56 +53,45 @@ 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,
error: 'Target member does not have the employee or contractor role.',
};
}

// 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');

Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/hooks/use-task-template-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export interface TaskTemplate {
}

export function useTaskTemplates(options: UseApiSWROptions<TaskTemplate[]> = {}) {
return useApiSWR<TaskTemplate[]>('/v1/framework-editor/task-template', options);
return useApiSWR<TaskTemplate[]>('/v1/tasks/templates', options);
}
Loading