Skip to content

Commit 68f0401

Browse files
committed
chore: merge main into release for new releases
2 parents 6aa0ae6 + 2b9659a commit 68f0401

File tree

168 files changed

+16296
-2109
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

168 files changed

+16296
-2109
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ APP_AWS_ACCESS_KEY_ID="" # AWS Access Key ID
1717
APP_AWS_SECRET_ACCESS_KEY="" # AWS Secret Access Key
1818
APP_AWS_REGION="" # AWS Region
1919
APP_AWS_BUCKET_NAME="" # AWS Bucket Name
20+
APP_AWS_QUESTIONNAIRE_UPLOAD_BUCKET="" # AWS, Required for Security Questionnaire feature
2021

2122
TRIGGER_SECRET_KEY="" # For background jobs. Self-host or use cloud-version @ https://trigger.dev
2223
# TRIGGER_API_URL="" # Only set if you are self-hosting
@@ -26,4 +27,6 @@ TRIGGER_SECRET_KEY="" # Secret key from Trigger.dev
2627
OPENAI_API_KEY="" # AI Chat + Auto Generated Policies, Risks + Vendors
2728
FIRECRAWL_API_KEY="" # For research, self-host or use cloud-version @ https://firecrawl.dev
2829

30+
TRUST_APP_URL="http://localhost:3008" # Trust portal public site for NDA signing and access requests
31+
2932
AUTH_TRUSTED_ORIGINS=http://localhost:3000,https://*.trycomp.ai,http://localhost:3002

.github/workflows/auto-pr-to-main.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ on:
1313
- claudio/*
1414
- mariano/*
1515
- lewis/*
16+
- daniel/*
1617
- COMP-*
1718
- cursor/*
1819
- codex/*
1920
- chas/*
21+
- tofik/*
2022
jobs:
2123
create-pull-request:
2224
runs-on: warp-ubuntu-latest-arm64-4x
@@ -35,9 +37,9 @@ jobs:
3537
uses: repo-sync/pull-request@v2
3638
with:
3739
github_token: ${{ secrets.GITHUB_TOKEN }}
38-
destination_branch: 'main'
39-
pr_title: '[dev] [${{ github.actor }}] ${{ github.ref_name }}'
40-
pr_label: 'automated-pr'
40+
destination_branch: "main"
41+
pr_title: "[dev] [${{ github.actor }}] ${{ github.ref_name }}"
42+
pr_label: "automated-pr"
4143
pr_body: |
4244
This is an automated pull request to merge ${{ github.ref_name }} into dev.
4345
It was created by the [Auto Pull Request] action.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ node_modules
55
.pnp
66
.pnp.js
77

8+
.idea/
89
# testing
910
coverage
1011

@@ -83,4 +84,5 @@ packages/*/dist
8384
**/src/db/generated/
8485

8586
# Release script
86-
scripts/sync-release-branch.sh
87+
scripts/sync-release-branch.sh
88+
/.vscode

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@
2323
},
2424
"[typescriptreact]": {
2525
"editor.defaultFormatter": "esbenp.prettier-vscode"
26-
}
26+
},
27+
"typescript.tsserver.experimental.enableProjectDiagnostics": true
2728
}

SELF_HOSTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Portal (`apps/portal`):
4343

4444
App (`apps/app`):
4545

