Skip to content

Commit aac5172

Browse files
committed
chore: merge main into release for new releases
2 parents f232d73 + 77b6c21 commit aac5172

170 files changed

Lines changed: 14142 additions & 297 deletions

File tree

Some content is hidden

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

apps/api/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ APP_AWS_ORG_ASSETS_BUCKET=
1212

1313
DATABASE_URL=
1414

15+
NOVU_API_KEY=
16+
INTERNAL_API_TOKEN=
1517

1618
# Upstash
1719
UPSTASH_REDIS_REST_URL=

apps/api/Dockerfile.multistage

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ COPY packages/db/package.json ./packages/db/
1313
COPY packages/utils/package.json ./packages/utils/
1414
COPY packages/integration-platform/package.json ./packages/integration-platform/
1515
COPY packages/tsconfig/package.json ./packages/tsconfig/
16+
COPY packages/email/package.json ./packages/email/
1617

1718
# Copy API package.json
1819
COPY apps/api/package.json ./apps/api/
@@ -32,6 +33,7 @@ COPY packages/db ./packages/db
3233
COPY packages/utils ./packages/utils
3334
COPY packages/integration-platform ./packages/integration-platform
3435
COPY packages/tsconfig ./packages/tsconfig
36+
COPY packages/email ./packages/email
3537

3638
# Copy API source
3739
COPY apps/api ./apps/api
@@ -42,6 +44,7 @@ COPY --from=deps /app/node_modules ./node_modules
4244
# Build workspace packages
4345
RUN cd packages/db && bun run build && cd ../..
4446
RUN cd packages/integration-platform && bun run build && cd ../..
47+
RUN cd packages/email && bun run build && cd ../..
4548

4649
# Generate Prisma client for API (copy schema and generate)
4750
RUN cd packages/db && node scripts/combine-schemas.js && cd ../..
@@ -75,6 +78,7 @@ COPY --from=builder /app/packages/db ./packages/db
7578
COPY --from=builder /app/packages/utils ./packages/utils
7679
COPY --from=builder /app/packages/integration-platform ./packages/integration-platform
7780
COPY --from=builder /app/packages/tsconfig ./packages/tsconfig
81+
COPY --from=builder /app/packages/email ./packages/email
7882

7983
# Copy production node_modules (includes symlinks to workspace packages above)
8084
COPY --from=builder /app/node_modules ./node_modules
@@ -101,4 +105,3 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
101105

102106
# Start the application
103107
CMD ["node", "dist/src/main.js"]
104-

apps/api/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@browserbasehq/sdk": "^2.6.0",
1515
"@browserbasehq/stagehand": "^3.0.5",
1616
"@comp/integration-platform": "workspace:*",
17+
"@mendable/firecrawl-js": "^4.9.3",
1718
"@nestjs/common": "^11.0.1",
1819
"@nestjs/config": "^4.0.2",
1920
"@nestjs/core": "^11.0.1",
@@ -25,7 +26,8 @@
2526
"@react-email/components": "^0.0.41",
2627
"@trigger.dev/build": "4.0.6",
2728
"@trigger.dev/sdk": "4.0.6",
28-
"@trycompai/db": "1.3.19",
29+
"@trycompai/db": "^1.3.20",
30+
"@trycompai/email": "workspace:*",
2931
"@upstash/vector": "^1.2.2",
3032
"adm-zip": "^0.5.16",
3133
"ai": "^5.0.60",

apps/api/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { SOAModule } from './soa/soa.module';
2828
import { IntegrationPlatformModule } from './integration-platform/integration-platform.module';
2929
import { CloudSecurityModule } from './cloud-security/cloud-security.module';
3030
import { BrowserbaseModule } from './browserbase/browserbase.module';
31+
import { TaskManagementModule } from './task-management/task-management.module';
3132

