---
title: "Evidence Forms"
description: "The Evidence Forms module provides a robust system for managing and processing various types of evidence submissions within an organization. It defines a set of pre-built forms, handles their submi..."
last_updated: "2026-05-06T07:29:41.620964+00:00"
canonical_url: "https://www.doc0.dev/docs/49c89830-117b-4def-8edc-b5bcc50766e0/technical/section-3/evidence-forms"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [apps/api/src/evidence-forms/evidence-forms.service.ts](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts)
- [apps/api/src/evidence-forms/evidence-forms.definitions.ts](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.definitions.ts)
- [apps/api/src/evidence-forms/evidence-forms.controller.ts](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.controller.ts)
- [apps/api/src/evidence-forms/evidence-forms.module.ts](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.module.ts)
</details>

The Evidence Forms module provides a robust system for managing and processing various types of evidence submissions within an organization. It defines a set of pre-built forms, handles their submission, validation, file uploads, and review processes. This module integrates with authentication and attachment services to ensure secure and efficient evidence management.

At a high level, the system allows users to submit structured data and associated files for different evidence types (e.g., meeting minutes, policy documents). Authorized personnel can then review these submissions, approving or rejecting them with reasons. All interactions are secured through JWT or API key authentication and scoped to specific organizations.

## Architecture Overview

The Evidence Forms module follows a standard NestJS architecture, comprising a Controller, Service, and Module. It leverages shared definitions for form structures and integrates with other core services like `AttachmentsService` for file management and the database (`@trycompai/db`) for persistence.

The `EvidenceFormsController` exposes RESTful API endpoints, handling incoming HTTP requests. These requests are then delegated to the `EvidenceFormsService`, which encapsulates the business logic, data validation, and interactions with the database and other services. The `EvidenceFormsModule` orchestrates these components, declaring dependencies and making the service available for injection.


Sources:
- [apps/api/src/evidence-forms/evidence-forms.module.ts:1-11](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.module.ts#L1-L11)
- [apps/api/src/evidence-forms/evidence-forms.controller.ts:1-50](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.controller.ts#L1-L50)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:1-26](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L1-L26)

## Data Structures and Definitions

The core of the Evidence Forms system relies on well-defined data structures, primarily sourced from a shared `@comp/company` package. These definitions dictate the structure of forms, their fields, and the schema for submissions.

### Form Definitions

Key definitions include:
*   `evidenceFormTypeSchema`: A Zod schema for validating the type of an evidence form (e.g., 'meeting', 'policy').
*   `evidenceFormDefinitions`: An object mapping `EvidenceFormType` to its detailed `EvidenceFormDefinition`.
*   `evidenceFormDefinitionList`: An array of all available `EvidenceFormDefinition` objects.
*   `evidenceFormSubmissionSchemaMap`: A map where each `EvidenceFormType` is associated with its specific Zod schema for validating submission payloads.
*   `EvidenceFormFieldDefinition`: Describes a single field within an evidence form, including its key, type (e.g., 'text', 'file', 'matrix'), and validation rules.
*   `EvidenceFormDefinition`: Defines an entire evidence form, including its `type`, `name`, `description`, `fields`, and `submissionDateMode`.


Sources:
- [apps/api/src/evidence-forms/evidence-forms.definitions.ts:1-10](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.definitions.ts#L1-L10)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:10-16](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L10-L16)

### Validation Schemas

The service uses Zod for robust input validation:

*   `listQuerySchema`: Validates query parameters for listing submissions (search, limit, offset).
*   `uploadSchema`: Validates parameters for file uploads (formType, fileName, fileType, fileData).
*   `reviewSchema`: Validates the payload for reviewing a submission (action: 'approved' | 'rejected', reason).

```typescript
// Example: listQuerySchema
const listQuerySchema = z.object({
  search: z.string().trim().optional(),
  limit: z.coerce.number().int().min(1).max(200).optional().default(50),
  offset: z.coerce.number().int().min(0).optional().default(0),
});
```
Sources:
- [apps/api/src/evidence-forms/evidence-forms.service.ts:18-22](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L18-L22)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:24-28](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L24-L28)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:30-33](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L30-L33)

## Core Service Logic (`EvidenceFormsService`)

The `EvidenceFormsService` handles all business logic related to evidence forms.

### Authentication and Authorization

The service enforces strict access control:
*   `requireJwtUser(authContext: AuthContext)`: Ensures the request is authenticated via JWT and has a `userId`. API key authentication is explicitly denied for operations requiring a user context.
*   `requirePrivilegedEvidenceAccess(authContext: AuthContext)`: Builds upon `requireJwtUser` by checking if the authenticated user possesses one of the `EVIDENCE_FORM_REVIEWER_ROLES` (`owner`, `admin`, `auditor`). This is crucial for operations like reviewing submissions or exporting data.

<Callout variant="info" title="Role-Based Access Control">
The `EVIDENCE_FORM_REVIEWER_ROLES` constant defines which user roles are authorized to perform privileged actions on evidence forms.
</Callout>
Sources:
- [apps/api/src/evidence-forms/evidence-forms.service.ts:100-109](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L100-L109)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:111-124](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L111-L124)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:35-35](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L35-L35)

