From edcda553b563f801e8f00b69b87dae61db881f94 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Wed, 15 Nov 2023 13:34:24 +0100 Subject: [PATCH 01/14] refactor(assignments-service): Move helper methods to AssigneeService --- .../assignments/src/assignee/assignee.controller.ts | 4 ++-- .../apps/assignments/src/assignee/assignee.service.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/services/apps/assignments/src/assignee/assignee.controller.ts b/services/apps/assignments/src/assignee/assignee.controller.ts index fbd2d871..02881212 100644 --- a/services/apps/assignments/src/assignee/assignee.controller.ts +++ b/services/apps/assignments/src/assignee/assignee.controller.ts @@ -34,7 +34,7 @@ export class AssigneeController { @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, @Param('field') field: string, ): Promise { - return this.assigneeService.model.distinct(field, {assignment}).exec(); + return this.assigneeService.distinct(assignment, field); } @Patch('assignments/:assignment/assignees') @@ -44,7 +44,7 @@ export class AssigneeController { @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, @Body() dtos: BulkUpdateAssigneeDto[], ): Promise { - return Promise.all(dtos.map(dto => this.assigneeService.upsert({assignment, solution: dto.solution}, dto))); + return this.assigneeService.upsertMany(assignment, dtos); } @Get('assignments/:assignment/solutions/:solution/assignee') diff --git a/services/apps/assignments/src/assignee/assignee.service.ts b/services/apps/assignments/src/assignee/assignee.service.ts index 5910600d..b2ab86b6 100644 --- a/services/apps/assignments/src/assignee/assignee.service.ts +++ b/services/apps/assignments/src/assignee/assignee.service.ts @@ -1,9 +1,10 @@ import {EventService, MongooseRepository} from '@mean-stream/nestx'; import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; -import {Model} from 'mongoose'; +import {Model, Types} from 'mongoose'; import {Assignee, AssigneeDocument} from './assignee.schema'; +import {BulkUpdateAssigneeDto} from "./assignee.dto"; @Injectable() export class AssigneeService extends MongooseRepository { @@ -17,4 +18,12 @@ export class AssigneeService extends MongooseRepository { + return this.model.distinct(field, {assignment}).exec(); + } + + async upsertMany(assignment: Types.ObjectId, dtos: BulkUpdateAssigneeDto[]): Promise { + return Promise.all(dtos.map(dto => this.upsert({assignment, solution: dto.solution}, dto))); + } } From 744fae021c22dd4631e77fb8441ad05ea40b4f57 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Wed, 15 Nov 2023 13:40:40 +0100 Subject: [PATCH 02/14] refactor(assignments-service): Use MongooseRepository for AssignmentService --- .../src/assignment/assignment.controller.ts | 37 +++++++++--- .../src/assignment/assignment.service.ts | 60 +++---------------- 2 files changed, 36 insertions(+), 61 deletions(-) diff --git a/services/apps/assignments/src/assignment/assignment.controller.ts b/services/apps/assignments/src/assignment/assignment.controller.ts index 3fe8e7ee..49bcac20 100644 --- a/services/apps/assignments/src/assignment/assignment.controller.ts +++ b/services/apps/assignments/src/assignment/assignment.controller.ts @@ -1,5 +1,5 @@ import {Auth, AuthUser, UserToken} from '@app/keycloak-auth'; -import {NotFound, notFound, ObjectIdArrayPipe} from '@mean-stream/nestx'; +import {NotFound, notFound, ObjectIdArrayPipe, ObjectIdPipe} from '@mean-stream/nestx'; import { Body, Controller, @@ -14,12 +14,13 @@ import { Query, } from '@nestjs/common'; import {ApiCreatedResponse, ApiHeader, ApiOkResponse, ApiTags, getSchemaPath} from '@nestjs/swagger'; -import {FilterQuery, Types} from 'mongoose'; +import {FilterQuery, Types, UpdateQuery} from 'mongoose'; import {AssignmentAuth} from './assignment-auth.decorator'; import {CreateAssignmentDto, ReadAssignmentDto, UpdateAssignmentDto,} from './assignment.dto'; -import {Assignment} from './assignment.schema'; +import {Assignment, ASSIGNMENT_COLLATION, ASSIGNMENT_SORT} from './assignment.schema'; import {AssignmentService} from './assignment.service'; import {MemberService} from "@app/member"; +import {generateToken} from "../utils"; const forbiddenResponse = 'Not owner or invalid Assignment-Token.'; @@ -39,7 +40,11 @@ export class AssignmentController { @Body() dto: CreateAssignmentDto, @AuthUser() user?: UserToken, ) { - return this.assignmentService.create(dto, user?.sub); + return this.assignmentService.create({ + ...dto, + token: generateToken(), + createdBy: user?.sub, + }); } @Get() @@ -64,7 +69,10 @@ export class AssignmentController { const members = await this.memberService.findAll({user: {$in: memberIds}}); (filter.$or ||= []).push({_id: {$in: members.map(m => m.parent)}}); } - return (await this.assignmentService.findAll(filter)).map(a => this.assignmentService.mask(a.toObject())); + return (await this.assignmentService.findAll(filter, { + sort: ASSIGNMENT_SORT, + collation: ASSIGNMENT_COLLATION, + })).map(a => this.assignmentService.mask(a.toObject())); } @Get(':id') @@ -81,7 +89,7 @@ export class AssignmentController { }) @ApiHeader({name: 'assignment-token', required: false}) async findOne( - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, @Headers('assignment-token') token?: string, @AuthUser() user?: UserToken, ): Promise { @@ -97,9 +105,20 @@ export class AssignmentController { @AssignmentAuth({forbiddenResponse}) @ApiOkResponse({type: Assignment}) async update( - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, @Body() dto: UpdateAssignmentDto, ): Promise { + const {token, classroom, ...rest} = dto; + const update: UpdateQuery = rest; + if (token) { + update.token = generateToken(); + } + if (classroom) { + // need to flatten the classroom object to prevent deleting the GitHub token all the time + for (const [key, value] of Object.entries(classroom)) { + update[`classroom.${key}`] = value; + } + } return this.assignmentService.update(id, dto); } @@ -108,8 +127,8 @@ export class AssignmentController { @AssignmentAuth({forbiddenResponse}) @ApiOkResponse({type: Assignment}) async remove( - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, ): Promise { - return this.assignmentService.remove(id); + return this.assignmentService.delete(id); } } diff --git a/services/apps/assignments/src/assignment/assignment.service.ts b/services/apps/assignments/src/assignment/assignment.service.ts index adda4658..b810dc7e 100644 --- a/services/apps/assignments/src/assignment/assignment.service.ts +++ b/services/apps/assignments/src/assignment/assignment.service.ts @@ -1,20 +1,21 @@ -import {EventService} from '@mean-stream/nestx'; +import {EventRepository, EventService, MongooseRepository} from '@mean-stream/nestx'; import {UserToken} from '@app/keycloak-auth'; import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; -import {FilterQuery, Model, Types, UpdateQuery} from 'mongoose'; -import {generateToken} from '../utils'; -import {CreateAssignmentDto, ReadAssignmentDto, ReadTaskDto, UpdateAssignmentDto} from './assignment.dto'; -import {Assignment, ASSIGNMENT_COLLATION, ASSIGNMENT_SORT, AssignmentDocument, Task} from './assignment.schema'; +import {Model} from 'mongoose'; +import {ReadAssignmentDto, ReadTaskDto} from './assignment.dto'; +import {Assignment, AssignmentDocument, Task} from './assignment.schema'; import {MemberService} from "@app/member"; @Injectable() -export class AssignmentService { +@EventRepository() +export class AssignmentService extends MongooseRepository { constructor( - @InjectModel(Assignment.name) private model: Model, + @InjectModel(Assignment.name) model: Model, private eventService: EventService, private readonly memberService: MemberService, ) { + super(model); } findTask(tasks: Task[], id: string): Task | undefined { @@ -30,28 +31,6 @@ export class AssignmentService { return undefined; } - async create(dto: CreateAssignmentDto, userId?: string): Promise { - const token = generateToken(); - const created = await this.model.create({ - ...dto, - token, - createdBy: userId, - }); - created && this.emit('created', created); - return created; - } - - async findAll(where: FilterQuery = {}): Promise { - return this.model.find(where, undefined,{ - sort: ASSIGNMENT_SORT, - collation: ASSIGNMENT_COLLATION, - }).exec(); - } - - async findOne(id: string | Types.ObjectId): Promise { - return this.model.findById(id).exec(); - } - mask(assignment: Assignment): ReadAssignmentDto { const {token, tasks, classroom, ...rest} = assignment; return { @@ -68,29 +47,6 @@ export class AssignmentService { }; } - async update(id: string | Types.ObjectId, dto: UpdateAssignmentDto | UpdateQuery): Promise { - const {token, classroom, ...rest} = dto; - const update: UpdateQuery = rest; - if (token) { - update.token = generateToken(); - } - if (classroom) { - // need to flatten the classroom object to prevent deleting the GitHub token all the time - for (const [key, value] of Object.entries(classroom)) { - update[`classroom.${key}`] = value; - } - } - const updated = await this.model.findByIdAndUpdate(id, update, {new: true}).exec(); - updated && this.emit('updated', updated); - return updated; - } - - async remove(id: string): Promise { - const deleted = await this.model.findByIdAndDelete(id).exec(); - deleted && this.emit('deleted', deleted); - return deleted; - } - async isAuthorized(assignment: Assignment, user?: UserToken, token?: string): Promise { if (assignment.token === token) { return true; From 703645ab717143ed5230d11397fd73258eace879 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Wed, 15 Nov 2023 13:45:41 +0100 Subject: [PATCH 03/14] fix(assignments-service): Use ObjectId for assignment IDs --- .../src/assignment/assignment-auth.guard.ts | 3 ++- .../src/assignment/assignment.controller.ts | 2 +- .../assignments/src/classroom/classroom.controller.ts | 11 ++++++----- .../assignments/src/comment/comment.controller.ts | 9 +++++---- .../assignments/src/embedding/embedding.controller.ts | 7 ++++--- .../assignments/src/evaluation/evaluation.service.ts | 2 +- services/apps/assignments/src/moss/moss.controller.ts | 3 ++- .../assignments/src/solution/solution-auth.guard.ts | 3 ++- .../src/statistics/statistics.controller.ts | 4 +++- .../assignments/src/statistics/statistics.service.ts | 10 +++++----- 10 files changed, 31 insertions(+), 23 deletions(-) diff --git a/services/apps/assignments/src/assignment/assignment-auth.guard.ts b/services/apps/assignments/src/assignment/assignment-auth.guard.ts index 79a1925e..92cf154d 100644 --- a/services/apps/assignments/src/assignment/assignment-auth.guard.ts +++ b/services/apps/assignments/src/assignment/assignment-auth.guard.ts @@ -4,6 +4,7 @@ import {Request} from 'express'; import {Observable} from 'rxjs'; import {AssignmentService} from './assignment.service'; import {notFound} from '@mean-stream/nestx'; +import {Types} from "mongoose"; @Injectable() export class AssignmentAuthGuard implements CanActivate { @@ -21,7 +22,7 @@ export class AssignmentAuthGuard implements CanActivate { } async checkAuth(id: string, user?: UserToken, token?: string): Promise { - const assignment = await this.assignmentService.findOne(id) ?? notFound(id); + const assignment = await this.assignmentService.find(new Types.ObjectId(id)) ?? notFound(id); return this.assignmentService.isAuthorized(assignment, user, token); } } diff --git a/services/apps/assignments/src/assignment/assignment.controller.ts b/services/apps/assignments/src/assignment/assignment.controller.ts index 49bcac20..878a8bab 100644 --- a/services/apps/assignments/src/assignment/assignment.controller.ts +++ b/services/apps/assignments/src/assignment/assignment.controller.ts @@ -93,7 +93,7 @@ export class AssignmentController { @Headers('assignment-token') token?: string, @AuthUser() user?: UserToken, ): Promise { - const assignment = await this.assignmentService.findOne(id) ?? notFound(id); + const assignment = await this.assignmentService.find(id) ?? notFound(id); if (await this.assignmentService.isAuthorized(assignment, user, token)) { return assignment; } diff --git a/services/apps/assignments/src/classroom/classroom.controller.ts b/services/apps/assignments/src/classroom/classroom.controller.ts index 6b49d9a8..a0f15fe4 100644 --- a/services/apps/assignments/src/classroom/classroom.controller.ts +++ b/services/apps/assignments/src/classroom/classroom.controller.ts @@ -5,7 +5,8 @@ import {AssignmentAuth} from '../assignment/assignment-auth.decorator'; import {ClassroomService} from './classroom.service'; import {ImportSolution} from "./classroom.dto"; import {AssignmentService} from "../assignment/assignment.service"; -import {notFound} from "@mean-stream/nestx"; +import {notFound, ObjectIdPipe} from "@mean-stream/nestx"; +import {Types} from "mongoose"; const forbiddenResponse = 'Not owner of assignment, or invalid Assignment-Token.'; @@ -23,9 +24,9 @@ export class ClassroomController { @AssignmentAuth({forbiddenResponse}) @ApiOkResponse({type: [ImportSolution]}) async previewImport( - @Param('assignment') id: string, + @Param('assignment', ObjectIdPipe) id: Types.ObjectId, ): Promise { - const assignment = await this.assignmentService.findOne(id) || notFound(id); + const assignment = await this.assignmentService.find(id) || notFound(id); return this.classroomService.previewImports(assignment); } @@ -35,11 +36,11 @@ export class ClassroomController { @AssignmentAuth({forbiddenResponse}) @ApiCreatedResponse({type: [ImportSolution]}) async importSolutions( - @Param('assignment') id: string, + @Param('assignment', ObjectIdPipe) id: Types.ObjectId, @Query('usernames', new ParseArrayPipe({optional: true})) usernames?: string[], @UploadedFiles() files?: Express.Multer.File[], ): Promise { - const assignment = await this.assignmentService.findOne(id) || notFound(id); + const assignment = await this.assignmentService.find(id) || notFound(id); return files ? this.classroomService.importFiles(assignment, files) : this.classroomService.importSolutions(assignment, usernames); } } diff --git a/services/apps/assignments/src/comment/comment.controller.ts b/services/apps/assignments/src/comment/comment.controller.ts index 163da8a2..b65668dc 100644 --- a/services/apps/assignments/src/comment/comment.controller.ts +++ b/services/apps/assignments/src/comment/comment.controller.ts @@ -1,5 +1,5 @@ import {AuthUser, UserToken} from '@app/keycloak-auth'; -import {NotFound, notFound} from '@mean-stream/nestx'; +import {NotFound, notFound, ObjectIdPipe} from '@mean-stream/nestx'; import {Body, Controller, Delete, Get, Headers, MessageEvent, Param, Patch, Post, Sse} from '@nestjs/common'; import {ApiCreatedResponse, ApiOkResponse, ApiTags} from '@nestjs/swagger'; import {Observable} from 'rxjs'; @@ -10,6 +10,7 @@ import {CommentAuth} from './comment-auth.decorator'; import {CreateCommentDto, UpdateCommentDto} from './comment.dto'; import {Comment} from './comment.schema'; import {CommentService} from './comment.service'; +import {Types} from "mongoose"; const forbiddenResponse = 'Not owner of assignment or solution, or invalid Assignment-Token or Solution-Token.'; const forbiddenCommentResponse = 'Not owner of comment.'; @@ -27,15 +28,15 @@ export class CommentController { @SolutionAuth({forbiddenResponse}) @ApiCreatedResponse({type: Comment}) async create( - @Param('assignment') assignmentId: string, + @Param('assignment', ObjectIdPipe) assignmentId: Types.ObjectId, @Param('solution') solution: string, @Body() dto: CreateCommentDto, @AuthUser() user?: UserToken, @Headers('assignment-token') assignmentToken?: string, ): Promise { - const assignment = await this.assignmentService.findOne(assignmentId) ?? notFound(assignmentId); + const assignment = await this.assignmentService.find(assignmentId) ?? notFound(assignmentId); const distinguished = await this.assignmentService.isAuthorized(assignment, user, assignmentToken); - return this.commentService.create(assignmentId, solution, dto, distinguished, user?.sub); + return this.commentService.create(assignmentId.toString(), solution, dto, distinguished, user?.sub); } @Sse('events') diff --git a/services/apps/assignments/src/embedding/embedding.controller.ts b/services/apps/assignments/src/embedding/embedding.controller.ts index 31d37bdd..7518af89 100644 --- a/services/apps/assignments/src/embedding/embedding.controller.ts +++ b/services/apps/assignments/src/embedding/embedding.controller.ts @@ -10,8 +10,9 @@ import { import {AssignmentAuth} from "../assignment/assignment-auth.decorator"; import {Embeddable, EmbeddingEstimate} from "./embedding.dto"; import {EmbeddingService} from "./embedding.service"; -import {notFound} from "@mean-stream/nestx"; +import {notFound, ObjectIdPipe} from "@mean-stream/nestx"; import {AssignmentService} from "../assignment/assignment.service"; +import {Types} from "mongoose"; @Controller('assignments/:assignment') @ApiTags('Embeddings') @@ -28,10 +29,10 @@ export class EmbeddingController { @ApiForbiddenResponse({description: 'No OpenAI API key configured for this assignment.'}) @AssignmentAuth({forbiddenResponse: 'You are not allowed to create embeddings.'}) async createEmbeddings( - @Param('assignment') assignmentId: string, + @Param('assignment', ObjectIdPipe) assignmentId: Types.ObjectId, @Query('estimate', new ParseBoolPipe({optional: true})) estimate?: boolean, ): Promise { - const assignment = await this.assignmentService.findOne(assignmentId) || notFound(assignmentId); + const assignment = await this.assignmentService.find(assignmentId) || notFound(assignmentId); return estimate ? this.embeddingService.estimateEmbeddings(assignment) : this.embeddingService.createEmbeddings(assignment); diff --git a/services/apps/assignments/src/evaluation/evaluation.service.ts b/services/apps/assignments/src/evaluation/evaluation.service.ts index 4e21237f..9facc418 100644 --- a/services/apps/assignments/src/evaluation/evaluation.service.ts +++ b/services/apps/assignments/src/evaluation/evaluation.service.ts @@ -122,7 +122,7 @@ export class EvaluationService { } private async codeSearch(assignmentId: Types.ObjectId, taskId: string, snippets: Snippet[]): Promise<[Types.ObjectId, Snippet[] | undefined][]> { - const assignment = await this.assignmentService.findOne(assignmentId); + const assignment = await this.assignmentService.find(assignmentId); const task = assignment && this.assignmentService.findTask(assignment.tasks, taskId); const resultsBySnippet = await Promise.all(snippets.map(async snippet => { const results = await this.searchService.find(assignmentId.toString(), { diff --git a/services/apps/assignments/src/moss/moss.controller.ts b/services/apps/assignments/src/moss/moss.controller.ts index 2b97e951..e33dfd57 100644 --- a/services/apps/assignments/src/moss/moss.controller.ts +++ b/services/apps/assignments/src/moss/moss.controller.ts @@ -6,6 +6,7 @@ import {SearchService} from "../search/search.service"; import {NotFound, notFound} from "@mean-stream/nestx"; import {SolutionService} from "../solution/solution.service"; import {ApiOperation, ApiTags} from "@nestjs/swagger"; +import {Types} from "mongoose"; @Controller('assignments/:assignment/moss') @ApiTags('MOSS') @@ -25,7 +26,7 @@ export class MossController { async runMoss( @Param('assignment') assignment: string, ): Promise { - const assignmentDoc = await this.assignmentService.findOne(assignment) || notFound(assignment); + const assignmentDoc = await this.assignmentService.find(new Types.ObjectId(assignment)) || notFound(assignment); const files = await this.searchService.findAll(assignment); const solutions = await this.solutionService.findAll({assignment}); const solutionNames = new Map(solutions.map(({author: {name, github, studentId, email}, _id}) => [ diff --git a/services/apps/assignments/src/solution/solution-auth.guard.ts b/services/apps/assignments/src/solution/solution-auth.guard.ts index a2374ec2..757a7a06 100644 --- a/services/apps/assignments/src/solution/solution-auth.guard.ts +++ b/services/apps/assignments/src/solution/solution-auth.guard.ts @@ -5,6 +5,7 @@ import {Observable} from 'rxjs'; import {AssignmentService} from '../assignment/assignment.service'; import {notFound} from '@mean-stream/nestx'; import {SolutionService} from './solution.service'; +import {Types} from "mongoose"; @Injectable() export class SolutionAuthGuard implements CanActivate { @@ -26,7 +27,7 @@ export class SolutionAuthGuard implements CanActivate { } async checkAuth(assignmentId: string, solutionId: string, user?: UserToken, assignmentToken?: string, solutionToken?: string): Promise { - const assignment = await this.assignmentService.findOne(assignmentId); + const assignment = await this.assignmentService.find(new Types.ObjectId(assignmentId)); if (!assignment) { notFound(assignmentId); } diff --git a/services/apps/assignments/src/statistics/statistics.controller.ts b/services/apps/assignments/src/statistics/statistics.controller.ts index f36a34d4..cd305938 100644 --- a/services/apps/assignments/src/statistics/statistics.controller.ts +++ b/services/apps/assignments/src/statistics/statistics.controller.ts @@ -2,6 +2,8 @@ import {Controller, Get, Param} from '@nestjs/common'; import {ApiOkResponse, ApiOperation, ApiTags} from '@nestjs/swagger'; import {AssignmentStatistics} from './statistics.dto'; import {StatisticsService} from './statistics.service'; +import {ObjectIdPipe} from "@mean-stream/nestx"; +import {Types} from "mongoose"; @Controller() @ApiTags('Statistics') @@ -15,7 +17,7 @@ export class StatisticsController { @ApiOperation({summary: 'Get statistics for an assignment'}) @ApiOkResponse({type: AssignmentStatistics}) async getAssignmentStatistics( - @Param('assignment') assignment: string, + @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, ): Promise { return this.statisticsService.getAssignmentStatistics(assignment); } diff --git a/services/apps/assignments/src/statistics/statistics.service.ts b/services/apps/assignments/src/statistics/statistics.service.ts index 03feece6..69560f6d 100644 --- a/services/apps/assignments/src/statistics/statistics.service.ts +++ b/services/apps/assignments/src/statistics/statistics.service.ts @@ -32,8 +32,8 @@ export class StatisticsService { } } - async getAssignmentStatistics(assignment: string): Promise { - const assignmentDoc = await this.assignmentService.findOne(assignment); + async getAssignmentStatistics(assignment: Types.ObjectId): Promise { + const assignmentDoc = await this.assignmentService.find(assignment); if (!assignmentDoc) { throw new NotFoundException(assignment); } @@ -60,10 +60,10 @@ export class StatisticsService { solutions, , ] = await Promise.all([ - this.timeStatistics(assignmentDoc._id, taskStats, tasks), - this.countComments(assignment), + this.timeStatistics(assignment, taskStats, tasks), + this.countComments(assignment.toString()), this.solutionStatistics(assignmentDoc), - this.fillEvaluationStatistics(assignmentDoc._id, taskStats, tasks, evaluations, weightedEvaluations), + this.fillEvaluationStatistics(assignment, taskStats, tasks, evaluations, weightedEvaluations), ]); // needs to happen after timeStatistics and fillEvaluationStatistics From ae5aa9617933d33dca718474ec6c128e133382da Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Thu, 16 Nov 2023 10:54:47 +0100 Subject: [PATCH 04/14] refactor(assignments-service): Use MongooseRepository for CommentService --- .../src/comment/comment-auth.guard.ts | 3 +- .../src/comment/comment.controller.ts | 27 +++++----- .../src/comment/comment.handler.ts | 2 +- .../src/comment/comment.service.ts | 52 ++----------------- 4 files changed, 22 insertions(+), 62 deletions(-) diff --git a/services/apps/assignments/src/comment/comment-auth.guard.ts b/services/apps/assignments/src/comment/comment-auth.guard.ts index 2f5085b8..e85d01fe 100644 --- a/services/apps/assignments/src/comment/comment-auth.guard.ts +++ b/services/apps/assignments/src/comment/comment-auth.guard.ts @@ -2,6 +2,7 @@ import {CanActivate, ExecutionContext, Injectable} from '@nestjs/common'; import {Request} from 'express'; import {notFound} from '@mean-stream/nestx'; import {CommentService} from './comment.service'; +import {Types} from "mongoose"; @Injectable() export class CommentAuthGuard implements CanActivate { @@ -14,7 +15,7 @@ export class CommentAuthGuard implements CanActivate { const req = context.switchToHttp().getRequest() as Request; const commentId = req.params.comment ?? req.params.id; const user = (req as any).user; - const comment = await this.commentService.findOne(commentId) ?? notFound(commentId); + const comment = await this.commentService.find(new Types.ObjectId(commentId)) ?? notFound(commentId); return this.commentService.isAuthorized(comment, user); } } diff --git a/services/apps/assignments/src/comment/comment.controller.ts b/services/apps/assignments/src/comment/comment.controller.ts index b65668dc..5bea5092 100644 --- a/services/apps/assignments/src/comment/comment.controller.ts +++ b/services/apps/assignments/src/comment/comment.controller.ts @@ -36,7 +36,14 @@ export class CommentController { ): Promise { const assignment = await this.assignmentService.find(assignmentId) ?? notFound(assignmentId); const distinguished = await this.assignmentService.isAuthorized(assignment, user, assignmentToken); - return this.commentService.create(assignmentId.toString(), solution, dto, distinguished, user?.sub); + return this.commentService.create({ + ...dto, + assignment: assignmentId.toString(), + solution, + timestamp: new Date(), + createdBy: user?.sub, + distinguished, + }); } @Sse('events') @@ -58,7 +65,7 @@ export class CommentController { @Param('assignment') assignment: string, @Param('solution') solution: string, ): Promise { - return this.commentService.findAll({assignment, solution}); + return this.commentService.findAll({assignment, solution}, {sort: {timestamp: 1}}); } @Get(':id') @@ -66,11 +73,9 @@ export class CommentController { @NotFound() @ApiOkResponse({type: Comment}) async findOne( - @Param('assignment') assignment: string, - @Param('solution') solution: string, - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, ): Promise { - return this.commentService.findOne(id); + return this.commentService.find(id); } @Patch(':id') @@ -78,9 +83,7 @@ export class CommentController { @NotFound() @ApiOkResponse({type: Comment}) async update( - @Param('assignment') assignment: string, - @Param('solution') solution: string, - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, @Body() dto: UpdateCommentDto, ): Promise { return this.commentService.update(id, dto); @@ -91,10 +94,8 @@ export class CommentController { @NotFound() @ApiOkResponse({type: Comment}) async remove( - @Param('assignment') assignment: string, - @Param('solution') solution: string, - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, ): Promise { - return this.commentService.remove(id); + return this.commentService.delete(id); } } diff --git a/services/apps/assignments/src/comment/comment.handler.ts b/services/apps/assignments/src/comment/comment.handler.ts index b802336a..cacc3c19 100644 --- a/services/apps/assignments/src/comment/comment.handler.ts +++ b/services/apps/assignments/src/comment/comment.handler.ts @@ -12,6 +12,6 @@ export class CommentHandler { @OnEvent('assignments.*.solutions.*.deleted') async onSolutionDeleted(solution: SolutionDocument) { - await this.commentService.removeAll({assignment: solution.assignment, solution: solution.id}); + await this.commentService.deleteMany({assignment: solution.assignment, solution: solution.id}); } } diff --git a/services/apps/assignments/src/comment/comment.service.ts b/services/apps/assignments/src/comment/comment.service.ts index d002334b..ef0d514f 100644 --- a/services/apps/assignments/src/comment/comment.service.ts +++ b/services/apps/assignments/src/comment/comment.service.ts @@ -1,60 +1,18 @@ -import {EventService} from '@mean-stream/nestx'; +import {EventRepository, EventService, MongooseRepository} from '@mean-stream/nestx'; import {UserToken} from '@app/keycloak-auth'; import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; -import {FilterQuery, Model} from 'mongoose'; -import {CreateCommentDto, UpdateCommentDto} from './comment.dto'; +import {Model} from 'mongoose'; import {Comment, CommentDocument} from './comment.schema'; @Injectable() -export class CommentService { +@EventRepository() +export class CommentService extends MongooseRepository { constructor( @InjectModel(Comment.name) public model: Model, private eventService: EventService, ) { - } - - async create(assignment: string, solution: string, dto: CreateCommentDto, distinguished: boolean, createdBy?: string): Promise { - const comment: Comment = { - ...dto, - assignment, - solution, - createdBy, - distinguished, - timestamp: new Date(), - }; - const created = await this.model.create(comment); - this.emit('created', created); - return created; - } - - async findAll(where: FilterQuery = {}): Promise { - return this.model.find(where).sort('+timestamp').exec(); - } - - async findOne(id: string): Promise { - return this.model.findById(id).exec(); - } - - async update(id: string, dto: UpdateCommentDto): Promise { - const updated = await this.model.findByIdAndUpdate(id, dto, {new: true}).exec(); - updated && this.emit('updated', updated); - return updated; - } - - async remove(id: string): Promise { - const deleted = await this.model.findByIdAndDelete(id).exec(); - deleted && this.emit('deleted', deleted); - return deleted; - } - - async removeAll(where: FilterQuery): Promise { - const comments = await this.findAll(where); - await this.model.deleteMany({_id: {$in: comments.map(a => a._id)}}).exec(); - for (const comment of comments) { - this.emit('deleted', comment); - } - return comments; + super(model); } isAuthorized(comment: Comment, bearerToken: UserToken) { From 11db978dfe95405bf8d55ecf3aba79d7f5054bab Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Thu, 16 Nov 2023 11:05:37 +0100 Subject: [PATCH 05/14] refactor(assignments-service): Use MongooseRepository for SolutionService --- .../src/solution/solution-auth.guard.ts | 2 +- .../src/solution/solution.controller.ts | 34 +++++++---- .../src/solution/solution.handler.ts | 2 +- .../src/solution/solution.service.ts | 56 ++----------------- 4 files changed, 31 insertions(+), 63 deletions(-) diff --git a/services/apps/assignments/src/solution/solution-auth.guard.ts b/services/apps/assignments/src/solution/solution-auth.guard.ts index 757a7a06..ae379d38 100644 --- a/services/apps/assignments/src/solution/solution-auth.guard.ts +++ b/services/apps/assignments/src/solution/solution-auth.guard.ts @@ -41,7 +41,7 @@ export class SolutionAuthGuard implements CanActivate { return false; } - const solution = await this.solutionService.findOne(solutionId); + const solution = await this.solutionService.find(new Types.ObjectId(solutionId)); if (!solution) { notFound(solutionId); } diff --git a/services/apps/assignments/src/solution/solution.controller.ts b/services/apps/assignments/src/solution/solution.controller.ts index 23feb23d..c001392a 100644 --- a/services/apps/assignments/src/solution/solution.controller.ts +++ b/services/apps/assignments/src/solution/solution.controller.ts @@ -1,5 +1,5 @@ import {Auth, AuthUser, UserToken} from '@app/keycloak-auth'; -import {NotFound, ObjectIdArrayPipe} from '@mean-stream/nestx'; +import {NotFound, ObjectIdArrayPipe, ObjectIdPipe} from '@mean-stream/nestx'; import { Body, Controller, @@ -19,10 +19,11 @@ import {FilterQuery, Types} from 'mongoose'; import {AssignmentAuth} from '../assignment/assignment-auth.decorator'; import {SolutionAuth} from './solution-auth.decorator'; import {BatchUpdateSolutionDto, CreateSolutionDto, RichSolutionDto, UpdateSolutionDto} from './solution.dto'; -import {Solution} from './solution.schema'; +import {Solution, SOLUTION_COLLATION, SOLUTION_SORT} from './solution.schema'; import {SolutionService} from './solution.service'; import {FilesInterceptor} from "@nestjs/platform-express"; import {FileService} from "../file/file.service"; +import {generateToken} from "../utils"; const forbiddenResponse = 'Not owner of solution or assignment, or invalid Assignment-Token or Solution-Token.'; const forbiddenAssignmentResponse = 'Not owner of assignment, or invalid Assignment-Token.'; @@ -46,7 +47,13 @@ export class SolutionController { @AuthUser() user?: UserToken, @UploadedFiles() files?: Express.Multer.File[], ): Promise { - const solution = await this.solutionService.create(assignment, dto, user?.sub); + const solution = await this.solutionService.create({ + ...dto, + assignment, + createdBy: user?.sub, + token: generateToken(), + timestamp: new Date(), + }); if (files && files.length) { await this.fileService.importFiles(assignment, solution.id, files); } @@ -127,9 +134,9 @@ export class SolutionController { @NotFound() @ApiOkResponse({type: Solution}) async findOne( - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, ): Promise { - return this.solutionService.findOne(id); + return this.solutionService.find(id); } @Get('solutions') @@ -139,7 +146,10 @@ export class SolutionController { async findOwn( @AuthUser() user: UserToken, ): Promise { - return this.solutionService.findAll({createdBy: user.sub}); + return this.solutionService.findAll({createdBy: user.sub}, { + sort: SOLUTION_SORT, + collation: SOLUTION_COLLATION, + }); } @Patch('assignments/:assignment/solutions/:id') @@ -147,7 +157,7 @@ export class SolutionController { @NotFound() @ApiOkResponse({type: Solution}) async update( - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, @Body() dto: UpdateSolutionDto, ): Promise { return this.solutionService.update(id, dto); @@ -167,7 +177,7 @@ export class SolutionController { @Param('assignment') assignment: string, @Body() dtos: BatchUpdateSolutionDto[], ): Promise<(Solution | null)[]> { - return this.solutionService.updateMany(assignment, dtos); + return this.solutionService.batchUpdate(assignment, dtos); } @Delete('assignments/:assignment/solutions/:id') @@ -175,9 +185,9 @@ export class SolutionController { @NotFound() @ApiOkResponse({type: Solution}) async remove( - @Param('id') id: string, + @Param('id', ObjectIdPipe) id: Types.ObjectId, ): Promise { - return this.solutionService.remove(id); + return this.solutionService.delete(id); } @Delete('assignments/:assignment/solutions') @@ -188,9 +198,11 @@ export class SolutionController { @Param('assignment') assignment: string, @Query('ids', ParseArrayPipe, ObjectIdArrayPipe) ids: Types.ObjectId[], ): Promise { - return this.solutionService.removeAll({ + const solutions = await this.solutionService.findAll({ assignment, _id: {$in: ids}, }); + await this.solutionService.deleteAll(solutions); + return solutions; } } diff --git a/services/apps/assignments/src/solution/solution.handler.ts b/services/apps/assignments/src/solution/solution.handler.ts index 63b85d80..161be7f6 100644 --- a/services/apps/assignments/src/solution/solution.handler.ts +++ b/services/apps/assignments/src/solution/solution.handler.ts @@ -12,6 +12,6 @@ export class SolutionHandler { @OnEvent('assignments.*.deleted') async onAssignmentDeleted(assignment: AssignmentDocument) { - await this.solutionService.removeAll({assignment: assignment.id}); + await this.solutionService.deleteMany({assignment: assignment.id}); } } diff --git a/services/apps/assignments/src/solution/solution.service.ts b/services/apps/assignments/src/solution/solution.service.ts index 739e65d0..77ec9ee2 100644 --- a/services/apps/assignments/src/solution/solution.service.ts +++ b/services/apps/assignments/src/solution/solution.service.ts @@ -1,38 +1,19 @@ -import {EventService} from '@mean-stream/nestx'; +import {EventRepository, EventService, MongooseRepository} from '@mean-stream/nestx'; import {UserToken} from '@app/keycloak-auth'; import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; import {FilterQuery, Model, UpdateQuery} from 'mongoose'; -import {generateToken} from '../utils'; -import {BatchUpdateSolutionDto, CreateSolutionDto, RichSolutionDto, UpdateSolutionDto} from './solution.dto'; +import {BatchUpdateSolutionDto, RichSolutionDto} from './solution.dto'; import {Solution, SOLUTION_COLLATION, SOLUTION_SORT, SolutionDocument} from './solution.schema'; @Injectable() -export class SolutionService { +@EventRepository() +export class SolutionService extends MongooseRepository { constructor( @InjectModel(Solution.name) public model: Model, private eventService: EventService, ) { - } - - async create(assignment: string, dto: CreateSolutionDto, createdBy?: string): Promise { - const created = await this.model.create({ - ...dto, - assignment, - createdBy, - token: generateToken(), - timestamp: new Date(), - }); - this.emit('created', created); - return created; - } - - async findAll(where: FilterQuery = {}): Promise { - return this.model - .find(where) - .sort(SOLUTION_SORT) - .collation(SOLUTION_COLLATION) - .exec(); + super(model); } async findRich(preFilter: FilterQuery, postFilter: FilterQuery): Promise { @@ -120,17 +101,7 @@ export class SolutionService { }); } - async findOne(id: string): Promise { - return this.model.findById(id).exec(); - } - - async update(id: string, dto: UpdateSolutionDto): Promise { - const updated = await this.model.findByIdAndUpdate(id, dto, {new: true}).exec(); - updated && this.emit('updated', updated); - return updated; - } - - async updateMany(assignment: string, dtos: BatchUpdateSolutionDto[]): Promise<(SolutionDocument | null)[]> { + async batchUpdate(assignment: string, dtos: BatchUpdateSolutionDto[]): Promise<(SolutionDocument | null)[]> { const updated = await Promise.all(dtos.map(dto => { const {_id, author, consent, ...rest} = dto; if (!_id && !author) { @@ -166,21 +137,6 @@ export class SolutionService { return updated; } - async remove(id: string): Promise { - const deleted = await this.model.findByIdAndDelete(id).exec(); - deleted && this.emit('deleted', deleted); - return deleted; - } - - async removeAll(where: FilterQuery): Promise { - const solutions = await this.model.find(where).exec(); - await this.model.deleteMany({_id: {$in: solutions.map(a => a._id)}}).exec(); - for (const solution of solutions) { - this.emit('deleted', solution); - } - return solutions; - } - isAuthorized(solution: Solution, user?: UserToken, token?: string): boolean { return solution.token === token || !!user && user.sub === solution.createdBy; } From b10bcbb2099c0914215a20313f97808728574ee0 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Thu, 16 Nov 2023 11:23:22 +0100 Subject: [PATCH 06/14] refactor(assignments-service): Use MongooseRepository for EvaluationService --- .../src/evaluation/evaluation.controller.ts | 14 ++--- .../src/evaluation/evaluation.handler.ts | 2 +- .../src/evaluation/evaluation.service.ts | 54 ++++++------------- 3 files changed, 23 insertions(+), 47 deletions(-) diff --git a/services/apps/assignments/src/evaluation/evaluation.controller.ts b/services/apps/assignments/src/evaluation/evaluation.controller.ts index 9bb4ad3b..d9a51071 100644 --- a/services/apps/assignments/src/evaluation/evaluation.controller.ts +++ b/services/apps/assignments/src/evaluation/evaluation.controller.ts @@ -76,7 +76,7 @@ export class EvaluationController { @Body() dto: CreateEvaluationDto, @AuthUser() user?: UserToken, ): Promise { - return this.evaluationService.create(assignment, solution, dto, user?.sub); + return this.evaluationService.createWithCodeSearch(assignment, solution, dto, user?.sub); } @Get('solutions/:solution/evaluations') @@ -108,9 +108,9 @@ export class EvaluationController { @ApiOkResponse({type: Evaluation}) @NotFound() async findOne( - @Param('id') id: string, + @Param('assignment', ObjectIdPipe) id: Types.ObjectId, ): Promise { - return this.evaluationService.findOne(id); + return this.evaluationService.find(id); } @Patch('solutions/:solution/evaluations/:id') @@ -118,10 +118,10 @@ export class EvaluationController { @ApiOkResponse({type: Evaluation}) @NotFound() async update( - @Param('id') id: string, + @Param('assignment', ObjectIdPipe) id: Types.ObjectId, @Body() dto: UpdateEvaluationDto, ): Promise { - return this.evaluationService.update(id, dto); + return this.evaluationService.updateWithCodeSearch(id, dto); } @Delete('solutions/:solution/evaluations/:id') @@ -129,8 +129,8 @@ export class EvaluationController { @ApiOkResponse({type: Evaluation}) @NotFound() async remove( - @Param('id') id: string, + @Param('assignment', ObjectIdPipe) id: Types.ObjectId, ): Promise { - return this.evaluationService.remove(id); + return this.evaluationService.deleteWithCodeSearch(id); } } diff --git a/services/apps/assignments/src/evaluation/evaluation.handler.ts b/services/apps/assignments/src/evaluation/evaluation.handler.ts index 9947524c..657d5bdd 100644 --- a/services/apps/assignments/src/evaluation/evaluation.handler.ts +++ b/services/apps/assignments/src/evaluation/evaluation.handler.ts @@ -13,7 +13,7 @@ export class EvaluationHandler { @OnEvent('assignments.*.solutions.*.deleted') async onSolutionDeleted(solution: SolutionDocument) { - await this.evaluationService.removeAll({ + await this.evaluationService.deleteMany({ assignment: new Types.ObjectId(solution.assignment), solution: solution._id, }); diff --git a/services/apps/assignments/src/evaluation/evaluation.service.ts b/services/apps/assignments/src/evaluation/evaluation.service.ts index 9facc418..f613c7b2 100644 --- a/services/apps/assignments/src/evaluation/evaluation.service.ts +++ b/services/apps/assignments/src/evaluation/evaluation.service.ts @@ -1,4 +1,4 @@ -import {EventService} from '@mean-stream/nestx'; +import {EventRepository, EventService, MongooseRepository} from '@mean-stream/nestx'; import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; import {FilterQuery, Model, Types, UpdateQuery} from 'mongoose'; @@ -8,13 +8,15 @@ import {CreateEvaluationDto, RemarkDto, UpdateEvaluationDto} from './evaluation. import {CodeSearchInfo, Evaluation, EvaluationDocument, Snippet} from './evaluation.schema'; @Injectable() -export class EvaluationService { +@EventRepository() +export class EvaluationService extends MongooseRepository { constructor( @InjectModel(Evaluation.name) public model: Model, private eventService: EventService, private searchService: SearchService, private assignmentService: AssignmentService, ) { + super(model); } private emit(event: string, evaluation: EvaluationDocument) { @@ -26,9 +28,9 @@ export class EvaluationService { return this.eventService.subscribe(`assignments.${assignment}.solutions.${solution}.evaluations.${evaluation}.${event}`, user); } - async create(assignment: Types.ObjectId, solution: Types.ObjectId, dto: CreateEvaluationDto, createdBy?: string): Promise { + async createWithCodeSearch(assignment: Types.ObjectId, solution: Types.ObjectId, dto: CreateEvaluationDto, createdBy?: string): Promise { const {codeSearch, ...rest} = dto; - const evaluation = await this.model.findOneAndUpdate({ + const evaluation = await this.upsert({ assignment, solution, task: dto.task, @@ -37,20 +39,15 @@ export class EvaluationService { assignment, solution, createdBy, - }, {upsert: true, new: true}).exec(); - this.emit('created', evaluation); + }); if (codeSearch && dto.snippets.length) { evaluation.codeSearch = await this.codeSearchCreate(assignment, evaluation._id, dto); } return evaluation; } - async findAll(where: FilterQuery = {}): Promise { - return this.model.find(where).exec(); - } - async findUnique(field: keyof Evaluation | string, where: FilterQuery = {}): Promise { - return this.model.find(where).sort(field).distinct(field).exec(); + return this.model.distinct(field, where).exec(); } async findRemarks(where: FilterQuery = {}): Promise { @@ -80,19 +77,10 @@ export class EvaluationService { ]).exec(); } - async findOne(id: string): Promise { - return this.model.findById(id).exec(); - } - - async update(id: string, dto: UpdateEvaluationDto): Promise { + async updateWithCodeSearch(id: Types.ObjectId, dto: UpdateEvaluationDto): Promise { const {codeSearch, ...rest} = dto; - const evaluation = await this.model.findByIdAndUpdate(id, rest, {new: true}).exec(); - if (!evaluation) { - return null; - } - - this.emit('updated', evaluation); - if (codeSearch && dto.snippets && dto.snippets.length) { + const evaluation = await this.update(id, rest); + if (evaluation && codeSearch && dto.snippets && dto.snippets.length) { evaluation.codeSearch = { ...evaluation.codeSearch, ...await this.codeSearchUpdate(evaluation.assignment, evaluation.task, evaluation._id, dto), @@ -101,26 +89,14 @@ export class EvaluationService { return evaluation; } - async remove(id: string): Promise { - const deleted = await this.model.findByIdAndDelete(id).exec(); - if (!deleted) { - return null; + async deleteWithCodeSearch(id: Types.ObjectId): Promise { + const deleted = await this.delete(id); + if (deleted) { + deleted.codeSearch = await this.codeSearchDelete(deleted); } - - this.emit('deleted', deleted); - deleted.codeSearch = await this.codeSearchDelete(deleted); return deleted; } - async removeAll(where: FilterQuery): Promise { - const evaluations = await this.findAll(where); - await this.model.deleteMany({_id: {$in: evaluations.map(a => a._id)}}).exec(); - for (const evaluation of evaluations) { - this.emit('deleted', evaluation); - } - return evaluations; - } - private async codeSearch(assignmentId: Types.ObjectId, taskId: string, snippets: Snippet[]): Promise<[Types.ObjectId, Snippet[] | undefined][]> { const assignment = await this.assignmentService.find(assignmentId); const task = assignment && this.assignmentService.findTask(assignment.tasks, taskId); From 612f053d2eb6b00f4e27c2f666ed0e0b5ad555e8 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 17 Nov 2023 16:40:48 +0100 Subject: [PATCH 07/14] fix(assignments-service): Wrong parameter names in EvaluationController --- .../assignments/src/evaluation/evaluation.controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/apps/assignments/src/evaluation/evaluation.controller.ts b/services/apps/assignments/src/evaluation/evaluation.controller.ts index d9a51071..c2fb8591 100644 --- a/services/apps/assignments/src/evaluation/evaluation.controller.ts +++ b/services/apps/assignments/src/evaluation/evaluation.controller.ts @@ -108,7 +108,7 @@ export class EvaluationController { @ApiOkResponse({type: Evaluation}) @NotFound() async findOne( - @Param('assignment', ObjectIdPipe) id: Types.ObjectId, + @Param('id', ObjectIdPipe) id: Types.ObjectId, ): Promise { return this.evaluationService.find(id); } @@ -118,7 +118,7 @@ export class EvaluationController { @ApiOkResponse({type: Evaluation}) @NotFound() async update( - @Param('assignment', ObjectIdPipe) id: Types.ObjectId, + @Param('id', ObjectIdPipe) id: Types.ObjectId, @Body() dto: UpdateEvaluationDto, ): Promise { return this.evaluationService.updateWithCodeSearch(id, dto); @@ -129,7 +129,7 @@ export class EvaluationController { @ApiOkResponse({type: Evaluation}) @NotFound() async remove( - @Param('assignment', ObjectIdPipe) id: Types.ObjectId, + @Param('id', ObjectIdPipe) id: Types.ObjectId, ): Promise { return this.evaluationService.deleteWithCodeSearch(id); } From aa33de2c8d02d7b4c05a6258c3f0cf1fb5b46301 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 17 Nov 2023 16:47:57 +0100 Subject: [PATCH 08/14] refactor(assignments-service): Change Solution.assignment to ObjectId --- .../apps/assignments/src/assignee/assignee.handler.ts | 2 +- .../assignments/src/classroom/classroom.service.ts | 2 +- .../apps/assignments/src/comment/comment.handler.ts | 2 +- .../assignments/src/embedding/embedding.handler.ts | 2 +- .../assignments/src/embedding/embedding.service.ts | 5 ++--- .../assignments/src/evaluation/evaluation.handler.ts | 2 +- services/apps/assignments/src/moss/moss.controller.ts | 2 +- services/apps/assignments/src/search/search.handler.ts | 2 +- .../assignments/src/solution/solution.controller.ts | 10 +++++----- .../apps/assignments/src/solution/solution.handler.ts | 2 +- .../apps/assignments/src/solution/solution.schema.ts | 8 +++----- .../apps/assignments/src/solution/solution.service.ts | 4 ++-- .../assignments/src/statistics/statistics.service.ts | 2 +- 13 files changed, 21 insertions(+), 24 deletions(-) diff --git a/services/apps/assignments/src/assignee/assignee.handler.ts b/services/apps/assignments/src/assignee/assignee.handler.ts index e47eba4b..e97c3dc1 100644 --- a/services/apps/assignments/src/assignee/assignee.handler.ts +++ b/services/apps/assignments/src/assignee/assignee.handler.ts @@ -14,7 +14,7 @@ export class AssigneeHandler { @OnEvent('assignments.*.solutions.*.deleted') async onSolutionDeleted(solution: SolutionDocument) { await this.assigneeService.deleteOne({ - assignment: new Types.ObjectId(solution.assignment), + assignment: solution.assignment, solution: solution._id, }); } diff --git a/services/apps/assignments/src/classroom/classroom.service.ts b/services/apps/assignments/src/classroom/classroom.service.ts index b70782c2..aef83735 100644 --- a/services/apps/assignments/src/classroom/classroom.service.ts +++ b/services/apps/assignments/src/classroom/classroom.service.ts @@ -239,7 +239,7 @@ export class ClassroomService { private createImportSolution(assignment: AssignmentDocument, repo: RepositoryInfo, commit: string | undefined): ImportSolution { return { - assignment: assignment._id.toString(), + assignment: assignment._id, author: { name: '', email: '', diff --git a/services/apps/assignments/src/comment/comment.handler.ts b/services/apps/assignments/src/comment/comment.handler.ts index cacc3c19..acc56674 100644 --- a/services/apps/assignments/src/comment/comment.handler.ts +++ b/services/apps/assignments/src/comment/comment.handler.ts @@ -12,6 +12,6 @@ export class CommentHandler { @OnEvent('assignments.*.solutions.*.deleted') async onSolutionDeleted(solution: SolutionDocument) { - await this.commentService.deleteMany({assignment: solution.assignment, solution: solution.id}); + await this.commentService.deleteMany({assignment: solution.assignment.toString(), solution: solution.id}); } } diff --git a/services/apps/assignments/src/embedding/embedding.handler.ts b/services/apps/assignments/src/embedding/embedding.handler.ts index c1169df0..923c3bf7 100644 --- a/services/apps/assignments/src/embedding/embedding.handler.ts +++ b/services/apps/assignments/src/embedding/embedding.handler.ts @@ -51,6 +51,6 @@ export class EmbeddingHandler { @OnEvent('assignments.*.solutions.*.deleted') async onSolutionDeleted(solution: SolutionDocument) { - await this.embeddingService.deleteBySolution(solution.assignment, solution.id); + await this.embeddingService.deleteBySolution(solution.assignment.toString(), solution.id); } } diff --git a/services/apps/assignments/src/embedding/embedding.service.ts b/services/apps/assignments/src/embedding/embedding.service.ts index 3d6bf28f..74ed8ae8 100644 --- a/services/apps/assignments/src/embedding/embedding.service.ts +++ b/services/apps/assignments/src/embedding/embedding.service.ts @@ -131,15 +131,14 @@ export class EmbeddingService implements OnModuleInit { } private async getDocuments(assignment: Assignment) { - const assignmentId = assignment._id.toString(); - const filter:FilterQuery = {assignment: assignmentId}; + const filter: FilterQuery = {assignment: assignment._id}; if (assignment.classroom?.openaiConsent !== false) { filter['consent.3P'] = true; } const solutionsWithConsent = await this.solutionService.model.find(filter, {_id: 1}); return { solutions: solutionsWithConsent.length, - documents: await this.searchService.findAll(assignmentId, solutionsWithConsent.map(s => s.id)), + documents: await this.searchService.findAll(assignment._id.toString(), solutionsWithConsent.map(s => s.id)), }; } diff --git a/services/apps/assignments/src/evaluation/evaluation.handler.ts b/services/apps/assignments/src/evaluation/evaluation.handler.ts index 657d5bdd..da68bb4b 100644 --- a/services/apps/assignments/src/evaluation/evaluation.handler.ts +++ b/services/apps/assignments/src/evaluation/evaluation.handler.ts @@ -14,7 +14,7 @@ export class EvaluationHandler { @OnEvent('assignments.*.solutions.*.deleted') async onSolutionDeleted(solution: SolutionDocument) { await this.evaluationService.deleteMany({ - assignment: new Types.ObjectId(solution.assignment), + assignment: solution.assignment, solution: solution._id, }); } diff --git a/services/apps/assignments/src/moss/moss.controller.ts b/services/apps/assignments/src/moss/moss.controller.ts index e33dfd57..725346a5 100644 --- a/services/apps/assignments/src/moss/moss.controller.ts +++ b/services/apps/assignments/src/moss/moss.controller.ts @@ -28,7 +28,7 @@ export class MossController { ): Promise { const assignmentDoc = await this.assignmentService.find(new Types.ObjectId(assignment)) || notFound(assignment); const files = await this.searchService.findAll(assignment); - const solutions = await this.solutionService.findAll({assignment}); + const solutions = await this.solutionService.findAll({assignment: assignmentDoc._id}); const solutionNames = new Map(solutions.map(({author: {name, github, studentId, email}, _id}) => [ _id.toString(), name || github || studentId || email, diff --git a/services/apps/assignments/src/search/search.handler.ts b/services/apps/assignments/src/search/search.handler.ts index 7fbc1496..7c7bb94c 100644 --- a/services/apps/assignments/src/search/search.handler.ts +++ b/services/apps/assignments/src/search/search.handler.ts @@ -12,6 +12,6 @@ export class SearchHandler { @OnEvent('assignments.*.solutions.*.deleted') async onSolutionDeleted(solution: SolutionDocument) { - await this.searchService.deleteAll(solution.assignment, solution.id); + await this.searchService.deleteAll(solution.assignment.toString(), solution.id); } } diff --git a/services/apps/assignments/src/solution/solution.controller.ts b/services/apps/assignments/src/solution/solution.controller.ts index c001392a..8d1dd030 100644 --- a/services/apps/assignments/src/solution/solution.controller.ts +++ b/services/apps/assignments/src/solution/solution.controller.ts @@ -42,7 +42,7 @@ export class SolutionController { @ApiCreatedResponse({type: Solution}) @UseInterceptors(FilesInterceptor('files')) async create( - @Param('assignment') assignment: string, + @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, @Body() dto: CreateSolutionDto, @AuthUser() user?: UserToken, @UploadedFiles() files?: Express.Multer.File[], @@ -55,7 +55,7 @@ export class SolutionController { timestamp: new Date(), }); if (files && files.length) { - await this.fileService.importFiles(assignment, solution.id, files); + await this.fileService.importFiles(assignment.toString(), solution.id, files); } return solution; } @@ -71,7 +71,7 @@ export class SolutionController { 'and `field:term` for searching any of the author fields and `assignee`, `origin`, or `status`.' }) async findAll( - @Param('assignment') assignment: string, + @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, @Query('q') search?: string, @Query('author.github') github?: string, ): Promise { @@ -174,7 +174,7 @@ export class SolutionController { @ApiBody({type: [BatchUpdateSolutionDto]}) @ApiOkResponse({type: [Solution]}) async updateMany( - @Param('assignment') assignment: string, + @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, @Body() dtos: BatchUpdateSolutionDto[], ): Promise<(Solution | null)[]> { return this.solutionService.batchUpdate(assignment, dtos); @@ -195,7 +195,7 @@ export class SolutionController { @AssignmentAuth({forbiddenResponse: forbiddenAssignmentResponse}) @ApiOkResponse({type: [Solution]}) async removeAll( - @Param('assignment') assignment: string, + @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, @Query('ids', ParseArrayPipe, ObjectIdArrayPipe) ids: Types.ObjectId[], ): Promise { const solutions = await this.solutionService.findAll({ diff --git a/services/apps/assignments/src/solution/solution.handler.ts b/services/apps/assignments/src/solution/solution.handler.ts index 161be7f6..44069ff3 100644 --- a/services/apps/assignments/src/solution/solution.handler.ts +++ b/services/apps/assignments/src/solution/solution.handler.ts @@ -12,6 +12,6 @@ export class SolutionHandler { @OnEvent('assignments.*.deleted') async onAssignmentDeleted(assignment: AssignmentDocument) { - await this.solutionService.deleteMany({assignment: assignment.id}); + await this.solutionService.deleteMany({assignment: assignment._id}); } } diff --git a/services/apps/assignments/src/solution/solution.schema.ts b/services/apps/assignments/src/solution/solution.schema.ts index 2313b834..58ac5936 100644 --- a/services/apps/assignments/src/solution/solution.schema.ts +++ b/services/apps/assignments/src/solution/solution.schema.ts @@ -15,7 +15,7 @@ import { ValidateNested, } from 'class-validator'; import {Types} from 'mongoose'; -import {Doc} from "@mean-stream/nestx"; +import {Doc, Ref} from "@mean-stream/nestx"; export class Consent { @Prop() @@ -88,10 +88,8 @@ export class Solution { @ApiProperty() token: string; - @Prop({index: 1}) - @ApiProperty() - @IsMongoId() - assignment: string; + @Ref('Assignment', {index: 1}) + assignment: Types.ObjectId; @Prop({index: 1}) @ApiProperty({required: false}) diff --git a/services/apps/assignments/src/solution/solution.service.ts b/services/apps/assignments/src/solution/solution.service.ts index 77ec9ee2..b9b308f4 100644 --- a/services/apps/assignments/src/solution/solution.service.ts +++ b/services/apps/assignments/src/solution/solution.service.ts @@ -2,7 +2,7 @@ import {EventRepository, EventService, MongooseRepository} from '@mean-stream/ne import {UserToken} from '@app/keycloak-auth'; import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; -import {FilterQuery, Model, UpdateQuery} from 'mongoose'; +import {FilterQuery, Model, Types, UpdateQuery} from 'mongoose'; import {BatchUpdateSolutionDto, RichSolutionDto} from './solution.dto'; import {Solution, SOLUTION_COLLATION, SOLUTION_SORT, SolutionDocument} from './solution.schema'; @@ -101,7 +101,7 @@ export class SolutionService extends MongooseRepository { }); } - async batchUpdate(assignment: string, dtos: BatchUpdateSolutionDto[]): Promise<(SolutionDocument | null)[]> { + async batchUpdate(assignment: Types.ObjectId, dtos: BatchUpdateSolutionDto[]): Promise<(SolutionDocument | null)[]> { const updated = await Promise.all(dtos.map(dto => { const {_id, author, consent, ...rest} = dto; if (!_id && !author) { diff --git a/services/apps/assignments/src/statistics/statistics.service.ts b/services/apps/assignments/src/statistics/statistics.service.ts index 69560f6d..0960dae0 100644 --- a/services/apps/assignments/src/statistics/statistics.service.ts +++ b/services/apps/assignments/src/statistics/statistics.service.ts @@ -168,7 +168,7 @@ export class StatisticsService { let graded = 0; let total = 0; let passed = 0; - for await (const {points} of this.solutionService.model.find({assignment: assignment.id}).select('points')) { + for await (const {points} of this.solutionService.model.find({assignment: assignment._id}).select('points')) { total++; if (points === undefined) { From c721576c8720bf9a366b7435073761b303027a9a Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 17 Nov 2023 17:04:57 +0100 Subject: [PATCH 09/14] fix(assignments-service): Course students aggregation --- .../apps/assignments/src/course/course.service.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/services/apps/assignments/src/course/course.service.ts b/services/apps/assignments/src/course/course.service.ts index 00c640ed..bd91f67a 100644 --- a/services/apps/assignments/src/course/course.service.ts +++ b/services/apps/assignments/src/course/course.service.ts @@ -2,7 +2,7 @@ import {EventRepository, EventService, MongooseRepository} from '@mean-stream/ne import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; import {Model, Types} from 'mongoose'; -import {AuthorInfo, SOLUTION_COLLATION, SOLUTION_SORT} from '../solution/solution.schema'; +import {AuthorInfo, Solution, SOLUTION_COLLATION, SOLUTION_SORT} from '../solution/solution.schema'; import {SolutionService} from '../solution/solution.service'; import {CourseAssignee, CourseStudent} from './course.dto'; import {Course, CourseDocument} from './course.schema'; @@ -34,7 +34,10 @@ export class CourseService extends MongooseRepository { } const students = new Map(); - const solutions = await this.solutionService.model.aggregate([ + const keys: (keyof AuthorInfo)[] = ['studentId', 'email', 'github', 'name']; + for await (const solution of this.solutionService.model.aggregate< + Pick & { assignee?: string } + >([ {$match: {assignment: {$in: courseAssignmentsWhereUserIsMember.map(o => o.toString())}}}, { $lookup: { @@ -58,10 +61,7 @@ export class CourseService extends MongooseRepository { {$sort: SOLUTION_SORT}, ], { collation: SOLUTION_COLLATION, - }); - - const keys: (keyof AuthorInfo)[] = ['studentId', 'email', 'github', 'name']; - for (const solution of solutions) { + })) { const {assignment, _id, assignee, author, points, feedback} = solution; let student: CourseStudent | undefined = undefined; for (const key of keys) { @@ -84,7 +84,7 @@ export class CourseService extends MongooseRepository { } } - const index = course.assignments.indexOf(assignment); + const index = course.assignments.indexOf(assignment.toString()); student.solutions[index] = { _id, points, From 1c0554072a4ccaca039d72151cf4fec910e49ad7 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 17 Nov 2023 17:05:26 +0100 Subject: [PATCH 10/14] refactor(assignments-service): Improve solutions with 3P consent query in EmbeddingService --- services/apps/assignments/src/embedding/embedding.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/apps/assignments/src/embedding/embedding.service.ts b/services/apps/assignments/src/embedding/embedding.service.ts index 74ed8ae8..00c2e661 100644 --- a/services/apps/assignments/src/embedding/embedding.service.ts +++ b/services/apps/assignments/src/embedding/embedding.service.ts @@ -135,7 +135,7 @@ export class EmbeddingService implements OnModuleInit { if (assignment.classroom?.openaiConsent !== false) { filter['consent.3P'] = true; } - const solutionsWithConsent = await this.solutionService.model.find(filter, {_id: 1}); + const solutionsWithConsent = await this.solutionService.findAll(filter, {projection: {_id: 1}}); return { solutions: solutionsWithConsent.length, documents: await this.searchService.findAll(assignment._id.toString(), solutionsWithConsent.map(s => s.id)), From a4cbfb2a199447114adae51fbf0ae935d1479dc5 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 17 Nov 2023 17:06:32 +0100 Subject: [PATCH 11/14] refactor(assignments-service): Improve solutions statistics query with aggregate --- .../src/statistics/statistics.service.ts | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/services/apps/assignments/src/statistics/statistics.service.ts b/services/apps/assignments/src/statistics/statistics.service.ts index 0960dae0..723ec03d 100644 --- a/services/apps/assignments/src/statistics/statistics.service.ts +++ b/services/apps/assignments/src/statistics/statistics.service.ts @@ -164,32 +164,35 @@ export class StatisticsService { private async solutionStatistics(assignment: AssignmentDocument): Promise { const passingMin = assignment.tasks.reduce((a, c) => c.points > 0 ? a + c.points : a, 0) / 2; - let pointsTotal = 0; - let graded = 0; - let total = 0; - let passed = 0; - for await (const {points} of this.solutionService.model.find({assignment: assignment._id}).select('points')) { - total++; - - if (points === undefined) { - continue; - } - - pointsTotal += points; - graded++; - - if (points < passingMin) { - continue; - } - passed++; + const [result] = await this.solutionService.model.aggregate([ + {$match: {assignment: assignment._id}}, + { + $group: { + _id: null, + total: {$sum: 1}, + points: {$sum: '$points'}, + graded: {$sum: {$cond: [{$gt: ['$points', null]}, 1, 0]}}, + passed: {$sum: {$cond: [{$gte: ['$points', passingMin]}, 1, 0]}}, + }, + }, + ]); + if (!result) { + return { + total: 0, + evaluated: 0, + graded: 0, + passed: 0, + pointsAvg: 0, + }; } + const {total, points, graded, passed} = result; const evaluated = (await this.evaluationService.findUnique('solution', {assignment: assignment._id})).length; return { total, evaluated, graded, passed, - pointsAvg: pointsTotal / graded, + pointsAvg: points / graded, }; } } From c0dba36177e127af31662133b125c264143bbf07 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 17 Nov 2023 17:25:48 +0100 Subject: [PATCH 12/14] refactor(assignments-service): Change Comment.assignment and solution to ObjectId --- .../src/comment/comment.controller.ts | 18 +++++++++--------- .../assignments/src/comment/comment.handler.ts | 2 +- .../assignments/src/comment/comment.schema.ts | 15 ++++++--------- .../assignments/src/comment/comment.service.ts | 4 ++-- .../src/statistics/statistics.service.ts | 4 ++-- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/services/apps/assignments/src/comment/comment.controller.ts b/services/apps/assignments/src/comment/comment.controller.ts index 5bea5092..777ac869 100644 --- a/services/apps/assignments/src/comment/comment.controller.ts +++ b/services/apps/assignments/src/comment/comment.controller.ts @@ -28,17 +28,17 @@ export class CommentController { @SolutionAuth({forbiddenResponse}) @ApiCreatedResponse({type: Comment}) async create( - @Param('assignment', ObjectIdPipe) assignmentId: Types.ObjectId, - @Param('solution') solution: string, + @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, + @Param('solution', ObjectIdPipe) solution: Types.ObjectId, @Body() dto: CreateCommentDto, @AuthUser() user?: UserToken, @Headers('assignment-token') assignmentToken?: string, ): Promise { - const assignment = await this.assignmentService.find(assignmentId) ?? notFound(assignmentId); - const distinguished = await this.assignmentService.isAuthorized(assignment, user, assignmentToken); + const assignmentDoc = await this.assignmentService.find(assignment) ?? notFound(assignment); + const distinguished = await this.assignmentService.isAuthorized(assignmentDoc, user, assignmentToken); return this.commentService.create({ ...dto, - assignment: assignmentId.toString(), + assignment, solution, timestamp: new Date(), createdBy: user?.sub, @@ -49,8 +49,8 @@ export class CommentController { @Sse('events') @SolutionAuth({forbiddenResponse}) events( - @Param('assignment') assignment: string, - @Param('solution') solution: string, + @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, + @Param('solution', ObjectIdPipe) solution: Types.ObjectId, @AuthUser() user?: UserToken, @Headers('assignment-token') assignmentToken?: string, @Headers('solution-token') solutionToken?: string, @@ -62,8 +62,8 @@ export class CommentController { @SolutionAuth({forbiddenResponse}) @ApiOkResponse({type: [Comment]}) async findAll( - @Param('assignment') assignment: string, - @Param('solution') solution: string, + @Param('assignment', ObjectIdPipe) assignment: Types.ObjectId, + @Param('solution', ObjectIdPipe) solution: Types.ObjectId, ): Promise { return this.commentService.findAll({assignment, solution}, {sort: {timestamp: 1}}); } diff --git a/services/apps/assignments/src/comment/comment.handler.ts b/services/apps/assignments/src/comment/comment.handler.ts index acc56674..cacc3c19 100644 --- a/services/apps/assignments/src/comment/comment.handler.ts +++ b/services/apps/assignments/src/comment/comment.handler.ts @@ -12,6 +12,6 @@ export class CommentHandler { @OnEvent('assignments.*.solutions.*.deleted') async onSolutionDeleted(solution: SolutionDocument) { - await this.commentService.deleteMany({assignment: solution.assignment.toString(), solution: solution.id}); + await this.commentService.deleteMany({assignment: solution.assignment, solution: solution.id}); } } diff --git a/services/apps/assignments/src/comment/comment.schema.ts b/services/apps/assignments/src/comment/comment.schema.ts index 819797f7..64eac9ea 100644 --- a/services/apps/assignments/src/comment/comment.schema.ts +++ b/services/apps/assignments/src/comment/comment.schema.ts @@ -1,19 +1,16 @@ import {Prop, Schema, SchemaFactory} from '@nestjs/mongoose'; import {ApiProperty} from '@nestjs/swagger'; import {IsBoolean, IsDateString, IsEmail, IsMongoId, IsNotEmpty, IsString} from 'class-validator'; -import {Doc} from "@mean-stream/nestx"; +import {Doc, Ref} from "@mean-stream/nestx"; +import {Types} from "mongoose"; @Schema() export class Comment { - @Prop() - @ApiProperty() - @IsMongoId() - assignment: string; + @Ref('Assignment') + assignment: Types.ObjectId; - @Prop() - @ApiProperty() - @IsMongoId() - solution: string; + @Ref('Assignment') + solution: Types.ObjectId; @Prop() @ApiProperty() diff --git a/services/apps/assignments/src/comment/comment.service.ts b/services/apps/assignments/src/comment/comment.service.ts index ef0d514f..db995864 100644 --- a/services/apps/assignments/src/comment/comment.service.ts +++ b/services/apps/assignments/src/comment/comment.service.ts @@ -2,7 +2,7 @@ import {EventRepository, EventService, MongooseRepository} from '@mean-stream/ne import {UserToken} from '@app/keycloak-auth'; import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; -import {Model} from 'mongoose'; +import {Model, Types} from 'mongoose'; import {Comment, CommentDocument} from './comment.schema'; @Injectable() @@ -24,7 +24,7 @@ export class CommentService extends MongooseRepository { this.eventService.emit(`assignments.${comment.assignment}.solutions.${comment.solution}.comments.${comment._id}.${event}`, comment); } - subscribe(assignment: string, solution: string, comment: string, event: string, user?: string) { + subscribe(assignment: Types.ObjectId, solution: Types.ObjectId, comment: string, event: string, user?: string) { return this.eventService.subscribe(`assignments.${assignment}.solutions.${solution}.comments.${comment}.${event}`, user); } } diff --git a/services/apps/assignments/src/statistics/statistics.service.ts b/services/apps/assignments/src/statistics/statistics.service.ts index 723ec03d..5b24fede 100644 --- a/services/apps/assignments/src/statistics/statistics.service.ts +++ b/services/apps/assignments/src/statistics/statistics.service.ts @@ -61,7 +61,7 @@ export class StatisticsService { , ] = await Promise.all([ this.timeStatistics(assignment, taskStats, tasks), - this.countComments(assignment.toString()), + this.countComments(assignment), this.solutionStatistics(assignmentDoc), this.fillEvaluationStatistics(assignment, taskStats, tasks, evaluations, weightedEvaluations), ]); @@ -152,7 +152,7 @@ export class StatisticsService { }; } - private countComments(assignment: string) { + private countComments(assignment: Types.ObjectId) { return this.commentService.model.find({ assignment, }).count().exec(); From 65df0cfa28f0c48c826c1c9640dfb75670357346 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 17 Nov 2023 17:30:21 +0100 Subject: [PATCH 13/14] fix(assignments-service): Make AssigneeService an EventRepository --- services/apps/assignments/src/assignee/assignee.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/apps/assignments/src/assignee/assignee.service.ts b/services/apps/assignments/src/assignee/assignee.service.ts index b2ab86b6..0eae0f1d 100644 --- a/services/apps/assignments/src/assignee/assignee.service.ts +++ b/services/apps/assignments/src/assignee/assignee.service.ts @@ -1,4 +1,4 @@ -import {EventService, MongooseRepository} from '@mean-stream/nestx'; +import {EventRepository, EventService, MongooseRepository} from '@mean-stream/nestx'; import {Injectable} from '@nestjs/common'; import {InjectModel} from '@nestjs/mongoose'; import {Model, Types} from 'mongoose'; @@ -7,6 +7,7 @@ import {Assignee, AssigneeDocument} from './assignee.schema'; import {BulkUpdateAssigneeDto} from "./assignee.dto"; @Injectable() +@EventRepository() export class AssigneeService extends MongooseRepository { constructor( @InjectModel(Assignee.name) public model: Model, From 17141f9375b7080e9ae6f5dcfdc218389b75fd31 Mon Sep 17 00:00:00 2001 From: Adrian Kunz Date: Fri, 17 Nov 2023 17:34:46 +0100 Subject: [PATCH 14/14] style(assignments-service): Remove unused imports --- services/apps/assignments/src/assignee/assignee.handler.ts | 1 - services/apps/assignments/src/comment/comment.schema.ts | 2 +- services/apps/assignments/src/evaluation/evaluation.handler.ts | 1 - services/apps/assignments/src/solution/solution.schema.ts | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/services/apps/assignments/src/assignee/assignee.handler.ts b/services/apps/assignments/src/assignee/assignee.handler.ts index e97c3dc1..96a7e7c9 100644 --- a/services/apps/assignments/src/assignee/assignee.handler.ts +++ b/services/apps/assignments/src/assignee/assignee.handler.ts @@ -2,7 +2,6 @@ import {Injectable} from '@nestjs/common'; import {OnEvent} from '@nestjs/event-emitter'; import {SolutionDocument} from '../solution/solution.schema'; import {AssigneeService} from './assignee.service'; -import {Types} from "mongoose"; @Injectable() export class AssigneeHandler { diff --git a/services/apps/assignments/src/comment/comment.schema.ts b/services/apps/assignments/src/comment/comment.schema.ts index 64eac9ea..2a2c9098 100644 --- a/services/apps/assignments/src/comment/comment.schema.ts +++ b/services/apps/assignments/src/comment/comment.schema.ts @@ -1,6 +1,6 @@ import {Prop, Schema, SchemaFactory} from '@nestjs/mongoose'; import {ApiProperty} from '@nestjs/swagger'; -import {IsBoolean, IsDateString, IsEmail, IsMongoId, IsNotEmpty, IsString} from 'class-validator'; +import {IsBoolean, IsDateString, IsEmail, IsNotEmpty, IsString} from 'class-validator'; import {Doc, Ref} from "@mean-stream/nestx"; import {Types} from "mongoose"; diff --git a/services/apps/assignments/src/evaluation/evaluation.handler.ts b/services/apps/assignments/src/evaluation/evaluation.handler.ts index da68bb4b..ec162002 100644 --- a/services/apps/assignments/src/evaluation/evaluation.handler.ts +++ b/services/apps/assignments/src/evaluation/evaluation.handler.ts @@ -2,7 +2,6 @@ import {Injectable} from '@nestjs/common'; import {OnEvent} from '@nestjs/event-emitter'; import {SolutionDocument} from '../solution/solution.schema'; import {EvaluationService} from './evaluation.service'; -import {Types} from "mongoose"; @Injectable() export class EvaluationHandler { diff --git a/services/apps/assignments/src/solution/solution.schema.ts b/services/apps/assignments/src/solution/solution.schema.ts index 58ac5936..8fac34fd 100644 --- a/services/apps/assignments/src/solution/solution.schema.ts +++ b/services/apps/assignments/src/solution/solution.schema.ts @@ -7,7 +7,6 @@ import { IsEmail, IsHash, IsIn, - IsMongoId, IsNumber, IsOptional, IsString,