Skip to content

Commit b94ed82

Browse files
authored
Merge branch 'main' into chas/organization-endpoints
2 parents 402deba + 859f3b8 commit b94ed82

File tree

81 files changed

+5536
-62
lines changed

Some content is hidden

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

81 files changed

+5536
-62
lines changed

Dockerfile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ COPY apps/app ./apps/app
6262
# Bring in node_modules for build and prisma prebuild
6363
COPY --from=deps /app/node_modules ./node_modules
6464

65+
# Pre-combine schemas for app build
66+
RUN cd packages/db && node scripts/combine-schemas.js
67+
RUN cp packages/db/dist/schema.prisma apps/app/prisma/schema.prisma
68+
6569
# Ensure Next build has required public env at build-time
6670
ARG NEXT_PUBLIC_BETTER_AUTH_URL
6771
ARG NEXT_PUBLIC_PORTAL_URL
@@ -87,8 +91,8 @@ ENV NEXT_PUBLIC_BETTER_AUTH_URL=$NEXT_PUBLIC_BETTER_AUTH_URL \
8791
NEXT_OUTPUT_STANDALONE=true \
8892
NODE_OPTIONS=--max_old_space_size=6144
8993

90-
# Build the app
91-
RUN cd apps/app && SKIP_ENV_VALIDATION=true bun run build
94+
# Build the app (schema already combined above)
95+
RUN cd apps/app && SKIP_ENV_VALIDATION=true bun run build:docker
9296

9397
# =============================================================================
9498
# STAGE 4: App Production
@@ -120,15 +124,19 @@ COPY apps/portal ./apps/portal
120124
# Bring in node_modules for build and prisma prebuild
121125
COPY --from=deps /app/node_modules ./node_modules
122126

127+
# Pre-combine schemas for portal build
128+
RUN cd packages/db && node scripts/combine-schemas.js
129+
RUN cp packages/db/dist/schema.prisma apps/portal/prisma/schema.prisma
130+
123131
# Ensure Next build has required public env at build-time
124132
ARG NEXT_PUBLIC_BETTER_AUTH_URL
125133
ENV NEXT_PUBLIC_BETTER_AUTH_URL=$NEXT_PUBLIC_BETTER_AUTH_URL \
126134
NEXT_TELEMETRY_DISABLED=1 NODE_ENV=production \
127135
NEXT_OUTPUT_STANDALONE=true \
128136
NODE_OPTIONS=--max_old_space_size=6144
129137

130-
# Build the portal
131-
RUN cd apps/portal && SKIP_ENV_VALIDATION=true bun run build
138+
# Build the portal (schema already combined above)
139+
RUN cd apps/portal && SKIP_ENV_VALIDATION=true bun run build:docker
132140

133141
# =============================================================================
134142
# STAGE 6: Portal Production

