Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ COPY apps/app ./apps/app
# Bring in node_modules for build and prisma prebuild
COPY --from=deps /app/node_modules ./node_modules

# Pre-combine schemas for app build
RUN cd packages/db && node scripts/combine-schemas.js
RUN cp packages/db/dist/schema.prisma apps/app/prisma/schema.prisma

# Ensure Next build has required public env at build-time
ARG NEXT_PUBLIC_BETTER_AUTH_URL
ARG NEXT_PUBLIC_PORTAL_URL
Expand All @@ -87,8 +91,8 @@ ENV NEXT_PUBLIC_BETTER_AUTH_URL=$NEXT_PUBLIC_BETTER_AUTH_URL \
NEXT_OUTPUT_STANDALONE=true \
NODE_OPTIONS=--max_old_space_size=6144

# Build the app
RUN cd apps/app && SKIP_ENV_VALIDATION=true bun run build
# Build the app (schema already combined above)
RUN cd apps/app && SKIP_ENV_VALIDATION=true bun run build:docker

# =============================================================================
# STAGE 4: App Production
Expand Down Expand Up @@ -120,15 +124,19 @@ COPY apps/portal ./apps/portal
# Bring in node_modules for build and prisma prebuild
COPY --from=deps /app/node_modules ./node_modules

# Pre-combine schemas for portal build
RUN cd packages/db && node scripts/combine-schemas.js
RUN cp packages/db/dist/schema.prisma apps/portal/prisma/schema.prisma

# Ensure Next build has required public env at build-time
ARG NEXT_PUBLIC_BETTER_AUTH_URL
ENV NEXT_PUBLIC_BETTER_AUTH_URL=$NEXT_PUBLIC_BETTER_AUTH_URL \
NEXT_TELEMETRY_DISABLED=1 NODE_ENV=production \
NEXT_OUTPUT_STANDALONE=true \
NODE_OPTIONS=--max_old_space_size=6144

# Build the portal
RUN cd apps/portal && SKIP_ENV_VALIDATION=true bun run build
# Build the portal (schema already combined above)
RUN cd apps/portal && SKIP_ENV_VALIDATION=true bun run build:docker

