Skip to content

Commit 1712189

Browse files
github-actions[bot]chasprowebdevMarfuen
authored
[FIX] Update Endpoints (#1896)
* fix(api): update response and docs for policy & risks endpoints * fix(api): add API key auth support with userId parameter for comments API * fix(api): add API key auth support with userId parameter for transfer-ownership and task/attachments endpoints * fix(api): move userId to request body for comment deletion endpoint * fix(api): don't expose sensitive fields of user model from /risks endpoint * fix(api): update error description of transfer-ownership api --------- Co-authored-by: chasprowebdev <[email protected]> Co-authored-by: Mariano Fuentes <[email protected]>
1 parent e59dc63 commit 1712189

File tree

12 files changed

+381
-66
lines changed

12 files changed

+381
-66
lines changed

apps/api/src/attachments/upload-attachment.dto.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,15 @@ export class UploadAttachmentDto {
6969
@IsString()
7070
@MaxLength(500)
7171
description?: string;
72+
73+
@ApiProperty({
74+
description:
75+
'User ID of the user uploading the attachment (required for API key auth, ignored for JWT auth)',
76+
example: 'usr_abc123def456',
77+
required: false,
78+
})
79+
@IsOptional()
80+
@IsString()
81+
@IsNotEmpty()
82+
userId?: string;
7283
}

apps/api/src/comments/comments.controller.ts

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ import {
55
Controller,
66
Delete,
77
Get,
8-
HttpCode,
9-
HttpStatus,
108
Param,
119
Post,
1210
Put,
1311
Query,
1412
UseGuards,
1513
} from '@nestjs/common';
1614
import {
15+
ApiBody,
1716
ApiHeader,
1817
ApiOperation,
1918
ApiParam,
@@ -28,6 +27,7 @@ import type { AuthContext as AuthContextType } from '../auth/types';
2827
import { CommentsService } from './comments.service';
2928
import { CommentResponseDto } from './dto/comment-responses.dto';
3029
import { CreateCommentDto } from './dto/create-comment.dto';
30+
import { DeleteCommentDto } from './dto/delete-comment.dto';
3131
import { UpdateCommentDto } from './dto/update-comment.dto';
3232

3333
@ApiTags('Comments')
@@ -92,13 +92,28 @@ export class CommentsController {
9292
@AuthContext() authContext: AuthContextType,
9393
@Body() createCommentDto: CreateCommentDto,
9494
): Promise<CommentResponseDto> {
95-
if (!authContext.userId) {
96-
throw new BadRequestException('User ID is required');
95+
// For API key auth, userId must be provided in the request body
96+
// For JWT auth, userId comes from the authenticated session
97+
let userId: string;
98+
if (authContext.isApiKey) {
99+
// For API key auth, userId must be provided in the DTO
100+
if (!createCommentDto.userId) {
101+
throw new BadRequestException(
102+
'User ID is required when using API key authentication. Provide userId in the request body.',
103+
);
104+
}
105+
userId = createCommentDto.userId;
106+
} else {
107+
// For JWT auth, use the authenticated user's ID
108+
if (!authContext.userId) {
109+
throw new BadRequestException('User ID is required');
110+
}
111+
userId = authContext.userId;
97112
}
98113

99114
return await this.commentsService.createComment(
100115
organizationId,
101-
authContext.userId,
116+
userId,
102117
createCommentDto,
103118
);
104119
}
@@ -124,14 +139,29 @@ export class CommentsController {
124139
@Param('commentId') commentId: string,
125140
@Body() updateCommentDto: UpdateCommentDto,
126141
): Promise<CommentResponseDto> {
127-
if (!authContext.userId) {
128-
throw new BadRequestException('User ID is required');
142+
// For API key auth, userId must be provided in the request body
143+
// For JWT auth, userId comes from the authenticated session
144+
let userId: string;
145+
if (authContext.isApiKey) {
146+
// For API key auth, userId must be provided in the DTO
147+
if (!updateCommentDto.userId) {
148+
throw new BadRequestException(
149+
'User ID is required when using API key authentication. Provide userId in the request body.',
150+
);
151+
}
152+
userId = updateCommentDto.userId;
153+
} else {
154+
// For JWT auth, use the authenticated user's ID
155+
if (!authContext.userId) {
156+
throw new BadRequestException('User ID is required');
157+
}
158+
userId = authContext.userId;
129159
}
130160

131161
return await this.commentsService.updateComment(
132162
organizationId,
133163
commentId,
134-
authContext.userId,
164+
userId,
135165
updateCommentDto.content,
136166
);
137167
}
@@ -146,6 +176,20 @@ export class CommentsController {
146176
description: 'Unique comment identifier',
147177
example: 'cmt_abc123def456',
148178
})
179+
@ApiBody({
180+
description: 'Delete comment request body',
181+
schema: {
182+
type: 'object',
183+
properties: {
184+
userId: {
185+
type: 'string',
186+
description:
187+
'User ID of the comment author (required for API key auth, ignored for JWT auth)',
188+
example: 'usr_abc123def456',
189+
},
190+
},
191+
},
192+
})
149193
@ApiResponse({
150194
status: 200,
151195
description: 'Comment deleted successfully',
@@ -198,15 +242,31 @@ export class CommentsController {
198242
@OrganizationId() organizationId: string,
199243
@AuthContext() authContext: AuthContextType,
200244
@Param('commentId') commentId: string,
245+
@Body() deleteDto: DeleteCommentDto,
201246
): Promise<{ success: boolean; deletedCommentId: string; message: string }> {
202-
if (!authContext.userId) {
203-
throw new BadRequestException('User ID is required');
247+
// For API key auth, userId must be provided in the request body
248+
// For JWT auth, userId comes from the authenticated session
249+
let userId: string;
250+
if (authContext.isApiKey) {
251+
// For API key auth, userId must be provided in the request body
252+
if (!deleteDto.userId) {
253+
throw new BadRequestException(
254+
'User ID is required when using API key authentication. Provide userId in the request body.',
255+
);
256+
}
257+
userId = deleteDto.userId;
258+
} else {
259+
// For JWT auth, use the authenticated user's ID
260+
if (!authContext.userId) {
261+
throw new BadRequestException('User ID is required');
262+
}
263+
userId = authContext.userId;
204264
}
205265

206266
await this.commentsService.deleteComment(
207267
organizationId,
208268
commentId,
209-
authContext.userId,
269+
userId,
210270
);
211271

212272
return {

apps/api/src/comments/dto/create-comment.dto.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,15 @@ export class CreateCommentDto {
4949
@ValidateNested({ each: true })
5050
@Type(() => UploadAttachmentDto)
5151
attachments?: UploadAttachmentDto[];
52+
53+
@ApiProperty({
54+
description:
55+
'User ID of the comment author (required for API key auth, ignored for JWT auth)',
56+
example: 'usr_abc123def456',
57+
required: false,
58+
})
59+
@IsOptional()
60+
@IsString()
61+
@IsNotEmpty()
62+
userId?: string;
5263
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
3+
4+
export class DeleteCommentDto {
5+
@ApiProperty({
6+
description:
7+
'User ID of the comment author (required for API key auth, ignored for JWT auth)',
8+
example: 'usr_abc123def456',
9+
required: false,
10+
})
11+
@IsOptional()
12+
@IsString()
13+
@IsNotEmpty()
14+
userId?: string;
15+
}

apps/api/src/comments/dto/update-comment.dto.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
2+
import { IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator';
33

44
export class UpdateCommentDto {
55
@ApiProperty({
@@ -11,4 +11,15 @@ export class UpdateCommentDto {
1111
@IsNotEmpty()
1212
@MaxLength(2000)
1313
content: string;
14+
15+
@ApiProperty({
16+
description:
17+
'User ID of the comment author (required for API key auth, ignored for JWT auth)',
18+
example: 'usr_abc123def456',
19+
required: false,
20+
})
21+
@IsOptional()
22+
@IsString()
23+
@IsNotEmpty()
24+
userId?: string;
1425
}

apps/api/src/organization/dto/transfer-ownership.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
export interface TransferOwnershipDto {
22
newOwnerId: string;
3+
/**
4+
* User ID of the current owner initiating the transfer
5+
* Required for API key auth, ignored for JWT auth
6+
*/
7+
userId?: string;
38
}
49

510
export interface TransferOwnershipResponseDto {

apps/api/src/organization/organization.controller.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,25 +119,40 @@ export class OrganizationController {
119119
@AuthContext() authContext: AuthContextType,
120120
@Body() transferData: TransferOwnershipDto,
121121
) {
122-
if (!authContext.userId) {
123-
throw new BadRequestException(
124-
'User ID is required for this operation. This endpoint requires session authentication.',
125-
);
122+
// For API key auth, userId must be provided in the request body
123+
// For JWT auth, userId comes from the authenticated session
124+
let userId: string;
125+
if (authContext.isApiKey) {
126+
// For API key auth, userId must be provided in the DTO
127+
if (!transferData.userId) {
128+
throw new BadRequestException(
129+
'User ID is required when using API key authentication. Provide userId in the request body.',
130+
);
131+
}
132+
userId = transferData.userId;
133+
} else {
134+
// For JWT auth, use the authenticated user's ID
135+
if (!authContext.userId) {
136+
throw new BadRequestException(
137+
'User ID is required for this operation. This endpoint requires session authentication.',
138+
);
139+
}
140+
userId = authContext.userId;
126141
}
127142

128143
const result = await this.organizationService.transferOwnership(
129144
organizationId,
130-
authContext.userId,
145+
userId,
131146
transferData.newOwnerId,
132147
);
133148

134149
return {
135150
...result,
136151
authType: authContext.authType,
137-
// Include user context for session auth (helpful for debugging)
152+
// Include user context (helpful for debugging)
138153
authenticatedUser: {
139-
id: authContext.userId,
140-
email: authContext.userEmail,
154+
id: userId,
155+
...(authContext.userEmail && { email: authContext.userEmail }),
141156
},
142157
};
143158
}

apps/api/src/organization/schemas/organization-api-bodies.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ export const TRANSFER_OWNERSHIP_BODY: ApiBodyOptions = {
7171
description: 'Member ID of the new owner',
7272
example: 'mem_xyz789',
7373
},
74+
userId: {
75+
type: 'string',
76+
description:
77+
'User ID of the current owner initiating the transfer (required for API key auth, ignored for JWT auth)',
78+
example: 'usr_abc123def456',
79+
},
7480
},
7581
additionalProperties: false,
7682
},

apps/api/src/policies/schemas/get-all-policies.responses.ts

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,78 @@ export const GET_ALL_POLICIES_RESPONSES: Record<string, ApiResponseOptions> = {
77
content: {
88
'application/json': {
99
schema: {
10-
type: 'array',
11-
items: { $ref: '#/components/schemas/PolicyResponseDto' },
10+
type: 'object',
11+
properties: {
12+
data: {
13+
type: 'array',
14+
items: { $ref: '#/components/schemas/PolicyResponseDto' },
15+
description: 'Array of policies',
16+
},
17+
authType: {
18+
type: 'string',
19+
enum: ['api-key', 'session'],
20+
description: 'How the request was authenticated',
21+
},
22+
authenticatedUser: {
23+
type: 'object',
24+
description: 'Authenticated user information (only present for session auth)',
25+
properties: {
26+
id: {
27+
type: 'string',
28+
description: 'User ID',
29+
example: 'usr_abc123def456',
30+
},
31+
email: {
32+
type: 'string',
33+
description: 'User email',
34+
example: '[email protected]',
35+
},
36+
},
37+
},
38+
},
39+
required: ['data', 'authType'],
1240
},
13-
example: [
14-
{
15-
id: 'pol_abc123def456',
16-
name: 'Data Privacy Policy',
17-
status: 'draft',
18-
content: [
19-
{ type: 'paragraph', content: [{ type: 'text', text: '...' }] },
20-
],
21-
isRequiredToSign: true,
22-
signedBy: [],
23-
createdAt: '2024-01-01T00:00:00.000Z',
24-
updatedAt: '2024-01-15T00:00:00.000Z',
25-
organizationId: 'org_abc123def456',
41+
example: {
42+
data: [
43+
{
44+
id: 'pol_abc123def456',
45+
name: 'Data Privacy Policy',
46+
description: 'This policy outlines how we handle and protect personal data',
47+
status: 'draft',
48+
content: [
49+
{
50+
type: 'paragraph',
51+
attrs: { textAlign: null },
52+
content: [
53+
{
54+
type: 'text',
55+
text: 'This policy outlines our commitment to protecting personal data.',
56+
},
57+
],
58+
},
59+
],
60+
frequency: 'yearly',
61+
department: 'IT',
62+
isRequiredToSign: true,
63+
signedBy: [],
64+
reviewDate: '2024-12-31T00:00:00.000Z',
65+
isArchived: false,
66+
createdAt: '2024-01-01T00:00:00.000Z',
67+
updatedAt: '2024-01-15T00:00:00.000Z',
68+
lastArchivedAt: null,
69+
lastPublishedAt: '2024-01-10T00:00:00.000Z',
70+
organizationId: 'org_abc123def456',
71+
assigneeId: 'usr_abc123def456',
72+
approverId: 'usr_xyz789abc123',
73+
policyTemplateId: null,
74+
},
75+
],
76+
authType: 'session',
77+
authenticatedUser: {
78+
id: 'usr_abc123def456',
79+
2680
},
27-
],
81+
},
2882
},
2983
},
3084
},

0 commit comments

Comments
 (0)