Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update #3

Merged
merged 2 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
"version": "1.0.0",
"description": "NestJs project generated using startease CLI tool",
"license": "MIT",
"engines": {
"node": "20"
},
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RepositoryModule } from './module/v1/repository/repository.module';
import { SeederModule } from './module/v1/seeder/seeder.module';
import { ProjectModule } from './module/v1/project/project.module';
import { TechnologyModule } from './module/v1/technology/technology.module';
import { RatingModule } from './module/v1/rating/rating.module';

@Module({
imports: [
Expand All @@ -20,6 +21,7 @@ import { TechnologyModule } from './module/v1/technology/technology.module';
SeederModule,
ProjectModule,
TechnologyModule,
RatingModule,
],
controllers: [],
providers: [],
Expand Down
9 changes: 0 additions & 9 deletions src/common/enums/meeting.enum.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/module/v1/project/project.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ import { RepositoryModule } from 'src/module/v1/repository/repository.module';
],
controllers: [ProjectController],
providers: [ProjectService],
exports: [ProjectService],
})
export class ProjectModule {}
6 changes: 5 additions & 1 deletion src/module/v1/project/project.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { FilterQuery, Model, UpdateQuery } from 'mongoose';
import { uploadSingleFile } from 'src/common/utils/aws.util';
import { BaseHelper } from 'src/common/utils/helper.util';
import { CreateProjectDto, UpdateProjectDto } from 'src/module/v1/project/dto/project.dto';
Expand Down Expand Up @@ -92,4 +92,8 @@ export class ProjectService {

return deletedProject;
}

async updateQuery(query: FilterQuery<ProjectDocument>, updatePayload: UpdateQuery<ProjectDocument>) {
return await this.projectModel.findOneAndUpdate(query, updatePayload, { new: true });
}
}
23 changes: 23 additions & 0 deletions src/module/v1/rating/dto/rating.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { PartialType } from '@nestjs/mapped-types';
import { IsMongoId, IsNotEmpty, IsNumber, IsOptional, Min } from 'class-validator';

export class CrateRatingDto {
@IsNotEmpty()
@IsNumber()
@Min(1)
rating: number;

@IsNotEmpty()
@IsMongoId({ message: 'Project id is not valid' })
projectId: string;

@IsNotEmpty()
@IsOptional()
comment: string;
}

export class UpdateRatingDto extends PartialType(CrateRatingDto) {
@IsNotEmpty()
@IsMongoId({ message: 'Rating id is not valid' })
ratingId: string;
}
36 changes: 36 additions & 0 deletions src/module/v1/rating/rating.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { RatingService } from './rating.service';
import { LoggedInUserDecorator } from 'src/common/decorators/logged_in_user.decorator';
import { UserDocument } from 'src/module/v1/user/schemas/user.schema';
import { CrateRatingDto, UpdateRatingDto } from 'src/module/v1/rating/dto/rating.dto';
import { PaginationDto } from 'src/module/v1/repository/dto/repository.dto';

@Controller('rating')
export class RatingController {
constructor(private readonly ratingService: RatingService) {}

@Post()
async create(@Body() payload: CrateRatingDto, @LoggedInUserDecorator() user: UserDocument) {
return await this.ratingService.create(user, payload);
}

@Patch()
async update(@Body() payload: UpdateRatingDto, @LoggedInUserDecorator() user: UserDocument) {
return await this.ratingService.updateRating(user, payload);
}

@Get('project/:projectId')
async getRatingByProject(@Param('projectId') projectId: string, @Query() query: PaginationDto) {
return await this.ratingService.getRatingByProject(projectId, query);
}

@Get(':id')
async getRatingById(@Param('id') id: string) {
return await this.ratingService.getRatingById(id);
}

@Delete(':ratingId')
async deleteRating(@Param('ratingId') ratingId: string, @LoggedInUserDecorator() user: UserDocument) {
return await this.ratingService.deleteRating(ratingId, user);
}
}
23 changes: 23 additions & 0 deletions src/module/v1/rating/rating.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Module } from '@nestjs/common';
import { RatingService } from './rating.service';
import { RatingController } from './rating.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { Rating, RatingSchema } from 'src/module/v1/rating/schema/rating.schema';
import { ProjectModule } from 'src/module/v1/project/project.module';
import { RepositoryModule } from 'src/module/v1/repository/repository.module';

