Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
84510e0
fix(app): update departments select in policy details page
chasprowebdev Jun 2, 2026
cb456c6
fix(db): change departments type of policy model
chasprowebdev Jun 2, 2026
2d9f2ea
fix(api): update policy endpoints to support custom departments
chasprowebdev Jun 2, 2026
1409aae
fix(db): change departments field type to string for member, risk and…
chasprowebdev Jun 3, 2026
cc9611a
fix(api): update member, risk and task endpoints to support custom de…
chasprowebdev Jun 3, 2026
c879d4e
fix(app): update UI to support cusotm departments on People, Task, an…
chasprowebdev Jun 3, 2026
d45d488
Merge branch 'main' into chas/add-custom-departments
chasprowebdev Jun 3, 2026
f5340f5
Merge branch 'main' into chas/add-custom-departments
chasprowebdev Jun 3, 2026
289ffee
fix(api): reject whitespace-only department filter in GetRisksQueryDto
chasprowebdev Jun 3, 2026
88a8420
fix(app): prevent custom department sentinel from colliding with real…
chasprowebdev Jun 3, 2026
37f6806
Merge branch 'main' of https://github.com/trycompai/comp into chas/ad…
chasprowebdev Jun 3, 2026
8a6685d
Merge branch 'chas/add-custom-departments' of https://github.com/tryc…
chasprowebdev Jun 3, 2026
3e7e46f
Merge branch 'main' of https://github.com/trycompai/comp into chas/ad…
chasprowebdev Jun 3, 2026
b1954fe
Merge branch 'main' into chas/add-custom-departments
chasprowebdev Jun 3, 2026
25bc02a
fix(api): update validation of department in CreateTaskDto
chasprowebdev Jun 3, 2026
97f4efd
Merge branch 'chas/add-custom-departments' of https://github.com/tryc…
chasprowebdev Jun 3, 2026
689dfa0
Merge branch 'main' of https://github.com/trycompai/comp into chas/ad…
chasprowebdev Jun 3, 2026
75856a2
fix(app): enforce 64-char limit on custom department input
chasprowebdev Jun 3, 2026
5986be2
Merge branch 'main' into chas/add-custom-departments
chasprowebdev Jun 3, 2026
78f6dae
Merge branch 'main' into chas/add-custom-departments
chasprowebdev Jun 3, 2026
dd01c64
fix(app): allow custom department strings in risk/policy forms and em…
chasprowebdev Jun 3, 2026
801fbcf
Merge branch 'main' of https://github.com/trycompai/comp into chas/ad…
chasprowebdev Jun 3, 2026
5be7e52
fix(app): update departments validation for updatePolicyFormSchema
chasprowebdev Jun 3, 2026
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
17 changes: 11 additions & 6 deletions apps/api/src/admin-organizations/admin-policies.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { db } from '@db';
import {
PolicyStatus,
Frequency,
Departments,
DEPARTMENT_MAX_LENGTH,
} from '../policies/dto/create-policy.dto';
import { auth as triggerAuth, tasks } from '@trigger.dev/sdk';
import type { updatePolicy } from '../trigger/policies/update-policy';
Expand Down Expand Up @@ -89,14 +89,19 @@ export class AdminPoliciesController {
}

