Skip to content

Commit 3614ac5

Browse files
authored
Apply the new permissions to the UE, comments and annals routes (#63)
All `@RequireUserType` which were used in a permission check context were replaced by `@RequireApiPermission` There are 2 new api permissions: `API_GIVE_OPINIONS_UE` and `API_SEE_ANNALS`. Some api permissions were renamed: * `API_UPLOAD_ANNAL` -> `API_UPLOAD_ANNALS` * `API_MODERATE_ANNAL` -> `API_MODERATE_ANNALS`
1 parent 691a301 commit 3614ac5

32 files changed

+449
-217
lines changed

prisma/schema.prisma

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -898,11 +898,13 @@ enum AddressPrivacy {
898898
}
899899

900900
enum Permission {
901-
API_SEE_OPINIONS_UE
902-
API_UPLOAD_ANNAL
903-
API_MODERATE_ANNAL
904-
API_MODERATE_COMMENTS
905-
906-
USER_SEE_DETAILS
907-
USER_UPDATE_DETAILS
901+
API_SEE_OPINIONS_UE // See the rates of an UE
902+
API_GIVE_OPINIONS_UE // Rate an UE you have done or are doing
903+
API_SEE_ANNALS // See and download annals
904+
API_UPLOAD_ANNALS // Upload an annal
905+
API_MODERATE_ANNALS // Moderate annals
906+
API_MODERATE_COMMENTS // Moderate comments
907+
908+
USER_SEE_DETAILS // See personal details about someone, even the ones the user decided to hide
909+
USER_UPDATE_DETAILS // Update personal details about someone
908910
}

prisma/seed/modules/ueSubscription.seed.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default function ueSubscriptionSeed(
1111
): Promise<RawUserUeSubscription[]> {
1212
console.log('Seeding UE subscriptions...');
1313
const subscriptions: Promise<RawUserUeSubscription>[] = [];
14-
/*for (const user of users) {
14+
for (const user of users) {
1515
const subscribedToUes = faker.helpers.arrayElements(ues, faker.datatype.number({ min: 1, max: 10 }));
1616
for (const ue of subscribedToUes) {
1717
subscriptions.push(
@@ -24,7 +24,7 @@ export default function ueSubscriptionSeed(
2424
}),
2525
);
2626
}
27-
}*/
27+
}
2828
console.log('WARNING: no ue subscriptions seeded !!');
2929
return Promise.all(subscriptions);
3030
}

src/auth/guard/role.guard.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ export class RoleGuard implements CanActivate {
1818
// If the user has one of the needed permissions, serve the request
1919
for (const requiredType of requiredTypes) if (user.userType === requiredType) return true;
2020
// The user has none of the required permissions, throw an error
21-
throw new AppException(ERROR_CODE.FORBIDDEN_INVALID_ROLE, requiredTypes[0]);
21+
throw new AppException(ERROR_CODE.FORBIDDEN_INVALID_ROLE, requiredTypes.join(','));
2222
}
2323
}