@Module({
imports: [
MongooseModule.forFeature([
{
name: Rating.name,
schema: RatingSchema,
},
]),
RepositoryModule,
ProjectModule,
],
controllers: [RatingController],
providers: [RatingService],
})
export class RatingModule {}
130 changes: 130 additions & 0 deletions src/module/v1/rating/rating.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import mongoose, { Model } from 'mongoose';
import { ProjectService } from 'src/module/v1/project/project.service';
import { CrateRatingDto, UpdateRatingDto } from 'src/module/v1/rating/dto/rating.dto';
import { Rating, RatingDocument } from 'src/module/v1/rating/schema/rating.schema';
import { PaginationDto } from 'src/module/v1/repository/dto/repository.dto';
import { RepositoryService } from 'src/module/v1/repository/repository.service';
import { UserDocument } from 'src/module/v1/user/schemas/user.schema';

@Injectable()
export class RatingService {
constructor(
@InjectModel(Rating.name) private ratingModel: Model<RatingDocument>,
private repositoryService: RepositoryService,
private projectService: ProjectService,
) {}

async create(user: UserDocument, payload: CrateRatingDto) {
const { projectId, rating, comment } = payload;
const project = await this.projectService.getProjectById(projectId);

if (!project) {
throw new NotFoundException('Project not found');
}

const ratingData = await this.ratingModel.create({
user: user._id,
project: project._id,
rating,
comment,
});

if (!ratingData) {
throw new NotFoundException('Unable to rate project, please try again');
}

const averageRating = await this.getAverageRating(projectId);
if (averageRating > 0) {
await this.projectService.updateQuery({ _id: project._id }, { averageRating });
}

return ratingData;
}

async getAverageRating(projectId: string) {
const averageRating = await this.ratingModel.aggregate([
{
$match: {
project: new mongoose.Types.ObjectId(projectId),
isDeleted: false,
},
},
{
$group: {
_id: '$project',
averageRating: { $avg: '$rating' },
},
},
{
$project: {
_id: 0,
averageRating: 1,
},
},
]);

return averageRating[0]?.averageRating || 0;
}

async updateRating(user: UserDocument, payload: UpdateRatingDto) {
const { ratingId } = payload;

const updateRating = await this.ratingModel.findOneAndUpdate(
{
_id: ratingId,
user: user._id,
},
Comment on lines +75 to +78

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query object depends on a
user-provided value
.
{
...payload,
},
Comment on lines +79 to +81

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query object depends on a
user-provided value
.
{ new: true },
);

if (!updateRating) {
throw new NotFoundException('Rating not found');
}

return updateRating;
}

async getRatingByProject(projectId: string, query: PaginationDto) {
const [averageRating, rating] = await Promise.all([
this.getAverageRating(projectId),
this.repositoryService.paginate(this.ratingModel, query, { project: projectId }, 'user project'),
]);

return {
rating,
averageRating,
};
}

async deleteRating(ratingId: string, user: UserDocument) {
const deletedRating = await this.ratingModel.findOneAndUpdate(
{
_id: ratingId,
user: user._id,
},
{
isDeleted: true,
},
);

if (!deletedRating) {
throw new NotFoundException('Rating not found');
}

// update project average rating
const project = await this.projectService.getProjectById(deletedRating.project);
if (project) {
const averageRating = await this.getAverageRating(deletedRating.project);
await this.projectService.updateQuery({ _id: project._id }, { averageRating });
}
}

async getRatingById(id: string) {
return await this.ratingModel.findById(id);
}
}
35 changes: 35 additions & 0 deletions src/module/v1/rating/schema/rating.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose, { Document } from 'mongoose';
import { Project } from 'src/module/v1/project/schema/project.schema';
import { User } from 'src/module/v1/user/schemas/user.schema';

export type RatingDocument = Rating & Document;

@Schema({ timestamps: true })
export class Rating {
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: User.name })
user: string;

@Prop({ type: mongoose.Schema.Types.ObjectId, ref: Project.name })
project: string;

@Prop({ enum: [1, 2, 3, 4, 5], default: 0 })
rating: number;

@Prop()
comment: string;

@Prop({ default: false })
isDeleted: boolean;
}

export const RatingSchema = SchemaFactory.createForClass(Rating);

RatingSchema.index({ project: 1, user: 1 }, { unique: true });

RatingSchema.pre('find', function (next) {
// remove deleted ratings
this.where({ isDeleted: false });

next();
});