46+
- **APP_AWS_REGION**, **APP_AWS_ACCESS_KEY_ID**, **APP_AWS_SECRET_ACCESS_KEY**, **APP_AWS_BUCKET_NAME**: AWS S3 credentials for file storage (attachments, general uploads).
47+
- **APP_AWS_QUESTIONNAIRE_UPLOAD_BUCKET**: AWS S3 bucket name specifically for questionnaire file uploads. Required for the Security Questionnaire feature. If not set, users will see an error when trying to parse questionnaires.
4648
- **OPENAI_API_KEY**: Enables AI features that call OpenAI models.
4749
- **UPSTASH_REDIS_REST_URL**, **UPSTASH_REDIS_REST_TOKEN**: Optional Redis (Upstash) used for rate limiting/queues/caching.
4850
- **NEXT_PUBLIC_POSTHOG_KEY**, **NEXT_PUBLIC_POSTHOG_HOST**: Client analytics via PostHog; leave unset to disable.
@@ -143,6 +145,12 @@ BETTER_AUTH_URL_PORTAL=http://localhost:3002
143145
NEXT_PUBLIC_BETTER_AUTH_URL_PORTAL=http://localhost:3002
144146
145147
# Optional
148+
# AWS S3 (for file storage)
149+
# APP_AWS_REGION=
150+
# APP_AWS_ACCESS_KEY_ID=
151+
# APP_AWS_SECRET_ACCESS_KEY=
152+
# APP_AWS_BUCKET_NAME=
153+
# APP_AWS_QUESTIONNAIRE_UPLOAD_BUCKET=
146154
# OPENAI_API_KEY=
147155
# UPSTASH_REDIS_REST_URL=
148156
# UPSTASH_REDIS_REST_TOKEN=
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
---
2+
description: Only the most important rules for writing basic Trigger.dev tasks
3+
globs: **/trigger/**/*.ts
4+
alwaysApply: false
5+
---
6+
# Trigger.dev Basic Tasks (v4)
7+
8+
**MUST use `@trigger.dev/sdk` (v4), NEVER `client.defineJob`**
9+
10+
## Basic Task
11+
12+
```ts
13+
import { task } from "@trigger.dev/sdk";
14+
15+
export const processData = task({
16+
id: "process-data",
17+
retry: {
18+
maxAttempts: 10,
19+
factor: 1.8,
20+
minTimeoutInMs: 500,
21+
maxTimeoutInMs: 30_000,
22+
randomize: false,
23+
},
24+
run: async (payload: { userId: string; data: any[] }) => {
25+
// Task logic - runs for long time, no timeouts
26+
console.log(`Processing ${payload.data.length} items for user ${payload.userId}`);
27+
return { processed: payload.data.length };
28+
},
29+
});
30+
```
31+
32+
## Schema Task (with validation)
33+
34+
```ts
35+
import { schemaTask } from "@trigger.dev/sdk";
36+
import { z } from "zod";
37+
38+
export const validatedTask = schemaTask({
39+
id: "validated-task",
40+
schema: z.object({
41+
name: z.string(),
42+
age: z.number(),
43+
email: z.string().email(),
44+
}),
45+
run: async (payload) => {
46+
// Payload is automatically validated and typed
47+
return { message: `Hello ${payload.name}, age ${payload.age}` };
48+
},
49+
});
50+
```
51+
52+
## Scheduled Task
53+
54+
```ts
55+
import { schedules } from "@trigger.dev/sdk";
56+
57+
const dailyReport = schedules.task({
58+
id: "daily-report",
59+
cron: "0 9 * * *", // Daily at 9:00 AM UTC
60+
// or with timezone: cron: { pattern: "0 9 * * *", timezone: "America/New_York" },
61+
run: async (payload) => {
62+
console.log("Scheduled run at:", payload.timestamp);
63+
console.log("Last run was:", payload.lastTimestamp);
64+
console.log("Next 5 runs:", payload.upcoming);
65+
66+
// Generate daily report logic
67+
return { reportGenerated: true, date: payload.timestamp };
68+
},
69+
});
70+
```
71+
72+
## Triggering Tasks
73+
74+
### From Backend Code
75+
76+
```ts
77+
import { tasks } from "@trigger.dev/sdk";
78+
import type { processData } from "./trigger/tasks";
79+
80+
// Single trigger
81+
const handle = await tasks.trigger<typeof processData>("process-data", {
82+
userId: "123",
83+
data: [{ id: 1 }, { id: 2 }],
84+
});
85+
86+
// Batch trigger
87+
const batchHandle = await tasks.batchTrigger<typeof processData>("process-data", [
88+
{ payload: { userId: "123", data: [{ id: 1 }] } },
89+
{ payload: { userId: "456", data: [{ id: 2 }] } },
90+
]);
91+
```
92+
93+
### From Inside Tasks (with Result handling)
94+
95+
```ts
96+
export const parentTask = task({
97+
id: "parent-task",
98+
run: async (payload) => {
99+
// Trigger and continue
100+
const handle = await childTask.trigger({ data: "value" });
101+
102+
// Trigger and wait - returns Result object, NOT task output
103+
const result = await childTask.triggerAndWait({ data: "value" });
104+
if (result.ok) {
105+
console.log("Task output:", result.output); // Actual task return value
106+
} else {
107+
console.error("Task failed:", result.error);
108+
}
109+
110+
// Quick unwrap (throws on error)
111+
const output = await childTask.triggerAndWait({ data: "value" }).unwrap();
112+
113+
// Batch trigger and wait
114+
const results = await childTask.batchTriggerAndWait([
115+
{ payload: { data: "item1" } },
116+
{ payload: { data: "item2" } },
117+
]);
118+
119+
for (const run of results) {
120+
if (run.ok) {
121+
console.log("Success:", run.output);
122+
} else {
123+
console.log("Failed:", run.error);
124+
}
125+
}
126+
},
127+
});
128+
129+
export const childTask = task({
130+
id: "child-task",
131+
run: async (payload: { data: string }) => {
132+
return { processed: payload.data };
133+
},
134+
});
135+
```
136+
137+
> Never wrap triggerAndWait or batchTriggerAndWait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks.
138+
139+
## Waits
140+
141+
```ts
142+
import { task, wait } from "@trigger.dev/sdk";
143+
144+
export const taskWithWaits = task({
145+
id: "task-with-waits",
146+
run: async (payload) => {
147+
console.log("Starting task");
148+
149+
// Wait for specific duration
150+
await wait.for({ seconds: 30 });
151+
await wait.for({ minutes: 5 });
152+
await wait.for({ hours: 1 });
153+
await wait.for({ days: 1 });
154+
155+
// Wait until specific date
156+
await wait.until({ date: new Date("2024-12-25") });
157+
158+
// Wait for token (from external system)
159+
await wait.forToken({
160+
token: "user-approval-token",
161+
timeoutInSeconds: 3600, // 1 hour timeout
162+
});
163+
164+
console.log("All waits completed");
165+
return { status: "completed" };
166+
},
167+
});
168+
```
169+
170+
> Never wrap wait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks.
171+
172+
## Key Points
173+
174+
- **Result vs Output**: `triggerAndWait()` returns a `Result` object with `ok`, `output`, `error` properties - NOT the direct task output
175+
- **Type safety**: Use `import type` for task references when triggering from backend
176+
- **Waits > 5 seconds**: Automatically checkpointed, don't count toward compute usage
177+
178+
## NEVER Use (v2 deprecated)
179+
180+
```ts
181+
// BREAKS APPLICATION
182+
client.defineJob({
183+
id: "job-id",
184+
run: async (payload, io) => {
185+
/* ... */
186+
},
187+
});
188+
```
189+
190+
Use v4 SDK (`@trigger.dev/sdk`), check `result.ok` before accessing `result.output`

