Skip to content
Merged
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
4 changes: 4 additions & 0 deletions apps/api/Dockerfile.multistage
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ COPY packages/utils/package.json ./packages/utils/
COPY packages/integration-platform/package.json ./packages/integration-platform/
COPY packages/tsconfig/package.json ./packages/tsconfig/
COPY packages/email/package.json ./packages/email/
COPY packages/company/package.json ./packages/company/

# Copy API package.json
COPY apps/api/package.json ./apps/api/
Expand All @@ -34,6 +35,7 @@ COPY packages/utils ./packages/utils
COPY packages/integration-platform ./packages/integration-platform
COPY packages/tsconfig ./packages/tsconfig
COPY packages/email ./packages/email
COPY packages/company ./packages/company

# Copy API source
COPY apps/api ./apps/api
Expand All @@ -45,6 +47,7 @@ COPY --from=deps /app/node_modules ./node_modules
RUN cd packages/db && bun run build && cd ../..
RUN cd packages/integration-platform && bun run build && cd ../..
RUN cd packages/email && bun run build && cd ../..
RUN cd packages/company && bun run build && cd ../..

# Generate Prisma client for API (copy schema and generate)
RUN cd packages/db && node scripts/combine-schemas.js && cd ../..
Expand Down Expand Up @@ -79,6 +82,7 @@ COPY --from=builder /app/packages/utils ./packages/utils
COPY --from=builder /app/packages/integration-platform ./packages/integration-platform
COPY --from=builder /app/packages/tsconfig ./packages/tsconfig
COPY --from=builder /app/packages/email ./packages/email
COPY --from=builder /app/packages/company ./packages/company