apps/api/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"@nestjs/platform-express": "^11.1.5",
1313
"@nestjs/swagger": "^11.2.0",
1414
"@trycompai/db": "^1.3.4",
15+
"archiver": "^7.0.1",
1516
"class-transformer": "^0.5.1",
1617
"class-validator": "^0.14.2",
1718
"jose": "^6.0.12",
@@ -26,6 +27,7 @@
2627
"@nestjs/cli": "^11.0.0",
2728
"@nestjs/schematics": "^11.0.0",
2829
"@nestjs/testing": "^11.0.1",
30+
"@types/archiver": "^6.0.3",
2931
"@types/express": "^5.0.0",
3032
"@types/jest": "^30.0.0",
3133
"@types/node": "^24.0.3",
@@ -67,8 +69,9 @@
6769
"private": true,
6870
"scripts": {
6971
"build": "nest build",
72+
"build:docker": "prisma generate && nest build",
7073
"db:generate": "bun run db:getschema && prisma generate",
71-
"db:getschema": "cp ../../node_modules/@trycompai/db/dist/schema.prisma prisma/schema.prisma",
74+
"db:getschema": "node ../../packages/db/scripts/combine-schemas.js && cp ../../packages/db/dist/schema.prisma prisma/schema.prisma",
7275
"dev": "nest start --watch",
7376
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
7477
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",

apps/api/src/app.module.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ import { AttachmentsModule } from './attachments/attachments.module';
66
import { AuthModule } from './auth/auth.module';
77
import { CommentsModule } from './comments/comments.module';
88
import { DevicesModule } from './devices/devices.module';
9+
import { DeviceAgentModule } from './device-agent/device-agent.module';
910
import { awsConfig } from './config/aws.config';
1011
import { HealthModule } from './health/health.module';
1112
import { OrganizationModule } from './organization/organization.module';
13+
import { PoliciesModule } from './policies/policies.module';
14+
import { RisksModule } from './risks/risks.module';
1215
import { TasksModule } from './tasks/tasks.module';
16+
import { VendorsModule } from './vendors/vendors.module';
17+
import { ContextModule } from './context/context.module';
18+
1319

1420
@Module({
1521
imports: [
@@ -23,6 +29,12 @@ import { TasksModule } from './tasks/tasks.module';
2329
}),
2430
AuthModule,
2531
OrganizationModule,
32+
RisksModule,
33+
VendorsModule,
34+
ContextModule,
35+
DevicesModule,
36+
PoliciesModule,
37+
DeviceAgentModule,
2638
DevicesModule,
2739
AttachmentsModule,
2840
TasksModule,
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import {
2+
Controller,
3+
Get,
4+
Post,
5+
Patch,
6+
Delete,
7+
Body,
8+
Param,
9+
UseGuards
10+
} from '@nestjs/common';
11+
import {
12+
ApiBody,
13+
ApiHeader,
14+
ApiOperation,
15+
ApiParam,
16+
ApiResponse,
17+
ApiSecurity,
18+
ApiTags,
19+
} from '@nestjs/swagger';
20+
import {
21+
AuthContext,
22+
OrganizationId,
23+
} from '../auth/auth-context.decorator';
24+
import { HybridAuthGuard } from '../auth/hybrid-auth.guard';
25+
import type { AuthContext as AuthContextType } from '../auth/types';
26+
import { CreateContextDto } from './dto/create-context.dto';
27+
import { UpdateContextDto } from './dto/update-context.dto';
28+
import { ContextService } from './context.service';
29+
import { CONTEXT_OPERATIONS } from './schemas/context-operations';
30+
import { CONTEXT_PARAMS } from './schemas/context-params';
31+
import { CONTEXT_BODIES } from './schemas/context-bodies';
32+
import { GET_ALL_CONTEXT_RESPONSES } from './schemas/get-all-context.responses';
33+
import { GET_CONTEXT_BY_ID_RESPONSES } from './schemas/get-context-by-id.responses';
34+
import { CREATE_CONTEXT_RESPONSES } from './schemas/create-context.responses';
35+
import { UPDATE_CONTEXT_RESPONSES } from './schemas/update-context.responses';
36+
import { DELETE_CONTEXT_RESPONSES } from './schemas/delete-context.responses';
37+
38+
@ApiTags('Context')
39+
@Controller({ path: 'context', version: '1' })
40+
@UseGuards(HybridAuthGuard)
41+
@ApiSecurity('apikey')
42+
@ApiHeader({
43+
name: 'X-Organization-Id',
44+
description:
45+
'Organization ID (required for session auth, optional for API key auth)',
46+
required: false,
47+
})
48+
export class ContextController {
49+
constructor(private readonly contextService: ContextService) {}
50+
51+
@Get()
52+
@ApiOperation(CONTEXT_OPERATIONS.getAllContext)
53+
@ApiResponse(GET_ALL_CONTEXT_RESPONSES[200])
54+
@ApiResponse(GET_ALL_CONTEXT_RESPONSES[401])
55+
@ApiResponse(GET_ALL_CONTEXT_RESPONSES[404])
56+
@ApiResponse(GET_ALL_CONTEXT_RESPONSES[500])
57+
async getAllContext(
58+
@OrganizationId() organizationId: string,
59+
@AuthContext() authContext: AuthContextType,
60+
) {
61+
const contextEntries = await this.contextService.findAllByOrganization(organizationId);
62+
63+
return {
64+
data: contextEntries,
65+
count: contextEntries.length,
66+
authType: authContext.authType,
67+
...(authContext.userId && authContext.userEmail && {
68+
authenticatedUser: {
69+
id: authContext.userId,
70+
email: authContext.userEmail,
71+
},
72+
}),
73+
};
74+
}
75+
76+
@Get(':id')
77+
@ApiOperation(CONTEXT_OPERATIONS.getContextById)
78+
@ApiParam(CONTEXT_PARAMS.contextId)
79+
@ApiResponse(GET_CONTEXT_BY_ID_RESPONSES[200])
80+
@ApiResponse(GET_CONTEXT_BY_ID_RESPONSES[401])
81+
@ApiResponse(GET_CONTEXT_BY_ID_RESPONSES[404])
82+
@ApiResponse(GET_CONTEXT_BY_ID_RESPONSES[500])
83+
async getContextById(
84+
@Param('id') contextId: string,
85+
@OrganizationId() organizationId: string,
86+
@AuthContext() authContext: AuthContextType,
87+
) {
88+
const contextEntry = await this.contextService.findById(contextId, organizationId);
89+
90+
return {
91+
...contextEntry,
92+
authType: authContext.authType,
93+
...(authContext.userId && authContext.userEmail && {
94+
authenticatedUser: {
95+
id: authContext.userId,
96+
email: authContext.userEmail,
97+
},
98+
}),
99+
};
100+
}
101+
102+
@Post()
103+
@ApiOperation(CONTEXT_OPERATIONS.createContext)
104+
@ApiBody(CONTEXT_BODIES.createContext)
105+
@ApiResponse(CREATE_CONTEXT_RESPONSES[201])
106+
@ApiResponse(CREATE_CONTEXT_RESPONSES[400])
107+
@ApiResponse(CREATE_CONTEXT_RESPONSES[401])
108+
@ApiResponse(CREATE_CONTEXT_RESPONSES[404])
109+
@ApiResponse(CREATE_CONTEXT_RESPONSES[500])
110+
async createContext(
111+
@Body() createContextDto: CreateContextDto,
112+
@OrganizationId() organizationId: string,
113+
@AuthContext() authContext: AuthContextType,
114+
) {
115+
const contextEntry = await this.contextService.create(organizationId, createContextDto);
116+
117+
return {
118+
...contextEntry,
119+
authType: authContext.authType,
120+
...(authContext.userId && authContext.userEmail && {
121+
authenticatedUser: {
122+
id: authContext.userId,
123+
email: authContext.userEmail,
124+
},
125+
}),
126+
};
127+
}
128+
129+
@Patch(':id')
130+
@ApiOperation(CONTEXT_OPERATIONS.updateContext)
131+
@ApiParam(CONTEXT_PARAMS.contextId)
132+
@ApiBody(CONTEXT_BODIES.updateContext)
133+
@ApiResponse(UPDATE_CONTEXT_RESPONSES[200])
134+
@ApiResponse(UPDATE_CONTEXT_RESPONSES[400])
135+
@ApiResponse(UPDATE_CONTEXT_RESPONSES[401])
136+
@ApiResponse(UPDATE_CONTEXT_RESPONSES[404])
137+
@ApiResponse(UPDATE_CONTEXT_RESPONSES[500])
138+
async updateContext(
139+
@Param('id') contextId: string,
140+
@Body() updateContextDto: UpdateContextDto,
141+
@OrganizationId() organizationId: string,
142+
@AuthContext() authContext: AuthContextType,
143+
) {
144+
const updatedContextEntry = await this.contextService.updateById(
145+
contextId,
146+
organizationId,
147+
updateContextDto,
148+
);
149+
150+
return {
151+
...updatedContextEntry,
152+
authType: authContext.authType,
153+
...(authContext.userId && authContext.userEmail && {
154+
authenticatedUser: {
155+
id: authContext.userId,
156+
email: authContext.userEmail,
157+
},
158+
}),
159+
};
160+
}
161+
162+
@Delete(':id')
163+
@ApiOperation(CONTEXT_OPERATIONS.deleteContext)
164+
@ApiParam(CONTEXT_PARAMS.contextId)
165+
@ApiResponse(DELETE_CONTEXT_RESPONSES[200])
166+
@ApiResponse(DELETE_CONTEXT_RESPONSES[401])
167+
@ApiResponse(DELETE_CONTEXT_RESPONSES[404])
168+
@ApiResponse(DELETE_CONTEXT_RESPONSES[500])
169+
async deleteContext(
170+
@Param('id') contextId: string,
171+
@OrganizationId() organizationId: string,
172+
@AuthContext() authContext: AuthContextType,
173+
) {
174+
const result = await this.contextService.deleteById(contextId, organizationId);
175+
176+
return {
177+
...result,
178+
authType: authContext.authType,
179+
...(authContext.userId && authContext.userEmail && {
180+
authenticatedUser: {
181+
id: authContext.userId,
182+
email: authContext.userEmail,
183+
},
184+
}),
185+
};
186+
}
187+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Module } from '@nestjs/common';
2+
import { AuthModule } from '../auth/auth.module';
3+
import { ContextController } from './context.controller';
4+
import { ContextService } from './context.service';
5+
6+
@Module({
7+
imports: [AuthModule],
8+
controllers: [ContextController],
9+
providers: [ContextService],
10+
exports: [ContextService],
11+
})
12+
export class ContextModule {}

0 commit comments

Comments
 (0)