Skip to content

Commit 44924b2

Browse files
COMP-259 [API] - Add following endpoints for Devices (#1455)
* create /v1/devices endpoint * create /v1/devices/member/{id} endpoint * minor update --------- Co-authored-by: chasprowebdev <[email protected]>
1 parent 84ea6e9 commit 44924b2

File tree

8 files changed

+800
-0
lines changed

8 files changed

+800
-0
lines changed

apps/api/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AppService } from './app.service';
55
import { AttachmentsModule } from './attachments/attachments.module';
66
import { AuthModule } from './auth/auth.module';
77
import { CommentsModule } from './comments/comments.module';
8+
import { DevicesModule } from './devices/devices.module';
89
import { awsConfig } from './config/aws.config';
910
import { HealthModule } from './health/health.module';
1011
import { OrganizationModule } from './organization/organization.module';
@@ -22,6 +23,7 @@ import { TasksModule } from './tasks/tasks.module';
2223
}),
2324
AuthModule,
2425
OrganizationModule,
26+
DevicesModule,
2527
AttachmentsModule,
2628
TasksModule,
2729
CommentsModule,
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import {
2+
Controller,
3+
Get,
4+
Param,
5+
UseGuards
6+
} from '@nestjs/common';
7+
import {
8+
ApiHeader,
9+
ApiOperation,
10+
ApiParam,
11+
ApiResponse,
12+
ApiSecurity,
13+
ApiTags,
14+
} from '@nestjs/swagger';
15+
import {
16+
AuthContext,
17+
OrganizationId,
18+
} from '../auth/auth-context.decorator';
19+
import { HybridAuthGuard } from '../auth/hybrid-auth.guard';
20+
import type { AuthContext as AuthContextType } from '../auth/types';
21+
import { DevicesByMemberResponseDto } from './dto/devices-by-member-response.dto';
22+
import { DevicesService } from './devices.service';
23+
24+
@ApiTags('Devices')
25+
@Controller({ path: 'devices', version: '1' })
26+
@UseGuards(HybridAuthGuard)
27+
@ApiSecurity('apikey')
28+
@ApiHeader({
29+
name: 'X-Organization-Id',
30+
description:
31+
'Organization ID (required for session auth, optional for API key auth)',
32+
required: false,
33+
})
34+
export class DevicesController {
35+
constructor(private readonly devicesService: DevicesService) {}
36+
37+
@Get()
38+
@ApiOperation({
39+
summary: 'Get all devices',
40+
description:
41+
'Returns all devices for the authenticated organization from FleetDM. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).',
42+
})
43+
@ApiResponse({
44+
status: 200,
45+
description: 'Devices retrieved successfully',
46+
schema: {
47+
type: 'object',
48+
properties: {
49+
data: {
50+
type: 'array',
51+
items: { $ref: '#/components/schemas/DeviceResponseDto' },
52+
},
53+
count: {
54+
type: 'number',
55+
description: 'Total number of devices',
56+
example: 25,
57+
},
58+
authType: {
59+
type: 'string',
60+
enum: ['api-key', 'session'],
61+
description: 'How the request was authenticated',
62+
},
63+
authenticatedUser: {
64+
type: 'object',
65+
properties: {
66+
id: {
67+
type: 'string',
68+
description: 'User ID',
69+
example: 'usr_abc123def456',
70+
},
71+
email: {
72+
type: 'string',
73+
description: 'User email',
74+
example: '[email protected]',
75+
},
76+
},
77+
},
78+
},
79+
},
80+
})
81+
@ApiResponse({
82+
status: 401,
83+
description:
84+
'Unauthorized - Invalid authentication or insufficient permissions',
85+
schema: {
86+
type: 'object',
87+
properties: {
88+
message: {
89+
type: 'string',
90+
examples: [
91+
'Invalid or expired API key',
92+
'Invalid or expired session',
93+
'User does not have access to organization',
94+
'Organization context required',
95+
],
96+
},
97+
},
98+
},
99+
})
100+
@ApiResponse({
101+
status: 404,
102+
description: 'Organization not found',
103+
schema: {
104+
type: 'object',
105+
properties: {
106+
message: {
107+
type: 'string',
108+
example: 'Organization with ID org_abc123def456 not found',
109+
},
110+
},
111+
},
112+
})
113+
@ApiResponse({
114+
status: 500,
115+
description: 'Internal server error - FleetDM integration issue',
116+
schema: {
117+
type: 'object',
118+
properties: {
119+
message: {
120+
type: 'string',
121+
examples: [
122+
'Failed to retrieve devices: FleetDM connection failed',
123+
'Organization does not have FleetDM configured',
124+
],
125+
},
126+
},
127+
},
128+
})
129+
async getAllDevices(
130+
@OrganizationId() organizationId: string,
131+
@AuthContext() authContext: AuthContextType,
132+
) {
133+
const devices = await this.devicesService.findAllByOrganization(organizationId);
134+
135+
return {
136+
data: devices,
137+
count: devices.length,
138+
authType: authContext.authType,
139+
...(authContext.userId && authContext.userEmail && {
140+
authenticatedUser: {
141+
id: authContext.userId,
142+
email: authContext.userEmail,
143+
},
144+
}),
145+
};
146+
}
147+
148+
@Get('member/:memberId')
149+
@ApiOperation({
150+
summary: 'Get devices by member ID',
151+
description:
152+
'Returns all devices assigned to a specific member within the authenticated organization. Devices are fetched from FleetDM using the member\'s dedicated fleetDmLabelId. Supports both API key authentication (X-API-Key header) and session authentication (cookies + X-Organization-Id header).',
153+
})
154+
@ApiParam({
155+
name: 'memberId',
156+
description: 'Member ID to get devices for',
157+
example: 'mem_abc123def456',
158+
})
159+
@ApiResponse({
160+
status: 200,
161+
description: 'Member devices retrieved successfully',
162+
type: DevicesByMemberResponseDto,
163+
})
164+
@ApiResponse({
165+
status: 401,
166+
description:
167+
'Unauthorized - Invalid authentication or insufficient permissions',
168+
})
169+
@ApiResponse({
170+
status: 404,
171+
description: 'Organization or member not found',
172+
schema: {
173+
type: 'object',
174+
properties: {
175+
message: {
176+
type: 'string',
177+
examples: [
178+
'Organization with ID org_abc123def456 not found',
179+
'Member with ID mem_abc123def456 not found in organization org_abc123def456',
180+
],
181+
},
182+
},
183+
},
184+
})
185+
@ApiResponse({
186+
status: 500,
187+
description: 'Internal server error - FleetDM integration issue',
188+
})
189+
async getDevicesByMember(
190+
@Param('memberId') memberId: string,
191+
@OrganizationId() organizationId: string,
192+
@AuthContext() authContext: AuthContextType,
193+
): Promise<DevicesByMemberResponseDto> {
194+
const [devices, member] = await Promise.all([
195+
this.devicesService.findAllByMember(organizationId, memberId),
196+
this.devicesService.getMemberById(organizationId, memberId),
197+
]);
198+
199+
return {
200+
data: devices,
201+
count: devices.length,
202+
member,
203+
authType: authContext.authType,
204+
...(authContext.userId && authContext.userEmail && {
205+
authenticatedUser: {
206+
id: authContext.userId,
207+
email: authContext.userEmail,
208+
},
209+
}),
210+
};
211+
}
212+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { AuthModule } from '../auth/auth.module';
3+
import { FleetService } from '../lib/fleet.service';
4+
import { DevicesController } from './devices.controller';
5+
import { DevicesService } from './devices.service';
6+
7+
@Module({
8+
imports: [AuthModule],
9+
controllers: [DevicesController],
10+
providers: [DevicesService, FleetService],
11+
exports: [DevicesService, FleetService],
12+
})
13+
export class DevicesModule {}

0 commit comments

Comments
 (0)