apps/api/buildspec.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ phases:
2626
- '[ -n "$DATABASE_URL" ] || { echo "❌ DATABASE_URL is not set"; exit 1; }'
2727
- '[ -n "$BASE_URL" ] || { echo "❌ BASE_URL is not set"; exit 1; }'
2828
- '[ -n "$BETTER_AUTH_URL" ] || { echo "❌ BETTER_AUTH_URL is not set"; exit 1; }'
29+
- '[ -n "$TRUST_APP_URL" ] || { echo "❌ TRUST_APP_URL is not set"; exit 1; }'
2930

3031
# Install only API workspace dependencies
3132
- echo "Installing API dependencies only..."

apps/api/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@
2020
"class-validator": "^0.14.2",
2121
"dotenv": "^17.2.3",
2222
"jose": "^6.0.12",
23+
"jspdf": "^3.0.3",
24+
"nanoid": "^5.1.6",
25+
"pdf-lib": "^1.17.1",
2326
"prisma": "^6.13.0",
2427
"reflect-metadata": "^0.2.2",
28+
"resend": "^6.4.2",
2529
"rxjs": "^7.8.1",
2630
"swagger-ui-express": "^5.0.1",
2731
"zod": "^4.0.14"

apps/api/src/attachments/attachments.service.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,60 @@ export class AttachmentsService {
267267
});
268268
}
269269

270-
/**
271-
* Sanitize filename for S3 storage
272-
*/
270+
async uploadToS3(
271+
fileBuffer: Buffer,
272+
fileName: string,
273+
contentType: string,
274+
organizationId: string,
275+
entityType: string,
276+
entityId: string,
277+
): Promise<string> {
278+
const fileId = randomBytes(16).toString('hex');
279+
const sanitizedFileName = this.sanitizeFileName(fileName);
280+
const timestamp = Date.now();
281+
const s3Key = `${organizationId}/attachments/${entityType}/${entityId}/${timestamp}-${fileId}-${sanitizedFileName}`;
282+
283+
const putCommand = new PutObjectCommand({
284+
Bucket: this.bucketName,
285+
Key: s3Key,
286+
Body: fileBuffer,
287+
ContentType: contentType,
288+
Metadata: {
289+
originalFileName: this.sanitizeHeaderValue(fileName),
290+
organizationId,
291+
entityId,
292+
entityType,
293+
},
294+
});
295+
296+
await this.s3Client.send(putCommand);
297+
return s3Key;
298+
}
299+
300+
async getPresignedDownloadUrl(s3Key: string): Promise<string> {
301+
return this.generateSignedUrl(s3Key);
302+
}
303+
304+
async getObjectBuffer(s3Key: string): Promise<Buffer> {
305+
const getCommand = new GetObjectCommand({
306+
Bucket: this.bucketName,
307+
Key: s3Key,
308+
});
309+
310+
const response = await this.s3Client.send(getCommand);
311+
const chunks: Uint8Array[] = [];
312+
313+
if (!response.Body) {
314+
throw new InternalServerErrorException('No file data received from S3');
315+
}
316+
317+
for await (const chunk of response.Body as any) {
318+
chunks.push(chunk);
319+
}
320+
321+
return Buffer.concat(chunks);
322+
}
323+
273324
private sanitizeFileName(fileName: string): string {
274325
return fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
275326
}
@@ -281,6 +332,7 @@ export class AttachmentsService {
281332
* - Trim whitespace
282333
*/
283334
private sanitizeHeaderValue(value: string): string {
335+
// eslint-disable-next-line no-control-regex
284336
const withoutControls = value.replace(/[\x00-\x1F\x7F]/g, '');
285337
const asciiOnly = withoutControls.replace(/[^\x20-\x7E]/g, '_');
286338
return asciiOnly.trim();

apps/api/src/comments/dto/update-comment.dto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ export class UpdateCommentDto {
1111
@IsNotEmpty()
1212
@MaxLength(2000)
1313
content: string;
14-
}
14+
}

0 commit comments

Comments
 (0)