# Copy production node_modules (includes symlinks to workspace packages above)
COPY --from=builder /app/node_modules ./node_modules
Expand Down
260 changes: 128 additions & 132 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,135 +1,131 @@
{
"name": "@comp/api",
"description": "",
"version": "0.0.1",
"author": "",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.53",
"@ai-sdk/groq": "^2.0.32",
"@ai-sdk/openai": "^2.0.65",
"@aws-sdk/client-s3": "^3.859.0",
"@aws-sdk/client-securityhub": "^3.948.0",
"@aws-sdk/client-sts": "^3.948.0",
"@aws-sdk/s3-request-presigner": "^3.859.0",
"@browserbasehq/sdk": "^2.6.0",
"@browserbasehq/stagehand": "^3.0.5",
"@comp/integration-platform": "workspace:*",
"@mendable/firecrawl-js": "^4.9.3",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.1.5",
"@nestjs/swagger": "^11.2.0",
"@nestjs/throttler": "^6.5.0",
"@prisma/client": "6.18.0",
"@prisma/instrumentation": "^6.13.0",
"@react-email/components": "^0.0.41",
"@trigger.dev/build": "4.0.6",
"@trigger.dev/sdk": "4.0.6",
"@trycompai/db": "1.3.22",
"@trycompai/utils": "1.0.0",
"@trycompai/email": "workspace:*",
"@upstash/redis": "^1.34.2",
"@upstash/vector": "^1.2.2",
"adm-zip": "^0.5.16",
"ai": "^5.0.60",
"archiver": "^7.0.1",
"axios": "^1.12.2",
"better-auth": "^1.3.27",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"dotenv": "^17.2.3",
"esbuild": "^0.27.1",
"exceljs": "^4.4.0",
"express": "^4.21.2",
"helmet": "^8.1.0",
"jose": "^6.0.12",
"jspdf": "^3.0.3",
"mammoth": "^1.8.0",
"nanoid": "^5.1.6",
"pdf-lib": "^1.17.1",
"playwright-core": "^1.57.0",
"prisma": "6.18.0",
"react": "^19.1.1",
"react-dom": "^19.1.0",
"reflect-metadata": "^0.2.2",
"resend": "^6.4.2",
"rxjs": "^7.8.1",
"safe-stable-stringify": "^2.5.0",
"swagger-ui-express": "^5.0.1",
"xlsx": "^0.18.5",
"zod": "^4.0.14"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/adm-zip": "^0.5.7",
"@types/archiver": "^6.0.3",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/multer": "^1.4.12",
"@types/node": "^24.0.3",
"@types/supertest": "^6.0.2",
"@types/swagger-ui-express": "^4.1.8",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"prettier": "^3.5.3",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
"name": "@comp/api",
"description": "",
"version": "0.0.1",
"author": "",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.53",
"@ai-sdk/groq": "^2.0.32",
"@ai-sdk/openai": "^2.0.65",
"@aws-sdk/client-s3": "^3.859.0",
"@aws-sdk/client-securityhub": "^3.948.0",
"@aws-sdk/client-sts": "^3.948.0",
"@aws-sdk/s3-request-presigner": "^3.859.0",
"@browserbasehq/sdk": "^2.6.0",
"@browserbasehq/stagehand": "^3.0.5",
"@comp/company": "workspace:*",
"@comp/integration-platform": "workspace:*",
"@mendable/firecrawl-js": "^4.9.3",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.1.5",
"@nestjs/swagger": "^11.2.0",
"@nestjs/throttler": "^6.5.0",
"@prisma/client": "6.18.0",
"@prisma/instrumentation": "^6.13.0",
"@react-email/components": "^0.0.41",
"@trigger.dev/build": "4.0.6",
"@trigger.dev/sdk": "4.0.6",
"@trycompai/db": "1.3.22",
"@trycompai/email": "workspace:*",
"@upstash/redis": "^1.34.2",
"@upstash/vector": "^1.2.2",
"adm-zip": "^0.5.16",
"ai": "^5.0.60",
"archiver": "^7.0.1",
"axios": "^1.12.2",
"better-auth": "^1.3.27",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"dotenv": "^17.2.3",
"esbuild": "^0.27.1",
"exceljs": "^4.4.0",
"express": "^4.21.2",
"helmet": "^8.1.0",
"jose": "^6.0.12",
"jspdf": "^3.0.3",
"mammoth": "^1.8.0",
"nanoid": "^5.1.6",
"pdf-lib": "^1.17.1",
"playwright-core": "^1.57.0",
"prisma": "6.18.0",
"react": "^19.1.1",
"react-dom": "^19.1.0",
"reflect-metadata": "^0.2.2",
"resend": "^6.4.2",
"rxjs": "^7.8.1",
"safe-stable-stringify": "^2.5.0",
"swagger-ui-express": "^5.0.1",
"xlsx": "^0.18.5",
"zod": "^4.0.14"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"license": "UNLICENSED",
"private": true,
"scripts": {
"build": "nest build",
"build:docker": "bunx prisma generate && nest build",
"db:generate": "bun run db:getschema && bunx prisma generate",
"db:getschema": "node ../../packages/db/scripts/combine-schemas.js && cp ../../packages/db/dist/schema.prisma prisma/schema.prisma",
"db:migrate": "cd ../../packages/db && bunx prisma migrate dev && cd ../../apps/api",
"deploy:trigger-prod": "npx trigger.dev@4.0.6 deploy",
"dev": "bunx concurrently --kill-others --names \"nest,trigger\" --prefix-colors \"green,blue\" \"nest start --watch\" \"bunx trigger.dev@4.0.6 dev\"",
"dev:nest": "nest start --watch",
"dev:trigger": "bunx trigger.dev@4.0.6 dev",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"prebuild": "bun run db:generate",
"start": "nest start",
"start:debug": "nest start --debug --watch",
"start:dev": "nest start --watch",
"start:prod": "node dist/main",
"test": "jest",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"test:watch": "jest --watch",
"typecheck": "tsc --noEmit"
}
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/adm-zip": "^0.5.7",
"@types/archiver": "^6.0.3",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/multer": "^1.4.12",
"@types/node": "^24.0.3",
"@types/supertest": "^6.0.2",
"@types/swagger-ui-express": "^4.1.8",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"prettier": "^3.5.3",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js", "json", "ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": ["**/*.(t|j)s"],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"license": "UNLICENSED",
"private": true,
"scripts": {
"build": "nest build",
"build:docker": "bunx prisma generate && nest build",
"db:generate": "bun run db:getschema && bunx prisma generate",
"db:getschema": "node ../../packages/db/scripts/combine-schemas.js && cp ../../packages/db/dist/schema.prisma prisma/schema.prisma",
"db:migrate": "cd ../../packages/db && bunx prisma migrate dev && cd ../../apps/api",
"deploy:trigger-prod": "npx trigger.dev@4.0.6 deploy",
"dev": "bunx concurrently --kill-others --names \"nest,trigger\" --prefix-colors \"green,blue\" \"nest start --watch\" \"bunx trigger.dev@4.0.6 dev\"",
"dev:nest": "nest start --watch",
"dev:trigger": "bunx trigger.dev@4.0.6 dev",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"prebuild": "bun run db:generate",
"start": "nest start",
"start:debug": "nest start --debug --watch",
"start:dev": "nest start --watch",
"start:prod": "node dist/main",
"test": "jest",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"test:watch": "jest --watch",
"typecheck": "tsc --noEmit"
}
}
2 changes: 2 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { TaskManagementModule } from './task-management/task-management.module';
import { AssistantChatModule } from './assistant-chat/assistant-chat.module';
import { OrgChartModule } from './org-chart/org-chart.module';
import { TrainingModule } from './training/training.module';
import { EvidenceFormsModule } from './evidence-forms/evidence-forms.module';

@Module({
imports: [
Expand Down Expand Up @@ -82,6 +83,7 @@ import { TrainingModule } from './training/training.module';
AssistantChatModule,
TrainingModule,
OrgChartModule,
EvidenceFormsModule,
],
controllers: [AppController],
providers: [
Expand Down
34 changes: 33 additions & 1 deletion apps/api/src/devices/devices.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
import { Injectable, NotFoundException, Logger } from '@nestjs/common';
import { db } from '@trycompai/db';
import { mergeDeviceLists } from '@trycompai/utils/devices';
import { FleetService } from '../lib/fleet.service';
import { DeviceResponseDto } from './dto/device-responses.dto';
import type { MemberResponseDto } from './dto/member-responses.dto';

/**
* Merges two device lists, deduplicating by serial number and hostname.
* Priority devices (first argument) take precedence over secondary devices.
*/
function mergeDeviceLists<T>(
priorityDevices: T[],
secondaryDevices: T[],
accessors: {
getSerialNumber: (device: T) => string | null | undefined;
getHostname: (device: T) => string | null | undefined;
},
): T[] {
const knownSerials = new Set<string>();
const knownHostnames = new Set<string>();

for (const device of priorityDevices) {
const serial = accessors.getSerialNumber(device);
const hostname = accessors.getHostname(device);
if (serial) knownSerials.add(serial.toLowerCase());
if (hostname) knownHostnames.add(hostname.toLowerCase());
}

const uniqueSecondaryDevices = secondaryDevices.filter((device) => {
const serial = accessors.getSerialNumber(device);
const hostname = accessors.getHostname(device);
if (serial && knownSerials.has(serial.toLowerCase())) return false;
if (hostname && knownHostnames.has(hostname.toLowerCase())) return false;
return true;
});

return [...priorityDevices, ...uniqueSecondaryDevices];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate device merge utility added

Low Severity

mergeDeviceLists was reimplemented in apps/api/src/devices/devices.service.ts even though the same logic already exists in packages/utils/src/devices.ts. This creates duplicated behavior that can drift over time and makes future fixes easy to miss in one path.

Fix in Cursor Fix in Web


/**
* Hybrid device service that fetches from both FleetDM and the Device Agent database.
* FleetDM is the legacy system; Device Agent is the new system.
Expand Down
Loading
Loading