src/exceptions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export const enum ERROR_CODE {
7474
NO_SUCH_ASSO = 4410,
7575
NO_SUCH_UEOF = 4411,
7676
NO_SUCH_APPLICATION = 4412,
77+
NO_SUCH_UE_AT_SEMESTER = 4413,
7778
ANNAL_ALREADY_UPLOADED = 4901,
7879
RESOURCE_UNAVAILABLE = 4902,
7980
RESOURCE_INVALID_TYPE = 4903,
@@ -224,7 +225,7 @@ export const ErrorData = Object.freeze({
224225
httpCode: HttpStatus.UNAUTHORIZED,
225226
},
226227
[ERROR_CODE.FORBIDDEN_INVALID_ROLE]: {
227-
message: 'Role % is required to access this resource',
228+
message: 'One of the following roles is required to access this resource: %',
228229
httpCode: HttpStatus.UNAUTHORIZED,
229230
},
230231
[ERROR_CODE.INVALID_CAS_TICKET]: {
@@ -331,6 +332,10 @@ export const ErrorData = Object.freeze({
331332
message: 'The application % does not exist',
332333
httpCode: HttpStatus.NOT_FOUND,
333334
},
335+
[ERROR_CODE.NO_SUCH_UE_AT_SEMESTER]: {
336+
message: 'UE % does not exist for semester %',
337+
httpCode: HttpStatus.NOT_FOUND,
338+
},
334339
[ERROR_CODE.ANNAL_ALREADY_UPLOADED]: {
335340
message: 'A file has alreay been uploaded for this annal',
336341
httpCode: HttpStatus.CONFLICT,

src/ue/annals/annals.controller.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AnnalsService } from './annals.service';
33
import { UeService } from '../ue.service';
44
import { Response as ExpressResponse } from 'express';
55
import { UUIDParam } from '../../app.pipe';
6-
import { GetUser, RequireUserType } from '../../auth/decorator';
6+
import { GetUser, RequireApiPermission } from '../../auth/decorator';
77
import { AppException, ERROR_CODE } from '../../exceptions';
88
import { FileSize, MulterWithMime, UploadRoute, UserFile } from '../../upload.interceptor';
99
import { CommentStatus } from '../comments/interfaces/comment.interface';
@@ -26,7 +26,7 @@ export class AnnalsController {
2626
constructor(readonly annalsService: AnnalsService, readonly ueService: UeService) {}
2727

2828
@Get()
29-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
29+
@RequireApiPermission('API_SEE_ANNALS')
3030
@ApiOperation({ description: 'Get the list of annals of a UE.' })
3131
@ApiOkResponse({ type: UeAnnalResDto, isArray: true })
3232
@ApiAppErrorResponse(ERROR_CODE.NO_SUCH_UE, 'Thrown when there is no UE with code `ueCode`.')
@@ -36,14 +36,14 @@ export class AnnalsController {
3636
@GetPermissions() permissions: PermissionManager,
3737
): Promise<UeAnnalResDto[]> {
3838
if (!(await this.ueService.doesUeExist(ueCode))) throw new AppException(ERROR_CODE.NO_SUCH_UE, ueCode);
39-
return this.annalsService.getUeAnnalsList(user, ueCode, permissions.can(Permission.API_MODERATE_ANNAL));
39+
return this.annalsService.getUeAnnalsList(user, ueCode, permissions.can(Permission.API_MODERATE_ANNALS));
4040
}
4141

4242
@Post()
43-
@RequireUserType('STUDENT')
43+
@RequireApiPermission('API_UPLOAD_ANNALS')
4444
@ApiOperation({
4545
description:
46-
'Create an annal. User must have done the UE, or have the permission `annalUploader`. Metadata of the annal will be created, but the file will not actually exist. To upload the file, see `PUT /v1/ue/annals/:annalId`.',
46+
'Create an annal. User must have done the UE, or have the permission `API_MODERATE_ANNALS`. Metadata of the annal will be created, but the file will not actually exist. To upload the file, see `PUT /v1/ue/annals/:annalId`.',
4747
})
4848
@ApiOkResponse({ type: UeAnnalResDto })
4949
@ApiAppErrorResponse(ERROR_CODE.NO_SUCH_UE, 'Thrown when there is no UE with code `ueCode`.')
@@ -53,28 +53,30 @@ export class AnnalsController {
5353
)
5454
@ApiAppErrorResponse(
5555
ERROR_CODE.NOT_DONE_UE_IN_SEMESTER,
56-
'User has not done the UE and is not an `annalUploader`, and thus cannot upload an annal for this UE.',
56+
'User has not done the UE and is not an `API_MODERATE_ANNALS`, and thus cannot upload an annal for this UE.',
5757
)
5858
async createUeAnnal(
5959
@Body() { ueCode, semester, typeId, ueof }: CreateAnnalReqDto,
6060
@GetUser() user: User,
6161
@GetPermissions() permissions: PermissionManager,
6262
): Promise<UeAnnalResDto> {
63-
if (ueof && permissions.can(Permission.API_UPLOAD_ANNAL))
64-
throw new AppException(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, Permission.API_UPLOAD_ANNAL);
63+
if (ueof && !permissions.can(Permission.API_MODERATE_ANNALS))
64+
throw new AppException(ERROR_CODE.FORBIDDEN_NOT_ENOUGH_API_PERMISSIONS, Permission.API_MODERATE_ANNALS);
6565
if (!ueof && !(await this.ueService.doesUeExist(ueCode))) throw new AppException(ERROR_CODE.NO_SUCH_UE, ueCode);
6666
if (ueof && !(await this.ueService.doesUeofExist(ueof))) throw new AppException(ERROR_CODE.NO_SUCH_UEOF, ueof);
6767
if (!(await this.annalsService.doesAnnalTypeExist(typeId))) throw new AppException(ERROR_CODE.NO_SUCH_ANNAL_TYPE);
6868
if (
6969
!(await this.ueService.hasUserAttended(ueCode, user.id, semester)) &&
70-
!permissions.can(Permission.API_UPLOAD_ANNAL)
70+
!permissions.can(Permission.API_MODERATE_ANNALS)
7171
)
7272
throw new AppException(ERROR_CODE.NOT_DONE_UE_IN_SEMESTER, ueCode, semester);
73+
if (!(await this.ueService.didUeHappenAtSemester(ueCode, semester)))
74+
throw new AppException(ERROR_CODE.NO_SUCH_UE_AT_SEMESTER, ueCode, semester);
7375
return this.annalsService.createAnnalFile(user, { ueCode, semester, typeId, ueof });
7476
}
7577

7678
@Get('metadata')
77-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
79+
@RequireApiPermission('API_SEE_ANNALS')
7880
@ApiOperation({
7981
description:
8082
'Get generic information about annals for a particular UE. User must have already done this UE, or be an `annalUploader`.',
@@ -92,13 +94,13 @@ export class AnnalsController {
9294
@GetPermissions() permissions: PermissionManager,
9395
): Promise<UeAnnalMetadataResDto> {
9496
if (!(await this.ueService.doesUeExist(ueCode))) throw new AppException(ERROR_CODE.NO_SUCH_UE, ueCode);
95-
if (!(await this.ueService.hasUserAttended(ueCode, user.id)) && !permissions.can(Permission.API_UPLOAD_ANNAL))
97+
if (!(await this.ueService.hasUserAttended(ueCode, user.id)) && !permissions.can(Permission.API_UPLOAD_ANNALS))
9698
throw new AppException(ERROR_CODE.NOT_ALREADY_DONE_UE);
97-
return this.annalsService.getUeAnnalMetadata(user, ueCode, permissions.can(Permission.API_UPLOAD_ANNAL));
99+
return this.annalsService.getUeAnnalMetadata(user, ueCode, permissions.can(Permission.API_UPLOAD_ANNALS));
98100
}
99101

100102
@Put(':annalId')
101-
@RequireUserType('STUDENT')
103+
@RequireApiPermission('API_UPLOAD_ANNALS')
102104
@UploadRoute('file')
103105
@ApiOperation({
104106
description:
@@ -125,15 +127,15 @@ export class AnnalsController {
125127
if (!(await this.annalsService.isUeAnnalSender(user.id, annalId)))
126128
throw new AppException(ERROR_CODE.NOT_ANNAL_SENDER);
127129
if (
128-
(await this.annalsService.getUeAnnal(annalId, user.id, permissions.can(Permission.API_MODERATE_ANNAL))).status !==
129-
CommentStatus.PROCESSING
130+
(await this.annalsService.getUeAnnal(annalId, user.id, permissions.can(Permission.API_MODERATE_ANNALS)))
131+
.status !== CommentStatus.PROCESSING
130132
)
131133
throw new AppException(ERROR_CODE.ANNAL_ALREADY_UPLOADED);
132134
return this.annalsService.uploadAnnalFile(await file, annalId, rotate);
133135
}
134136

135137
@Get(':annalId')
136-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
138+
@RequireApiPermission('API_SEE_ANNALS')
137139
@ApiOperation({ description: 'Get the file linked to a specific annal.' })
138140
@ApiOkResponse({ description: 'The file is sent back.' })
139141
@ApiAppErrorResponse(
@@ -146,12 +148,14 @@ export class AnnalsController {
146148
@Response() response: ExpressResponse,
147149
@GetPermissions() permissions: PermissionManager,
148150
) {
149-
if (!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNAL))))
151+
if (
152+
!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNALS)))
153+
)
150154
throw new AppException(ERROR_CODE.NO_SUCH_ANNAL, annalId);
151155
const annalFile = await this.annalsService.getUeAnnalFile(
152156
annalId,
153157
user.id,
154-
permissions.can(Permission.API_MODERATE_ANNAL),
158+
permissions.can(Permission.API_MODERATE_ANNALS),
155159
);
156160
if (!annalFile) throw new AppException(ERROR_CODE.NO_SUCH_ANNAL, annalId);
157161
response.setHeader('Content-Type', 'application/pdf');
@@ -163,7 +167,7 @@ export class AnnalsController {
163167
}
164168

165169
@Patch(':annalId')
166-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
170+
@RequireApiPermission('API_UPLOAD_ANNALS')
167171
@ApiOperation({
168172
description:
169173
'Modify the metadata of an annal. User must be the original sender of the annal, or be an `annalModerator`.',
@@ -177,18 +181,20 @@ export class AnnalsController {
177181
@GetUser() user: User,
178182
@GetPermissions() permissions: PermissionManager,
179183
): Promise<UeAnnalResDto> {
180-
if (!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNAL))))
184+
if (
185+
!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNALS)))
186+
)
181187
throw new AppException(ERROR_CODE.NO_SUCH_ANNAL, annalId);
182188
if (
183189
!(await this.annalsService.isUeAnnalSender(user.id, annalId)) &&
184-
!permissions.can(Permission.API_MODERATE_ANNAL)
190+
!permissions.can(Permission.API_MODERATE_ANNALS)
185191
)
186192
throw new AppException(ERROR_CODE.NOT_ANNAL_SENDER);
187193
return this.annalsService.updateAnnalMetadata(annalId, body);
188194
}
189195

190196
@Delete(':annalId')
191-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
197+
@RequireApiPermission('API_UPLOAD_ANNALS')
192198
@ApiOperation({
193199
description:
194200
'Delete an annal. The file attached to the annal will not actually be deleted. User must be the original sender of the annal, or be an `annalModerator`.',
@@ -201,11 +207,13 @@ export class AnnalsController {
201207
@GetUser() user: User,
202208
@GetPermissions() permissions: PermissionManager,
203209
): Promise<UeAnnalResDto> {
204-
if (!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNAL))))
210+
if (
211+
!(await this.annalsService.isAnnalAccessible(user.id, annalId, permissions.can(Permission.API_MODERATE_ANNALS)))
212+
)
205213
throw new AppException(ERROR_CODE.NO_SUCH_ANNAL, annalId);
206214
if (
207215
!(await this.annalsService.isUeAnnalSender(user.id, annalId)) &&
208-
!permissions.can(Permission.API_MODERATE_ANNAL)
216+
!permissions.can(Permission.API_MODERATE_ANNALS)
209217
)
210218
throw new AppException(ERROR_CODE.NOT_ANNAL_SENDER);
211219
return this.annalsService.deleteAnnal(annalId);

src/ue/annals/annals.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class AnnalsService {
8686
},
8787
ueof: {
8888
connect: {
89-
code: subscription.ueofCode ?? params.ueof,
89+
code: subscription?.ueofCode ?? params.ueof,
9090
},
9191
},
9292
},