if (body.department !== undefined) {
if (
!Object.values(Departments).includes(body.department as Departments)
) {
if (typeof body.department !== 'string') {
throw new BadRequestException('department must be a string');
}
const trimmed = body.department.trim();
if (trimmed.length === 0) {
throw new BadRequestException('department must not be empty');
}
if (trimmed.length > DEPARTMENT_MAX_LENGTH) {
throw new BadRequestException(
`Invalid department. Must be one of: ${Object.values(Departments).join(', ')}`,
`department must be at most ${DEPARTMENT_MAX_LENGTH} characters`,
);
}
updateData.department = body.department as Departments;
updateData.department = trimmed;
}

if (body.frequency !== undefined) {
Expand Down
23 changes: 18 additions & 5 deletions apps/api/src/admin-organizations/dto/create-admin-policy.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsOptional, IsEnum } from 'class-validator';
import { Transform } from 'class-transformer';
import {
IsString,
IsNotEmpty,
IsOptional,
IsEnum,
MaxLength,
} from 'class-validator';
import {
PolicyStatus,
Frequency,
Departments,
DEPARTMENT_MAX_LENGTH,
} from '../../policies/dto/create-policy.dto';

export class CreateAdminPolicyDto {
Expand Down Expand Up @@ -45,12 +53,17 @@ export class CreateAdminPolicyDto {
frequency?: Frequency;

@ApiProperty({
description: 'Department this policy applies to',
enum: Departments,
description:
'Department this policy applies to. Built-in values: none, admin, gov, hr, it, itsm, qms. Custom department names are also accepted.',
example: Departments.IT,
required: false,
type: 'string',
maxLength: DEPARTMENT_MAX_LENGTH,
})
@IsOptional()
@IsEnum(Departments)
department?: Departments;
@IsString()
@IsNotEmpty()
@MaxLength(DEPARTMENT_MAX_LENGTH)
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
department?: string;
}
6 changes: 2 additions & 4 deletions apps/api/src/auth/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// Types for API authentication - supports API keys and session-based auth

import { Departments } from '@db';

export interface AuthenticatedRequest extends Request {
organizationId: string;
authType: 'api-key' | 'session' | 'service';
Expand All @@ -13,7 +11,7 @@ export interface AuthenticatedRequest extends Request {
userEmail?: string;
userRoles: string[] | null;
memberId?: string; // Member ID for assignment filtering (only available for session auth)
memberDepartment?: Departments; // Member department for visibility filtering (only available for session auth)
memberDepartment?: string; // Member department for visibility filtering (only available for session auth)
apiKeyScopes?: string[]; // Scopes for API key auth (empty = legacy full access)
apiKeyId?: string; // ApiKey row id — only set for API key auth. Used by ActingUserResolver / audit log attribution.
apiKeyName?: string; // Human-readable API key name (e.g. "CI Pipeline") — only set for API key auth.
Expand All @@ -34,7 +32,7 @@ export interface AuthContext {
userEmail?: string; // Only available for session auth
userRoles: string[] | null;
memberId?: string; // Member ID for assignment filtering (only available for session auth)
memberDepartment?: Departments; // Member department for visibility filtering (only available for session auth)
memberDepartment?: string; // Member department for visibility filtering (only available for session auth)
apiKeyScopes?: string[]; // Scopes for API key auth (empty = legacy full access)
impersonatedBy?: string; // User ID of the admin who initiated impersonation (only set during impersonation sessions)
}
17 changes: 12 additions & 5 deletions apps/api/src/people/dto/create-people.dto.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
IsString,
IsNotEmpty,
IsOptional,
IsEnum,
IsBoolean,
IsNumber,
MaxLength,
} from 'class-validator';
import { Departments } from '@db';
import { DEPARTMENT_MAX_LENGTH } from '../../policies/dto/create-policy.dto';

export class CreatePeopleDto {
@ApiProperty({
Expand All @@ -27,14 +29,19 @@ export class CreatePeopleDto {
role: string;

@ApiProperty({
description: 'Member department',
enum: Departments,
description:
'Member department. Built-in values: none, admin, gov, hr, it, itsm, qms. Custom department names are also accepted.',
example: Departments.it,
required: false,
type: 'string',
maxLength: DEPARTMENT_MAX_LENGTH,
})
@IsOptional()
@IsEnum(Departments)
department?: Departments;
@IsString()
@IsNotEmpty()
@MaxLength(DEPARTMENT_MAX_LENGTH)
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
department?: string;

@ApiProperty({
description: 'Whether member is active',
Expand Down
7 changes: 4 additions & 3 deletions apps/api/src/people/dto/people-responses.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ export class PeopleResponseDto {
createdAt: Date;

@ApiProperty({
description: 'Member department',
enum: Departments,
description:
'Member department. May be one of the built-in values (none, admin, gov, hr, it, itsm, qms) or a custom department name.',
example: Departments.it,
type: 'string',
})
department: Departments;
department: string;

@ApiProperty({
description: 'Job title for the member',
Expand Down
21 changes: 17 additions & 4 deletions apps/api/src/policies/dto/create-policy.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
IsArray,
IsDateString,
IsObject,
IsNotEmpty,
MaxLength,
} from 'class-validator';

export enum PolicyStatus {
Expand All @@ -22,6 +24,10 @@ export enum Frequency {
YEARLY = 'yearly',
}

/**
* Built-in department values. Organizations may also use custom department
* names — the `department` field accepts any non-empty string.
*/
export enum Departments {
NONE = 'none',
ADMIN = 'admin',
Expand All @@ -32,6 +38,8 @@ export enum Departments {
QMS = 'qms',
}

export const DEPARTMENT_MAX_LENGTH = 64;

export class CreatePolicyDto {
@ApiProperty({
description: 'Name of the policy',
Expand Down Expand Up @@ -96,14 +104,19 @@ export class CreatePolicyDto {
frequency?: Frequency;

@ApiProperty({
description: 'Department this policy applies to',
enum: Departments,
description:
'Department this policy applies to. Built-in values: none, admin, gov, hr, it, itsm, qms. Custom department names are also accepted.',
example: Departments.IT,
required: false,
type: 'string',
maxLength: DEPARTMENT_MAX_LENGTH,
})
@IsOptional()
@IsEnum(Departments)
department?: Departments;
@IsString()
@IsNotEmpty()
@MaxLength(DEPARTMENT_MAX_LENGTH)
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
department?: string;

@ApiProperty({
description: 'Whether this policy requires a signature',
Expand Down
11 changes: 7 additions & 4 deletions apps/api/src/policies/dto/policy-responses.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { PolicyStatus, Frequency, Departments } from './create-policy.dto';

const DEPARTMENT_EXAMPLE = Departments.IT;

export class PolicyResponseDto {
@ApiProperty({
description: 'The policy ID',
Expand Down Expand Up @@ -61,12 +63,13 @@ export class PolicyResponseDto {
frequency?: Frequency;

@ApiProperty({
description: 'Department this policy applies to',
enum: Departments,
example: Departments.IT,
description:
'Department this policy applies to. May be one of the built-in values (none, admin, gov, hr, it, itsm, qms) or a custom department name.',
example: DEPARTMENT_EXAMPLE,
nullable: true,
type: 'string',
})
department?: Departments;
department?: string;

@ApiProperty({
description: 'Whether this policy requires a signature',
Expand Down
23 changes: 18 additions & 5 deletions apps/api/src/risks/dto/create-risk.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsOptional, IsEnum } from 'class-validator';
import { Transform } from 'class-transformer';
import {
IsString,
IsNotEmpty,
IsOptional,
IsEnum,
MaxLength,
} from 'class-validator';
import {
RiskCategory,
Departments,
Expand All @@ -8,6 +15,7 @@ import {
Impact,
RiskTreatmentType,
} from '@db';
import { DEPARTMENT_MAX_LENGTH } from '../../policies/dto/create-policy.dto';

export class CreateRiskDto {
@ApiProperty({
Expand Down Expand Up @@ -36,14 +44,19 @@ export class CreateRiskDto {
category: RiskCategory;

@ApiProperty({
description: 'Department responsible for the risk',
enum: Departments,
description:
'Department responsible for the risk. Built-in values: none, admin, gov, hr, it, itsm, qms. Custom department names are also accepted.',
required: false,
example: Departments.it,
type: 'string',
maxLength: DEPARTMENT_MAX_LENGTH,
})
@IsOptional()
@IsEnum(Departments)
department?: Departments;
@IsString()
@IsNotEmpty()
@MaxLength(DEPARTMENT_MAX_LENGTH)
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
department?: string;

@ApiProperty({
description: 'Current status of the risk',
Expand Down
28 changes: 22 additions & 6 deletions apps/api/src/risks/dto/get-risks-query.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
import { Type } from 'class-transformer';
import {
IsEnum,
IsInt,
IsNotEmpty,
IsOptional,
IsString,
Max,
MaxLength,
Min,
} from 'class-validator';
import { Transform, Type } from 'class-transformer';
import { RiskCategory, Departments, RiskStatus } from '@db';
import { DEPARTMENT_MAX_LENGTH } from '../../policies/dto/create-policy.dto';

export enum RiskSortBy {
CREATED_AT = 'createdAt',
Expand Down Expand Up @@ -85,12 +95,18 @@ export class GetRisksQueryDto {
category?: RiskCategory;

@ApiPropertyOptional({
description: 'Filter by department',
enum: Departments,
description:
'Filter by department. Built-in values: none, admin, gov, hr, it, itsm, qms. Custom department names are also accepted.',
type: 'string',
example: Departments.it,
maxLength: DEPARTMENT_MAX_LENGTH,
})
@IsOptional()
@IsEnum(Departments)
department?: Departments;
@IsString()
Comment thread
chasprowebdev marked this conversation as resolved.
@IsNotEmpty()
@MaxLength(DEPARTMENT_MAX_LENGTH)
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
department?: string;

@ApiPropertyOptional({
description: 'Filter by assignee member ID',
Expand Down
7 changes: 4 additions & 3 deletions apps/api/src/risks/dto/risk-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ export class RiskResponseDto {
category: RiskCategory;

@ApiProperty({
description: 'Department responsible for the risk',
enum: Departments,
description:
'Department responsible for the risk. May be one of the built-in values (none, admin, gov, hr, it, itsm, qms) or a custom department name.',
nullable: true,
example: Departments.it,
type: 'string',
})
department: Departments | null;
department: string | null;

@ApiProperty({
description: 'Current status of the risk',
Expand Down
24 changes: 16 additions & 8 deletions apps/api/src/tasks/dto/swagger.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@ export class CreateTaskDto {
integrationScheduleFrequency?: string;

@ApiProperty({
description: 'Department assignment',
enum: ['none', 'admin', 'gov', 'hr', 'it', 'itsm', 'qms'],
description:
'Department assignment. Built-in values: none, admin, gov, hr, it, itsm, qms. Custom department names are also accepted.',
type: 'string',
Comment thread
chasprowebdev marked this conversation as resolved.
nullable: true,
default: 'none',
required: false,
maxLength: 64,
})
department?: string;

Expand Down Expand Up @@ -97,9 +100,11 @@ export class UpdateTaskDto {
integrationScheduleFrequency?: string;

@ApiProperty({
description: 'Department assignment',
enum: ['none', 'admin', 'gov', 'hr', 'it', 'itsm', 'qms'],
description:
'Department assignment. Built-in values: none, admin, gov, hr, it, itsm, qms. Custom department names are also accepted.',
type: 'string',
required: false,
maxLength: 64,
})
department?: string;

Expand Down Expand Up @@ -143,9 +148,11 @@ export class TaskQueryDto {
frequency?: string;

@ApiProperty({
description: 'Filter by department',
enum: ['none', 'admin', 'gov', 'hr', 'it', 'itsm', 'qms'],
description:
'Filter by department. Built-in values: none, admin, gov, hr, it, itsm, qms. Custom department names are also accepted.',
type: 'string',
required: false,
maxLength: 64,
})
department?: string;

Expand Down Expand Up @@ -201,8 +208,9 @@ export class TaskResponseDto {
integrationLastRunAt: Date | null;

@ApiProperty({
description: 'Department assignment',
enum: ['none', 'admin', 'gov', 'hr', 'it', 'itsm', 'qms'],
description:
'Department assignment. May be one of the built-in values (none, admin, gov, hr, it, itsm, qms) or a custom department name.',
type: 'string',
nullable: true,
})
department: string | null;
Expand Down
Loading
Loading