3233
@Module({
3334
imports: [
@@ -68,6 +69,7 @@ import { BrowserbaseModule } from './browserbase/browserbase.module';
6869
IntegrationPlatformModule,
6970
CloudSecurityModule,
7071
BrowserbaseModule,
72+
TaskManagementModule,
7173
],
7274
controllers: [AppController],
7375
providers: [

apps/api/src/app/s3.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { GetObjectCommand, S3Client, type GetObjectCommandOutput } from '@aws-sdk/client-s3';
1+
import {
2+
GetObjectCommand,
3+
S3Client,
4+
type GetObjectCommandOutput,
5+
} from '@aws-sdk/client-s3';
26
import { Logger } from '@nestjs/common';
37
import '../config/load-env';
48

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { UploadAttachmentDto } from './upload-attachment.dto';
1919
export class AttachmentsService {
2020
private s3Client: S3Client;
2121
private bucketName: string;
22-
private readonly MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
22+
private readonly MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024; // 100MB
2323
private readonly SIGNED_URL_EXPIRY = 900; // 15 minutes
2424

2525
constructor() {
@@ -129,7 +129,20 @@ export class AttachmentsService {
129129
const fileId = randomBytes(16).toString('hex');
130130
const sanitizedFileName = this.sanitizeFileName(uploadDto.fileName);
131131
const timestamp = Date.now();
132-
const s3Key = `${organizationId}/attachments/${entityType}/${entityId}/${timestamp}-${fileId}-${sanitizedFileName}`;
132+
133+
// Special S3 path structure for task items: org_{orgId}/attachments/task-item/{entityType}/{entityId}
134+
let s3Key: string;
135+
if (entityType === 'task_item') {
136+
// For task items, extract entityType and entityId from metadata
137+
// Metadata should contain taskItemEntityType and taskItemEntityId
138+
const taskItemEntityType =
139+
uploadDto.description?.split('|')[0] || 'unknown';
140+
const taskItemEntityId =
141+
uploadDto.description?.split('|')[1] || entityId;
142+
s3Key = `${organizationId}/attachments/task-item/${taskItemEntityType}/${taskItemEntityId}/${timestamp}-${fileId}-${sanitizedFileName}`;
143+
} else {
144+
s3Key = `${organizationId}/attachments/${entityType}/${entityId}/${timestamp}-${fileId}-${sanitizedFileName}`;
145+
}
133146

134147
// Upload to S3
135148
const putCommand = new PutObjectCommand({

apps/api/src/auth/auth-context.decorator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export const AuthContext = createParamDecorator(
99
(data: unknown, ctx: ExecutionContext): AuthContextType => {
1010
const request = ctx.switchToHttp().getRequest<AuthenticatedRequest>();
1111

12-
const { organizationId, authType, isApiKey, userId, userEmail } = request;
12+
const { organizationId, authType, isApiKey, userId, userEmail, userRoles } =
13+
request;
1314

1415
if (!organizationId || !authType) {
1516
throw new Error(
@@ -23,6 +24,7 @@ export const AuthContext = createParamDecorator(
2324
isApiKey,
2425
userId,
2526
userEmail,
27+
userRoles,
2628
};
2729
},
2830
);

apps/api/src/auth/auth.module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
22
import { ApiKeyGuard } from './api-key.guard';
33
import { ApiKeyService } from './api-key.service';
44
import { HybridAuthGuard } from './hybrid-auth.guard';
5+
import { InternalTokenGuard } from './internal-token.guard';
56

67
@Module({
7-
providers: [ApiKeyService, ApiKeyGuard, HybridAuthGuard],
8-
exports: [ApiKeyService, ApiKeyGuard, HybridAuthGuard],
8+
providers: [ApiKeyService, ApiKeyGuard, HybridAuthGuard, InternalTokenGuard],
9+
exports: [ApiKeyService, ApiKeyGuard, HybridAuthGuard, InternalTokenGuard],
910
})
1011
export class AuthModule {}

apps/api/src/auth/hybrid-auth.guard.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export class HybridAuthGuard implements CanActivate {
7070
request.organizationId = organizationId;
7171
request.authType = 'api-key';
7272
request.isApiKey = true;
73+
// API keys are organization-scoped and are not tied to a specific user/member.
74+
request.userRoles = null;
7375

7476
return true;
7577
}
@@ -171,9 +173,23 @@ export class HybridAuthGuard implements CanActivate {
171173
);
172174
}
173175

176+
const member = await db.member.findFirst({
177+
where: {
178+
userId,
179+
organizationId: explicitOrgId,
180+
deactivated: false,
181+
},
182+
select: {
183+
role: true,
184+
},
185+
});
186+
187+
const userRoles = member?.role ? member.role.split(',') : null;
188+
174189
// Set request context for JWT auth
175190
request.userId = userId;
176191
request.userEmail = userEmail;
192+
request.userRoles = userRoles;
177193
request.organizationId = explicitOrgId;
178194
request.authType = 'jwt';
179195
request.isApiKey = false;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {
2+
CanActivate,
3+
ExecutionContext,
4+
Injectable,
5+
Logger,
6+
UnauthorizedException,
7+
} from '@nestjs/common';
8+
9+
type RequestWithHeaders = {
10+
headers: Record<string, string | string[] | undefined>;
11+
};
12+
13+
@Injectable()
14+
export class InternalTokenGuard implements CanActivate {
15+
private readonly logger = new Logger(InternalTokenGuard.name);
16+
17+
canActivate(context: ExecutionContext): boolean {
18+
const expectedToken = process.env.INTERNAL_API_TOKEN;
19+
20+
// In production, we require the token to be configured.
21+
if (!expectedToken) {
22+
if (process.env.NODE_ENV === 'production') {
23+
this.logger.error('INTERNAL_API_TOKEN is not configured in production');
24+
throw new UnauthorizedException('Internal access is not configured');
25+
}
26+
27+
// In local/dev, allow requests if not configured to keep DX smooth.
28+
this.logger.warn(
29+
'INTERNAL_API_TOKEN is not configured; allowing internal request in non-production',
30+
);
31+
return true;
32+
}
33+
34+
const req = context.switchToHttp().getRequest<RequestWithHeaders>();
35+
const headerValue = req.headers['x-internal-token'];
36+
const token = Array.isArray(headerValue) ? headerValue[0] : headerValue;
37+
38+
if (!token || token !== expectedToken) {
39+
throw new UnauthorizedException('Invalid internal token');
40+
}
41+
42+
return true;
43+
}
44+
}
45+
46+

0 commit comments

Comments
 (0)