### File Handling

The service provides functionality for uploading and managing files associated with evidence forms.
*   `decodeBase64File(fileData: string)`: Decodes a base64 encoded file string into a Buffer, performing basic validation on the input format and size.
*   `uploadFile(params: { ... })`: Handles the entire file upload process. It validates the input payload, decodes the base64 file data, checks against size limits (`MAX_UPLOAD_FILE_SIZE_BYTES`, `MAX_UPLOAD_BASE64_LENGTH`), and then delegates to `AttachmentsService` to upload the file to S3 and generate a presigned download URL.

<Callout variant="warning" title="File Size Limits">
Uploaded files are subject to a maximum size limit of 100MB. This is enforced both by checking the base64 string length and the decoded file buffer length.
</Callout>
Sources:
- [apps/api/src/evidence-forms/evidence-forms.service.ts:126-146](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L126-L146)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:37-38](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L37-L38)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:321-356](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L321-L356)

### Data Transformation and Export

Several utility functions facilitate data manipulation, especially for CSV export:
*   `toCsvRow(values: string[])`: Converts an array of strings into a CSV row, handling proper escaping of double quotes.
*   `flattenValue(value: unknown)`: Converts various data types (objects, numbers, booleans, strings) into a string representation suitable for CSV. Special handling is included for file objects to return their download URL.
*   `flattenMatrixRows(value: unknown, field: EvidenceFormFieldDefinition)`: Specifically designed to flatten data from 'matrix' type form fields into a readable string format for CSV.
*   `normalizeSubmissionFormType<T>(submission: T)`: Transforms the internal database `DbEvidenceFormType` to the external `EvidenceFormType` for API responses.

Sources:
- [apps/api/src/evidence-forms/evidence-forms.service.ts:40-42](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L40-L42)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:44-71](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L44-L71)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:73-94](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L73-L94)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:96-98](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L96-L98)

### Submission and Review Workflow

The service manages the lifecycle of evidence form submissions.



**Key methods:**
*   `listForms()`: Returns a list of all available evidence form definitions.
*   `getFormStatuses(organizationId: string)`: Retrieves the latest submission date for each form type within a given organization.
*   `getFormWithSubmissions(params: { ... })`: Fetches a specific form definition along with its submissions for an organization. Requires privileged access and supports search, pagination.
*   `getSubmission(params: { ... })`: Retrieves a single evidence form submission by ID. Requires privileged access.
*   `submitForm(params: { ... })`: Creates a new evidence form submission. It validates the payload against the form's schema, handles automatic submission date population, and stores the data in the database.
*   `reviewSubmission(params: { ... })`: Allows privileged users to approve or reject a pending submission. Requires a reason for rejection.
*   `getMySubmissions(params: { ... })`: Retrieves all submissions made by the currently authenticated user.
*   `getPendingSubmissionCount(params: { ... })`: Returns the count of pending submissions for the authenticated user.
*   `exportCsv(params: { ... })`: Exports all submissions for a specific form type within an organization to a CSV format. This method requires privileged access and uses the data transformation utilities (`flattenValue`, `flattenMatrixRows`, `toCsvRow`) to format the output. It also generates presigned URLs for any attached files.