# =============================================================================
# STAGE 6: Portal Production
Expand Down
5 changes: 4 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@nestjs/platform-express": "^11.1.5",
"@nestjs/swagger": "^11.2.0",
"@trycompai/db": "^1.3.4",
"archiver": "^7.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"jose": "^6.0.12",
Expand All @@ -26,6 +27,7 @@
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/archiver": "^6.0.3",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.3",
Expand Down Expand Up @@ -67,8 +69,9 @@
"private": true,
"scripts": {
"build": "nest build",
"build:docker": "prisma generate && nest build",
"db:generate": "bun run db:getschema && prisma generate",
"db:getschema": "cp ../../node_modules/@trycompai/db/dist/schema.prisma prisma/schema.prisma",
"db:getschema": "node ../../packages/db/scripts/combine-schemas.js && cp ../../packages/db/dist/schema.prisma prisma/schema.prisma",
"dev": "nest start --watch",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
Expand Down
12 changes: 12 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import { AttachmentsModule } from './attachments/attachments.module';
import { AuthModule } from './auth/auth.module';
import { CommentsModule } from './comments/comments.module';
import { DevicesModule } from './devices/devices.module';
import { DeviceAgentModule } from './device-agent/device-agent.module';
import { awsConfig } from './config/aws.config';
import { HealthModule } from './health/health.module';
import { OrganizationModule } from './organization/organization.module';
import { PoliciesModule } from './policies/policies.module';
import { RisksModule } from './risks/risks.module';
import { TasksModule } from './tasks/tasks.module';
import { VendorsModule } from './vendors/vendors.module';
import { ContextModule } from './context/context.module';


@Module({
imports: [
Expand All @@ -23,6 +29,12 @@ import { TasksModule } from './tasks/tasks.module';
}),
AuthModule,
OrganizationModule,
RisksModule,
VendorsModule,
ContextModule,
DevicesModule,
PoliciesModule,
DeviceAgentModule,
DevicesModule,
AttachmentsModule,
TasksModule,
Expand Down
187 changes: 187 additions & 0 deletions apps/api/src/context/context.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
UseGuards
} from '@nestjs/common';
import {
ApiBody,
ApiHeader,
ApiOperation,
ApiParam,
ApiResponse,
ApiSecurity,
ApiTags,
} from '@nestjs/swagger';
import {
AuthContext,
OrganizationId,
} from '../auth/auth-context.decorator';
import { HybridAuthGuard } from '../auth/hybrid-auth.guard';
import type { AuthContext as AuthContextType } from '../auth/types';
import { CreateContextDto } from './dto/create-context.dto';
import { UpdateContextDto } from './dto/update-context.dto';
import { ContextService } from './context.service';
import { CONTEXT_OPERATIONS } from './schemas/context-operations';
import { CONTEXT_PARAMS } from './schemas/context-params';
import { CONTEXT_BODIES } from './schemas/context-bodies';
import { GET_ALL_CONTEXT_RESPONSES } from './schemas/get-all-context.responses';
import { GET_CONTEXT_BY_ID_RESPONSES } from './schemas/get-context-by-id.responses';
import { CREATE_CONTEXT_RESPONSES } from './schemas/create-context.responses';
import { UPDATE_CONTEXT_RESPONSES } from './schemas/update-context.responses';
import { DELETE_CONTEXT_RESPONSES } from './schemas/delete-context.responses';

@ApiTags('Context')
@Controller({ path: 'context', 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 ContextController {
constructor(private readonly contextService: ContextService) {}

@Get()
@ApiOperation(CONTEXT_OPERATIONS.getAllContext)
@ApiResponse(GET_ALL_CONTEXT_RESPONSES[200])
@ApiResponse(GET_ALL_CONTEXT_RESPONSES[401])
@ApiResponse(GET_ALL_CONTEXT_RESPONSES[404])
@ApiResponse(GET_ALL_CONTEXT_RESPONSES[500])
async getAllContext(
@OrganizationId() organizationId: string,
@AuthContext() authContext: AuthContextType,
) {
const contextEntries = await this.contextService.findAllByOrganization(organizationId);

return {
data: contextEntries,
count: contextEntries.length,
authType: authContext.authType,
...(authContext.userId && authContext.userEmail && {
authenticatedUser: {
id: authContext.userId,
email: authContext.userEmail,
},
}),
};
}

@Get(':id')
@ApiOperation(CONTEXT_OPERATIONS.getContextById)
@ApiParam(CONTEXT_PARAMS.contextId)
@ApiResponse(GET_CONTEXT_BY_ID_RESPONSES[200])
@ApiResponse(GET_CONTEXT_BY_ID_RESPONSES[401])
@ApiResponse(GET_CONTEXT_BY_ID_RESPONSES[404])
@ApiResponse(GET_CONTEXT_BY_ID_RESPONSES[500])
async getContextById(
@Param('id') contextId: string,
@OrganizationId() organizationId: string,
@AuthContext() authContext: AuthContextType,
) {
const contextEntry = await this.contextService.findById(contextId, organizationId);

return {
...contextEntry,
authType: authContext.authType,
...(authContext.userId && authContext.userEmail && {
authenticatedUser: {
id: authContext.userId,
email: authContext.userEmail,
},
}),
};
}

@Post()
@ApiOperation(CONTEXT_OPERATIONS.createContext)
@ApiBody(CONTEXT_BODIES.createContext)
@ApiResponse(CREATE_CONTEXT_RESPONSES[201])
@ApiResponse(CREATE_CONTEXT_RESPONSES[400])
@ApiResponse(CREATE_CONTEXT_RESPONSES[401])
@ApiResponse(CREATE_CONTEXT_RESPONSES[404])
@ApiResponse(CREATE_CONTEXT_RESPONSES[500])
async createContext(
@Body() createContextDto: CreateContextDto,
@OrganizationId() organizationId: string,
@AuthContext() authContext: AuthContextType,
) {
const contextEntry = await this.contextService.create(organizationId, createContextDto);

return {
...contextEntry,
authType: authContext.authType,
...(authContext.userId && authContext.userEmail && {
authenticatedUser: {
id: authContext.userId,
email: authContext.userEmail,
},
}),
};
}

@Patch(':id')
@ApiOperation(CONTEXT_OPERATIONS.updateContext)
@ApiParam(CONTEXT_PARAMS.contextId)
@ApiBody(CONTEXT_BODIES.updateContext)
@ApiResponse(UPDATE_CONTEXT_RESPONSES[200])
@ApiResponse(UPDATE_CONTEXT_RESPONSES[400])
@ApiResponse(UPDATE_CONTEXT_RESPONSES[401])
@ApiResponse(UPDATE_CONTEXT_RESPONSES[404])
@ApiResponse(UPDATE_CONTEXT_RESPONSES[500])
async updateContext(
@Param('id') contextId: string,
@Body() updateContextDto: UpdateContextDto,
@OrganizationId() organizationId: string,
@AuthContext() authContext: AuthContextType,
) {
const updatedContextEntry = await this.contextService.updateById(
contextId,
organizationId,
updateContextDto,
);

return {
...updatedContextEntry,
authType: authContext.authType,
...(authContext.userId && authContext.userEmail && {
authenticatedUser: {
id: authContext.userId,
email: authContext.userEmail,
},
}),
};
}

@Delete(':id')
@ApiOperation(CONTEXT_OPERATIONS.deleteContext)
@ApiParam(CONTEXT_PARAMS.contextId)
@ApiResponse(DELETE_CONTEXT_RESPONSES[200])
@ApiResponse(DELETE_CONTEXT_RESPONSES[401])
@ApiResponse(DELETE_CONTEXT_RESPONSES[404])
@ApiResponse(DELETE_CONTEXT_RESPONSES[500])
async deleteContext(
@Param('id') contextId: string,
@OrganizationId() organizationId: string,
@AuthContext() authContext: AuthContextType,
) {
const result = await this.contextService.deleteById(contextId, organizationId);

return {
...result,
authType: authContext.authType,
...(authContext.userId && authContext.userEmail && {
authenticatedUser: {
id: authContext.userId,
email: authContext.userEmail,
},
}),
};
}
}
12 changes: 12 additions & 0 deletions apps/api/src/context/context.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module';
import { ContextController } from './context.controller';
import { ContextService } from './context.service';

@Module({
imports: [AuthModule],
controllers: [ContextController],
providers: [ContextService],
exports: [ContextService],
})
export class ContextModule {}
Loading
Loading