src/ue/comments/comments.controller.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Patch, Post, Query } from '@nestjs/common';
22
import { UUIDParam } from '../../app.pipe';
3-
import { GetUser, RequireApiPermission, RequireUserType } from '../../auth/decorator';
3+
import { GetUser, RequireApiPermission } from '../../auth/decorator';
44
import { AppException, ERROR_CODE } from '../../exceptions';
55
import UeCommentPostReqDto from './dto/req/ue-comment-post-req.dto';
66
import CommentReplyReqDto from './dto/req/ue-comment-reply-req.dto';
@@ -24,15 +24,14 @@ export class CommentsController {
2424
constructor(readonly commentsService: CommentsService, readonly ueService: UeService) {}
2525

2626
@Get()
27-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
2827
@RequireApiPermission(Permission.API_SEE_OPINIONS_UE)
2928
@ApiOperation({ description: 'Get the comments of a UE. This route is paginated.' })
3029
@ApiOkResponse({ type: paginatedResponseDto(UeCommentResDto) })
3130
@ApiAppErrorResponse(
3231
ERROR_CODE.NO_SUCH_UE,
3332
'This error is sent back when there is no UE associated with the code provided.',
3433
)
35-
async getUEComments(
34+
async getUeComments(
3635
@GetUser() user: User,
3736
@Query() dto: GetUeCommentsReqDto,
3837
@GetPermissions() permissions: PermissionManager,
@@ -42,7 +41,7 @@ export class CommentsController {
4241
}
4342

4443
@Post()
45-
@RequireUserType('STUDENT')
44+
@RequireApiPermission('API_GIVE_OPINIONS_UE')
4645
@ApiOperation({ description: 'Send a comment for a UE.' })
4746
@ApiOkResponse({ type: UeCommentResDto })
4847
@ApiAppErrorResponse(
@@ -69,7 +68,7 @@ export class CommentsController {
6968

7069
// TODO : en vrai la route GET /ue/comments renvoie les mêmes infos nan ? :sweat_smile:
7170
@Get(':commentId')
72-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
71+
@RequireApiPermission('API_SEE_OPINIONS_UE')
7372
@ApiOperation({ description: 'Fetch a specific comment.' })
7473
@ApiOkResponse({ type: UeCommentResDto })
7574
@ApiAppErrorResponse(ERROR_CODE.NO_SUCH_COMMENT, 'No comment is associated with the given commentId')
@@ -88,7 +87,7 @@ export class CommentsController {
8887
}
8988

9089
@Patch(':commentId')
91-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
90+
@RequireApiPermission('API_GIVE_OPINIONS_UE')
9291
@ApiOperation({ description: 'Edit a comment.' })
9392
@ApiOkResponse({ type: UeCommentResDto })
9493
@ApiAppErrorResponse(ERROR_CODE.NO_SUCH_COMMENT, 'No comment has the given commentId.')
@@ -111,7 +110,7 @@ export class CommentsController {
111110
}
112111

113112
@Delete(':commentId')
114-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
113+
@RequireApiPermission('API_GIVE_OPINIONS_UE')
115114
@ApiOperation({
116115
description: 'Delete a comment. The user must be the author or have the `commentModerator` permission.',
117116
})
@@ -135,7 +134,7 @@ export class CommentsController {
135134
}
136135

137136
@Post(':commentId/upvote')
138-
@RequireUserType('STUDENT')
137+
@RequireApiPermission('API_GIVE_OPINIONS_UE')
139138
@HttpCode(HttpStatus.OK)
140139
@ApiOperation({
141140
description: 'Give an upvote for a comment. User cannot be the author. Each user can only upvote a comment once.',
@@ -164,7 +163,7 @@ export class CommentsController {
164163
}
165164

166165
@Delete(':commentId/upvote')
167-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
166+
@RequireApiPermission('API_GIVE_OPINIONS_UE')
168167
@HttpCode(HttpStatus.OK)
169168
@ApiOperation({ description: 'Remove an upvote for a comment. User' })
170169
@ApiOkResponse({ type: UeCommentUpvoteResDto$False })
@@ -191,7 +190,7 @@ export class CommentsController {
191190
}
192191

193192
@Post(':commentId/reply')
194-
@RequireUserType('STUDENT')
193+
@RequireApiPermission('API_GIVE_OPINIONS_UE')
195194
@ApiOperation({ description: 'Reply to a comment.' })
196195
@ApiOkResponse({ type: UeCommentReplyResDto })
197196
@ApiAppErrorResponse(ERROR_CODE.NO_SUCH_COMMENT, 'There is no comment with the provided commentId.')
@@ -208,7 +207,7 @@ export class CommentsController {
208207
}
209208

210209
@Patch('reply/:replyId')
211-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
210+
@RequireApiPermission('API_GIVE_OPINIONS_UE')
212211
@ApiOperation({
213212
description:
214213
'Edit a reply to a comment. The user must be the author of the reply or have the `commentModerator` permission.',
@@ -232,7 +231,7 @@ export class CommentsController {
232231
}
233232

234233
@Delete('reply/:replyId')
235-
@RequireUserType('STUDENT', 'FORMER_STUDENT')
234+
@RequireApiPermission('API_GIVE_OPINIONS_UE')
236235
@ApiOperation({
237236
description:
238237
'Delete a reply to a comment. The user must be the author of the reply or have the `commentModerator` permission.',

0 commit comments

Comments
 (0)