Sources:
- [apps/api/src/evidence-forms/evidence-forms.service.ts:148-150](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L148-L150)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:152-167](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L152-L167)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:169-216](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L169-L216)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:218-251](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L218-L251)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:253-319](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L253-L319)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:358-444](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L358-L444)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:446-500](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L446-L500)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:502-526](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L502-L526)
- [apps/api/src/evidence-forms/evidence-forms.service.ts:528-543](https://github.com/blade47/comp/blob/main/apps/api/src/evidence-forms/evidence-forms.service.ts#L528-L543)

## API Endpoints (`EvidenceFormsController`)

The `EvidenceFormsController` exposes a set of RESTful API endpoints for interacting with the evidence forms system. All endpoints are protected by `HybridAuthGuard` and require an `X-Organization-Id` header.

| Method | Path                                      | Summary                                                               | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    --- File: apps/api/src/evidence-forms/evidence-forms.service.ts ---
import { AttachmentsService } from '@/attachments/attachments.service';
import type { AuthContext } from '@/auth/types';
import { db, EvidenceFormType as DbEvidenceFormType } from '@trycompai/db';
import {
  toDbEvidenceFormType,
  toExternalEvidenceFormType,
} from '@comp/company';
import {
  BadRequestException,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import { z } from 'zod';
import {
  evidenceFormDefinitionList,
  evidenceFormDefinitions,
  evidenceFormSubmissionSchemaMap,
  evidenceFormTypeSchema,
  type EvidenceFormFieldDefinition,
  type EvidenceFormType,
} from './evidence-forms.definitions';

const listQuerySchema = z.object({
  search: z.string().trim().optional(),
  limit: z.coerce.number().int().min(1).max(200).optional().default(50),
  offset: z.coerce.number().int().min(0).optional().default(0),
});

const uploadSchema = z.object({
  formType: evidenceFormTypeSchema,
  fileName: z.string().min(1),
  fileType: z.string().min(1),
  fileData: z.string().min(1),
});

const reviewSchema = z.object({
  action: z.enum(['approved', 'rejected']),
  reason: z.string().trim().optional(),
});

const EVIDENCE_FORM_REVIEWER_ROLES = ['owner', 'admin', 'auditor'] as const;
const MAX_UPLOAD_FILE_SIZE_BYTES = 100 * 1024 * 1024;
const MAX_UPLOAD_BASE64_LENGTH = Math.ceil(MAX_UPLOAD_FILE_SIZE_BYTES / 3) * 4;

function toCsvRow(values: string[]): string {
  return values.map((value) => `"${value.replace(/"/g, '""')}"`).join(',');
}

function flattenValue(value: unknown): string {
  if (value === null || value === undefined) {
    return '';
  }

  if (typeof value === 'object') {
    if (
      'fileName' in value &&
      typeof value.fileName === 'string' &&
      'downloadUrl' in value &&
      typeof value.downloadUrl === 'string'
    ) {
      return value.downloadUrl;
    }
    return JSON.stringify(value);
  }

  if (typeof value === 'string') {
    return value;
  }
  if (
    typeof value === 'number' ||
    typeof value === 'boolean' ||
    typeof value === 'bigint'
  ) {
    return value.toString();
  }
  if (typeof value === 'symbol') {
    return value.description ?? '';
  }

  return '';
}

function flattenMatrixRows(
  value: unknown,
  field: EvidenceFormFieldDefinition,
): string {
  if (!Array.isArray(value)) {
    return '';
  }

  const columns = Array.isArray(field.columns) ? field.columns : [];
  if (columns.length === 0) {
    return JSON.stringify(value);
  }

  return value
    .filter((row) => row && typeof row === 'object')
    .map((row) => {
      const rowRecord = row as Record<string, unknown>;
      return columns
        .map((column) => {
          const cellValue = rowRecord[column.key];
          const normalizedValue =
            typeof cellValue === 'string' ? cellValue : '';
          return `${column.label}: ${normalizedValue}`;
        })
        .join(' | ');
    })
    .join(' || ');
}

function normalizeSubmissionFormType<
  T extends { formType: DbEvidenceFormType },
>(submission: T): Omit<T, 'formType'> & { formType: EvidenceFormType } {
  return {
    ...submission,
    formType: toExternalEvidenceFormType(submission.formType) ?? 'meeting',
  };
}

@Injectable()
export class EvidenceFormsService {
  constructor(private readonly attachmentsService: AttachmentsService) {}

  private requireJwtUser(authContext: AuthContext): string {
    if (authContext.isApiKey || authContext.authType === 'api-key') {
      throw new UnauthorizedException(
        'This endpoint requires JWT authentication and does not support API key authentication',
      );
    }

    if (!authContext.userId) {
      throw new UnauthorizedException('Authenticated user session is required');
    }

    return authContext.userId;
  }

  private requirePrivilegedEvidenceAccess(authContext: AuthContext): string {
    const userId = this.requireJwtUser(authContext);
    const roles = authContext.userRoles ?? [];
    const hasRequiredRole = EVIDENCE_FORM_REVIEWER_ROLES.some((role) =>
      roles.includes(role),
    );

    if (!hasRequiredRole) {
      throw new UnauthorizedException(
        `Access denied. Required one of roles: ${EVIDENCE_FORM_REVIEWER_ROLES.join(', ')}`,
      );
    }

    return userId;
  }

  private decodeBase64File(fileData: string): Buffer {
    const normalized = fileData.trim();
    if (normalized.length === 0 || normalized.length % 4 !== 0) {
      throw new BadRequestException(
        'Invalid file data. Expected base64 string.',
      );
    }

    const base64Pattern = /^[A-Za-z0-9+/]+={0,2}$/;
    if (!base64Pattern.test(normalized)) {
      throw new BadRequestException(
        'Invalid file data. Expected base64 string.',
      );
    }

    const fileBuffer = Buffer.from(normalized, 'base64');
    if (!fileBuffer.length) {
      throw new BadRequestException('File cannot be empty');
    }

    return fileBuffer;
  }

  listForms() {
    return evidenceFormDefinitionList;
  }

  async getFormStatuses(organizationId: string) {
    const results = await db.evidenceSubmission.groupBy({
      by: ['formType'],
      where: { organizationId },
      _max: { submittedAt: true },
    });

    const statuses: Record<string, { lastSubmittedAt: string | null }> = {};

    for (const form of evidenceFormDefinitionList) {
      const match = results.find(
        (r) => r.formType === toDbEvidenceFormType(form.type),
      );
      statuses[form.type] = {
        lastSubmittedAt: match?._max.submittedAt?.toISOString() ?? null,
      };
    }

    return statuses;
  }

  async getFormWithSubmissions(params: {
    organizationId: string;
    authContext: AuthContext;
    formType: string;
    search?: string;
    limit?: string;
    offset?: string;
  }) {
    const { organizationId, formType } = params;
    this.requirePrivilegedEvidenceAccess(params.authContext);

    const parsedType = evidenceFormTypeSchema.safeParse(formType);
    if (!parsedType.success) {
      throw new BadRequestException('Unsupported form type');
    }

    const parsedQuery = listQuerySchema.safeParse({
      search: params.search,
      limit: params.limit,
      offset: params.offset,
    });
    if (!parsedQuery.success) {
      throw new BadRequestException(parsedQuery.error.flatten());
    }
    const query = parsedQuery.data;

    const submissions = await db.evidenceSubmission.findMany({
      where: {
        organizationId,
        formType: toDbEvidenceFormType(parsedType.data),
      },
      include: {
        submittedBy: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
      orderBy: {
        submittedAt: 'desc',
      },
    });

    const filtered = query.search
      ? submissions.filter((submission) => {
          const searchTarget = JSON.stringify(submission.data).toLowerCase();
          return searchTarget.includes(query.search!.toLowerCase());
        })
      : submissions;

    const paginated = filtered.slice(query.offset, query.offset + query.limit);

    return {
      form: evidenceFormDefinitions[parsedType.data],
      submissions: paginated.map(normalizeSubmissionFormType),
      total: filtered.length,
    };
  }

  async getSubmission(params: {
    organizationId: string;
    authContext: AuthContext;
    formType: string;
    submissionId: string;
  }) {
    this.requirePrivilegedEvidenceAccess(params.authContext);

    const parsedType = evidenceFormTypeSchema.safeParse(params.formType);
    if (!parsedType.success) {
      throw new BadRequestException('Unsupported form type');
    }

    const submission = await db.evidenceSubmission.findFirst({
      where: {
        id: params.submissionId,
        organizationId: params.organizationId,
        formType: toDbEvidenceFormType(parsedType.data),
      },
      include: {
        submittedBy: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
        reviewedBy: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    });

    if (!submission) {
      throw new NotFoundException('Submission not found');
    }

    return {
      form: evidenceFormDefinitions[parsedType.data],
      submission: normalizeSubmissionFormType(submission),
    };
  }

  async submitForm(params: {
    organizationId: string;
    formType: string;
    payload: unknown;
    authContext: AuthContext;
  }) {
    const parsedType = evidenceFormTypeSchema.safeParse(params.formType);
    if (!parsedType.success) {
      throw new BadRequestException('Unsupported form type');
    }

    if (!params.authContext.userId) {
      throw new BadRequestException(
        'Authenticated user session is required to submit evidence forms',
      );
    }

    const formDefinition = evidenceFormDefinitions[parsedType.data];
    const nowIso = new Date().toISOString();

    if (!params.payload || typeof params.payload !== 'object') {
      throw new BadRequestException('Submission payload must be an object');
    }

    const payloadObject: Record<string, unknown> = {
      ...(params.payload as Record<string, unknown>),
    };

    if (formDefinition.submissionDateMode === 'auto') {
      payloadObject.submissionDate = nowIso;
    }

    const schema = evidenceFormSubmissionSchemaMap[parsedType.data];
    const parsedPayload = schema.safeParse(payloadObject);
    if (!parsedPayload.success) {
      const flattened = parsedPayload.error.flatten();
      const fieldErrors = Object.entries(flattened.fieldErrors)
        .map(([field, messages]) => {
          const msg =
            Array.isArray(messages) && messages.length > 0
              ? messages[0]
              : 'is required';
          return `${field}: ${msg}`;
        })
        .slice(0, 5);

      const message =
        fieldErrors.length > 0
          ? `Please fix the following: ${fieldErrors.join('; ')}`
          : 'Please fill in all required fields';

      throw new BadRequestException(message);
    }

    return await db.evidenceSubmission
      .create({
        data: {
          organizationId: params.organizationId,
          formType: toDbEvidenceFormType(parsedType.data),
          submittedById: params.authContext.userId,
          data: parsedPayload.data,
        },
        include: {
          submittedBy: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
      })
      .then(normalizeSubmissionFormType);
  }

  async uploadFile(params: {
    organizationId: string;
    authContext: AuthContext;
    payload: unknown;
  }) {
    if (!params.authContext.userId) {
      throw new BadRequestException(
        'Authenticated user session is required to upload evidence files',
      );
    }

    const parsed = uploadSchema.safeParse(params.payload);
    if (!parsed.success) {
      throw new BadRequestException(parsed.error.flatten());
    }

    if (parsed.data.fileData.length > MAX_UPLOAD_BASE64_LENGTH) {
      throw new BadRequestException(
        `File exceeds the ${MAX_UPLOAD_FILE_SIZE_BYTES / (1024 * 1024)}MB limit`,
      );
    }

    const fileBuffer = this.decodeBase64File(parsed.data.fileData);
    if (fileBuffer.length > MAX_UPLOAD_FILE_SIZE_BYTES) {
      throw new BadRequestException(
        `File exceeds the ${MAX_UPLOAD_FILE_SIZE_BYTES / (1024 * 1024)}MB limit`,
      );
    }

    const fileKey = await this.attachmentsService.uploadToS3(
      fileBuffer,
      parsed.data.fileName,
      parsed.data.fileType,
      params.organizationId,
      'evidence-forms',
      parsed.data.formType,
    );

    const downloadUrl =
      await this.attachmentsService.getPresignedDownloadUrl(fileKey);

    return {
      fileName: parsed.data.fileName,
      fileKey,
      downloadUrl,
    };
  }

  async exportCsv(params: {
    organizationId: string;
    formType: string;
    authContext: AuthContext;
  }) {
    this.requirePrivilegedEvidenceAccess(params.authContext);

    const parsedType = evidenceFormTypeSchema.safeParse(params.formType);
    if (!parsedType.success) {
      throw new BadRequestException('Unsupported form type');
    }

    const formType: EvidenceFormType = parsedType.data;
    const form = evidenceFormDefinitions[formType];

    const submissions = await db.evidenceSubmission.findMany({
      where: {
        organizationId: params.organizationId,
        formType: toDbEvidenceFormType(formType),
      },
      include: {
        submittedBy: {
          select: {
            name: true,
            email: true,
          },
        },
      },
      orderBy: {
        submittedAt: 'desc',
      },
    });

    if (submissions.length === 0) {
      throw new BadRequestException(
        'No submissions available for export for this form',
      );
    }

    const headers = [
      'submissionId',
      'submissionDate',
      'submittedByName',
      'submittedByEmail',
      ...form.fields
        .filter((field) => field.key !== 'submissionDate')
        .map((field) => field.key),
    ];

    const rows = await Promise.all(
      submissions.map(async (submission) => {
        const data = submission.data as Record<string, unknown>;
        const fieldValues = await Promise.all(
          form.fields
            .filter((field) => field.key !== 'submissionDate')
            .map(async (field) => {
              const rawValue = data[field.key];
              if (
                rawValue &&
                typeof rawValue === 'object' &&
                'fileKey' in rawValue &&
                typeof rawValue.fileKey === 'string'
              ) {
                const signedUrl =
                  await this.attachmentsService.getPresignedDownloadUrl(
                    rawValue.fileKey,
                  );
                return signedUrl;
              }
              if (field.type === 'matrix') {
                return flattenMatrixRows(rawValue, field);
              }
              return flattenValue(rawValue);
            }),
        );

        return [
          submission.id,
          typeof data.submissionDate === 'string'
            ? data.submissionDate
            : submission.submittedAt.toISOString(),
          submission.submittedBy?.name ?? '',
          submission.submittedBy?.email ?? '',
          ...fieldValues,
        ];
      }),
    );

    const csvLines = [toCsvRow(headers), ...rows.map((row) => toCsvRow(row))];
    return csvLines.join('\n');
  }

  async reviewSubmission(params: {
    organizationId: string;
    formType: string;
    submissionId: string;
    payload: unknown;
    authContext: AuthContext;
  }) {
    const parsedType = evidenceFormTypeSchema.safeParse(params.formType);
    if (!parsedType.success) {
      throw new BadRequestException('Unsupported form type');
    }

    const reviewerUserId = this.requirePrivilegedEvidenceAccess(
      params.authContext,
    );

    const parsed = reviewSchema.safeParse(params.payload);
    if (!parsed.success) {
      throw new BadRequestException(parsed.error.flatten());
    }

    if (parsed.data.action === 'rejected' && !parsed.data.reason) {
      throw new BadRequestException(
        'A reason is required when rejecting a submission',
      );
    }

    const submission = await db.evidenceSubmission.findFirst({
      where: {
        id: params.submissionId,
        organizationId: params.organizationId,
        formType: toDbEvidenceFormType(parsedType.data),
      },
    });

    if (!submission) {
      throw new NotFoundException('Submission not found');
    }

    if (submission.status !== 'pending') {
      throw new BadRequestException(
        'Submission must be pending to be reviewed',
      );
    }

    return await db.evidenceSubmission
      .update({
        where: { id: params.submissionId },
        data: {
          status: parsed.data.action,
          reviewedById: reviewerUserId,
          reviewedAt: new Date(),
          reviewReason: parsed.data.reason,
        },
        include: {
          submittedBy: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
          reviewedBy: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
      })
      .then(normalizeSubmissionFormType);
  }

  async getMySubmissions(params: {
    organizationId: string;
    authContext: AuthContext;
    formType?: string;
  }) {
    const userId = this.requireJwtUser(params.authContext);

    const where: Record<string, unknown> = {
      organizationId: params.organizationId,
      submittedById: userId,
    };

    if (params.formType) {
      const parsedType = evidenceFormTypeSchema.safeParse(params.formType);
      if (!parsedType.success) {
        throw new BadRequestException('Unsupported form type');
      }
      where.formType = toDbEvidenceFormType(parsedType.data);
    }

    return await db.evidenceSubmission
      .findMany({
        where,
        include: {
          reviewedBy: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
        orderBy: {
          submittedAt: 'desc',
        },
      })
      .then((submissions) => submissions.map(normalizeSubmissionFormType));
  }

  async getPendingSubmissionCount(params: {
    organizationId: string;
    authContext: AuthContext;
  }) {
    const userId = this.requireJwtUser(params.authContext);

    const count = await db.evidenceSubmission.count({
      where: {
        organizationId: params.organizationId,
        submittedById: userId,
        status: 'pending',
      },
    });

    return { count };
  }
}


--- File: apps/api/src/evidence-forms/evidence-forms.definitions.ts ---
// Single source of truth: re-export from shared @comp/company package
export {
  evidenceFormTypeSchema,
  evidenceFormFileSchema,
  evidenceFormSubmissionSchemaMap,
  evidenceFormDefinitions,
  evidenceFormDefinitionList,
  type EvidenceFormType,
  type EvidenceFormFieldDefinition,
  type EvidenceFormDefinition,
} from '@comp/company';


--- File: apps/api/src/evidence-forms/evidence-forms.controller.ts ---
import { AuthContext, OrganizationId } from '@/auth/auth-context.decorator';
import { HybridAuthGuard } from '@/auth/hybrid-auth.guard';
import type { AuthContext as AuthContextType } from '@/auth/types';
import {
  Body,
  Controller,
  Get,
  Header,
  Param,
  Patch,
  Post,
  Query,
  Res,
  UseGuards,
} from '@nestjs/common';
import { ApiHeader, ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import type { Response } from 'express';
import { EvidenceFormsService } from './evidence-forms.service';

@ApiTags('Evidence Forms')
@Controller({ path: 'evidence-forms', version: '1' })
@UseGuards(HybridAuthGuard)
@ApiSecurity('apikey')
@ApiHeader({
  name: 'X-Organization-Id',
  description:
    'Organization ID (required for session auth, optional for API key auth)',
  required: false,
})
export class EvidenceFormsController {
  constructor(private readonly evidenceFormsService: EvidenceFormsService) {}

  @Get()
  @ApiOperation({
    summary: 'List evidence forms',
    description: 'List all available pre-built evidence forms',
  })
  listForms() {
    return this.evidenceFormsService.listForms();
  }

  @Get('statuses')
  @ApiOperation({
    summary: 'Get submission statuses for all forms',
    description:
      'Returns the latest submission date per form type for the active organization',
  })
  async getFormStatuses(@OrganizationId() organizationId: string) {
    return this.evidenceFormsService.getFormStatuses(organizationId);
  }

  @Get('my-submissions')
  @ApiOperation({
    summary: 'Get current user submissions',
    description:
      'Returns all evidence form submissions by the authenticated user for the active organization',
  })
  async getMySubmissions(
    @OrganizationId() organizationId: string,
    @AuthContext() authContext: AuthContextType,
    @Query('formType') formType?: string,
  ) {
    return this.evidenceFormsService.getMySubmissions({
      organizationId,
      authContext,
      formType,
    });
  }

  @Get('my-submissions/pending-count')
  @ApiOperation({
    summary: 'Get pending submission count for current user',
    description:
      'Returns the count of pending evidence submissions for the authenticated user',
  })
  async getPendingSubmissionCount(
    @OrganizationId() organizationId: string,
    @AuthContext() authContext: AuthContextType,
  ) {
    return this.evidenceFormsService.getPendingSubmissionCount({
      organizationId,
      authContext,
    });
  }

  @Get(':formType')
  @ApiOperation({
    summary: 'Get form definition and submissions',
    description:
      'Fetch a specific form definition with submissions for the active organization',
  })
  async getFormWithSubmissions(
    @OrganizationId() organizationId: string,
    @AuthContext() authContext: AuthContextType,
    @Param('formType') formType: string,
    @Query('search') search?: string,
    @Query('limit') limit?: string,
    @Query('offset') offset?: string,
  ) {
    return this.evidenceFormsService.getFormWithSubmissions({
      organizationId,
      authContext,
      formType,
      search,
      limit,
      offset,
    });
  }

  @Get(':formType/submissions/:submissionId')
  @ApiOperation({
    summary: 'Get a single submission',
    description:
      'Fetch one evidence form submission for the active organization',
  })
  async getSubmission(
    @OrganizationId() organizationId: string,
    @AuthContext() authContext: AuthContextType,
    @Param('formType') formType: string,
    @Param('submissionId') submissionId: string,
  ) {
    return this.evidenceFormsService.getSubmission({
      organizationId,
      authContext,
      formType,
      submissionId,
    });
  }

  @Post(':formType/submissions')
  @ApiOperation({
    summary: 'Submit evidence form entry',
    description:
      'Create a new organization-scoped evidence form submission using Zod-validated payloads',
  })
  async submitForm(
    @OrganizationId() organizationId: string,
    @AuthContext() authContext: AuthContextType,
    @Param('formType') formType: string,
    @Body() body: unknown,
  ) {
    return this.evidenceFormsService.submitForm({
      organizationId,
      formType,
      payload: body,
      authContext,
    });
  }

  @Patch(':formType/submissions/:submissionId/review')
  @ApiOperation({
    summary: 'Review a submission',
    description:
      'Approve or reject an evidence form submission with an optional reason',
  })
  async reviewSubmission(
    @OrganizationId() organizationId: string,
    @AuthContext() authContext: AuthContextType,
    @Param('formType') formType: string,
    @Param('submissionId') submissionId: string,
    @Body() body: unknown,
  ) {
    return this.evidenceFormsService.reviewSubmission({
      organizationId,
      formType,
      submissionId,
      payload: body,
      authContext,
    });
  }

  @Post('uploads')
  @ApiOperation({
    summary: 'Upload evidence form file',
    description:
      'Upload a file for evidence form fields and return file metadata for submission payload',
  })
  async uploadFile(
    @OrganizationId() organizationId: string,
    @AuthContext() authContext: AuthContextType,
    @Body() body: unknown,
  ) {
    return this.evidenceFormsService.uploadFile({
      organizationId,
      authContext,
      payload: body,
    });
  }

  @Get(':formType/export.csv')
  @ApiOperation({
    summary: 'Export form submissions to CSV',
    description: 'Export all form submissions for an organization as CSV',
  })
  @Header('Content-Type', 'text/csv')
  async exportCsv(
    @OrganizationId() organizationId: string,
    @AuthContext() authContext: AuthContextType,
    @Param('formType') formType: string,
    @Res() res: Response,
  ) {
    const csv = await this.evidenceFormsService.exportCsv({
      organizationId,
      authContext,
      formType,
    });

    const filename = `${formType}-submissions-${new Date().toISOString().slice(0, 10)}.csv`;
    res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
    res.send(csv);
  }
}


--- File: apps/api/src/evidence-forms/evidence-forms.module.ts ---
import { Module } from '@nestjs/common';
import { AttachmentsModule } from '@/attachments/attachments.module';
import { AuthModule } from '@/auth/auth.module';
import { EvidenceFormsController } from './evidence-forms.controller';
import { EvidenceFormsService } from './evidence-forms.service';

@Module({
  imports: [AuthModule, AttachmentsModule],
  controllers: [EvidenceFormsController],
  providers: [EvidenceFormsService],
  exports: [EvidenceFormsService],
})
export class EvidenceFormsModule {}



## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/49c89830-117b-4def-8edc-b5bcc50766e0/llms.txt) for all pages in this wiki.
