From f3479171c3e1ad8e36e6a9ff63463a0b67dc6e08 Mon Sep 17 00:00:00 2001 From: Biniyamseid Date: Sat, 18 Feb 2023 02:01:40 +0300 Subject: [PATCH 1/4] modify create reveiw --- databases/dev.db | Bin 65536 -> 65536 bytes prisma/schema.prisma | 1 + src/review/review.service.ts | 13 ++++++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/databases/dev.db b/databases/dev.db index 50c22b77c3b87dd1b1bbe95e5b155a6d861ababb..3940038fd824a19c91ec07e331313a26b06c79d9 100644 GIT binary patch delta 123 zcmZo@U}I)CS8 wL4#s`3m$eh21ZU!ai+`?h0J0eHWVH!3XcVuH~HXsLlz!pHipgL&T|<60EtZ Date: Sat, 18 Feb 2023 04:07:37 +0300 Subject: [PATCH 2/4] update the review part and added delete and get average rating --- databases/dev.db | Bin 65536 -> 69632 bytes .../migration.sql | 17 ++++ .../migration.sql | 8 ++ prisma/schema.prisma | 1 + src/review/review.controller.ts | 12 ++- src/review/review.dto.ts | 2 + src/review/review.service.ts | 96 ++++++++++++++---- 7 files changed, 113 insertions(+), 23 deletions(-) create mode 100644 prisma/migrations/20230217230230_created_at_for_the_review_model/migration.sql create mode 100644 prisma/migrations/20230218005318_make_review_unique_author_id_hotel_id/migration.sql diff --git a/databases/dev.db b/databases/dev.db index 3940038fd824a19c91ec07e331313a26b06c79d9..e17cb945edf86a6a85ed6c689028ca4b09e2a4c8 100644 GIT binary patch delta 1561 zcma)6U1%It6uvXFKbf7KxmoOXvxc_2Yodwi#`)dZO`tW_SOO-SY!izO?J_%aC)p&M zZIj)!L20tBmLf$O2KrVhD1r(t&44HoqX?xc#cC0Y4@L{x1p5&1#fL_{`;$EQ&~%1- z&*i({Ip@27XMWx>f5maJ(f1Ajz=zxB7hl*bVX!^D0mm%*uMIJkfLVS+r6-kJvbJ{pcPobGC zI+-p`W=|ARS}PVt^Hb@pksi$#&;%ZoU4L3HwRkIvvt`8@MZuyHMpQ6WBP#2%6pfp_ zf=4PJPnd#&3^}8yiYBNDN!DW~FPprIS0~3L-B6@siXa&oK^A#cR1>_KQ8ZnX)tDk9 zB*}@Gz?%xrt6fGNHOXpOxWuRRDWnyUk=6=n6U!`&Zt2hE4Kz{l@!L(oUtPOd_5pxL zxr^{FyvE(*_CW@oV;^vSZUoBQr*=5>4V!AAhKR@6ezr_}oK<`6eeE|04+U2M{0pwY zHQUF34?Q#$APQ)@FiKf#`=24<#~6MOw!%L_Ju!(lzj_94uZ!A6r1JUP;Ouck(+=!4 zG>xXFDQhLUlcA{~Q6(~q{f>H^WEsHwa2ejg(XPWCPf%^uB+rlJwCqH?p3hPCoxH$% zDE1bBaqbLz%jT{uv%!)`)qy~ut(44FRBd(E%Eq%V6w!=!YV62!y|X+&)_bIP&@ATq zPBx7eGTkRqU5S3aIG`u{Q`0H&;7eIF(k8t!APy$`wAtj?%UwOGA$7pIeT}!zTs=)R zu=rh2a0$RQ_yb(x?r;8B@i=Fl{oI%XfCv%<9dMG)Y$24LE+65ChcQYKG#!x03Ru1c zQC^G+YDknic(Fs`ap2#!Wh`&Yn7h0!<+VpMLZln~pW$aPMi2of<)CV-%5bZ1`aPR4 zwF$RofN!6luHobsuKn}TJx^uxRssAQuESNE`{HNV;CA4+l!K{S%2TlR06`3J zPqDwS7red9@622DkM4PQBlR}5hqTEh2p%5BBtS9~$iH?YD-he;vn~+yU!5k78YA z>a1aMkL_U2lUN)vY@a3%6GDl6+8sC$BuYwl(mvou+4UQ#~3-7_+K*cf98L= zS@6PL{>k_4IVOL!m)|UCu$*6m{|bWuV_s@`yhnaXYECf^8?!89P=0=HNM%7PCnpQD zIAdsWYEd!Ml6EIX2(U*EHZ_ z;%j2yw*V3hoB0;(2f1VG=B9OZjC{aAfB`Oqibb(O;q@I<>l!4!x guaS4hX1))x+(64uZ8rVW%*fK{#>~BW&tDfN09&kc6#xJL diff --git a/prisma/migrations/20230217230230_created_at_for_the_review_model/migration.sql b/prisma/migrations/20230217230230_created_at_for_the_review_model/migration.sql new file mode 100644 index 0000000..163c2da --- /dev/null +++ b/prisma/migrations/20230217230230_created_at_for_the_review_model/migration.sql @@ -0,0 +1,17 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_review" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "authorId" INTEGER NOT NULL, + "hotelId" INTEGER NOT NULL, + "rating" INTEGER NOT NULL, + "text" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "review_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "Users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "review_hotelId_fkey" FOREIGN KEY ("hotelId") REFERENCES "Hotels" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_review" ("authorId", "hotelId", "id", "rating", "text") SELECT "authorId", "hotelId", "id", "rating", "text" FROM "review"; +DROP TABLE "review"; +ALTER TABLE "new_review" RENAME TO "review"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/prisma/migrations/20230218005318_make_review_unique_author_id_hotel_id/migration.sql b/prisma/migrations/20230218005318_make_review_unique_author_id_hotel_id/migration.sql new file mode 100644 index 0000000..517cc08 --- /dev/null +++ b/prisma/migrations/20230218005318_make_review_unique_author_id_hotel_id/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[authorId,hotelId]` on the table `review` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "review_authorId_hotelId_key" ON "review"("authorId", "hotelId"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4d81ef9..07be582 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -77,5 +77,6 @@ model review { rating Int text String createdAt DateTime @default(now()) + @@unique([authorId, hotelId]) } diff --git a/src/review/review.controller.ts b/src/review/review.controller.ts index 87631b8..7845ef0 100644 --- a/src/review/review.controller.ts +++ b/src/review/review.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Param, ParseIntPipe, Patch, Post, Req, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Req, UseGuards } from '@nestjs/common'; import { JwtGuard } from 'src/auth/jwt.guard'; import { reviewDto, updateReviewDto } from './review.dto'; import { ReviewService } from './review.service'; @@ -42,7 +42,15 @@ export class ReviewController { } - + @UseGuards(JwtGuard) + @Delete('/:id') + async deleteReview(@Param('id', new ParseIntPipe()) reviewId:number, @Req() req: any){ + return await this.reviewService.deleteReview(reviewId,req.user.id) + } + @Get('average/:id') + async getAverageRating(@Param('id', new ParseIntPipe()) hotelId:number){ + return await this.reviewService.getAverageRating(hotelId) + } diff --git a/src/review/review.dto.ts b/src/review/review.dto.ts index b75b69f..b8fe961 100644 --- a/src/review/review.dto.ts +++ b/src/review/review.dto.ts @@ -17,3 +17,5 @@ export class updateReviewDto{ @IsNotEmpty() hotelId:number } + + diff --git a/src/review/review.service.ts b/src/review/review.service.ts index a1ad769..a33a32a 100644 --- a/src/review/review.service.ts +++ b/src/review/review.service.ts @@ -1,4 +1,4 @@ -import { All, Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { All, ForbiddenException, Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'; import { Prisma } from '@prisma/client'; import { PrismaService } from 'src/prisma/prisma.service'; import { reviewDto, updateReviewDto } from './review.dto' @@ -8,27 +8,35 @@ export class ReviewService { constructor(private prisma:PrismaService){} async createReview(dto: reviewDto, userId: number) { - let hotel = await this.prisma.hotel.findUnique({ - where:{ - id:dto.hotelId + try { + let hotel = await this.prisma.hotel.findUnique({ + where:{ + id:dto.hotelId + } + }) + if (!hotel){ + throw new NotFoundException(`Hotel with ID ${dto.hotelId} not found.`); } - }) - if (!hotel){ - throw new NotFoundException(`Hotel with ID ${dto.hotelId} not found.`); - } - let newReview = await this.prisma.review.create({ - data: { - authorId: userId, - hotelId:dto.hotelId, - rating:dto.rating, - text:dto.text + let newReview = await this.prisma.review.create({ + data: { + authorId: userId, + hotelId:dto.hotelId, + rating:dto.rating, + text:dto.text + } + }); + return { + success:true, + data:newReview } - }); - return { - success:true, - data:newReview - } - } + } catch (error) { + if (error.code === "P2002") { + return { success: false, message: "You have already reviewed this hotel." }; + } + return { success: false, message: error.message }; + } +} + async getAllReviews(){ let AllReviews = await this.prisma.review.findMany(); @@ -74,6 +82,52 @@ export class ReviewService { } - + async deleteReview(id: number, userId: number): Promise { + // Check if review with given id exists + const review = await this.prisma.review.findUnique({ where: { id } }); + if (!review) { + throw new NotFoundException(`Review with id ${id} not found`); + } + + // Check if the user is the author of the review + if (review.authorId !== userId) { + throw new ForbiddenException('You are not authorized to delete this review'); + } + + // Delete the review + return this.prisma.review.delete({ where: { id } }); + } + + + async getAverageRating(hotelId:number):Promise { + const reviews = await this.prisma.review.findMany({ + where: { + hotelId: hotelId, + }, + select: { + rating: true, + }, + }); + if (!reviews){ + return 0; + } + + const numReviews = reviews.length; + const sumRatings = reviews.reduce((acc, review) => acc + review.rating, 0); + + if (numReviews === 0) { + return 0; + } else { + const averageRating = sumRatings / numReviews; + await this.prisma.hotel.update({ + where: { id: hotelId }, + data: { averageRating: averageRating }, + }); + return averageRating; + } + } } + + + From b41b80e2a8836c93cc59c89886d362f03bd19cc9 Mon Sep 17 00:00:00 2001 From: Biniyamseid Date: Sun, 19 Feb 2023 06:58:39 +0300 Subject: [PATCH 3/4] update frontend staff --- public/auth/front.css | 106 +++++++++++++++++++++++++++++ public/auth/script/front.js | 44 ++++++++++++ src/main.ts | 1 + src/review/review.controller.ts | 7 +- src/review/review.service.ts | 8 ++- views/front.hbs | 115 +++++++++++++++++++++----------- 6 files changed, 239 insertions(+), 42 deletions(-) create mode 100644 public/auth/front.css create mode 100644 public/auth/script/front.js diff --git a/public/auth/front.css b/public/auth/front.css new file mode 100644 index 0000000..92ab08a --- /dev/null +++ b/public/auth/front.css @@ -0,0 +1,106 @@ +/* Hero image */ +.hero-image { + background-image: url("https://source.unsplash.com/1600x900/?hotel"); + background-color: #000; + height: 50vh; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + position: relative; + } + + .hero-text { + text-align: center; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + } + + /* Hotel cards */ + .card { + border: none; + margin: 20px; + } + + .card .card-img-top { + object-fit: cover; + height: 200px; + } + + .card-title { + font-size: 24px; + font-weight: 500; + } + + .card-text { + margin-top: 10px; + margin-bottom: 10px; + } + + + .rating { + color:red + } + + /* Star rating */ +.stars { + display: inline-block; + font-size: 18px; + color: #ffd700; + } + + .stars i { + margin-right: 5px; + } + + .half { + position: relative; + } + + .half::before { + content: "\f089"; + font-family: "Font Awesome 5 Free"; + font-weight: 900; + position: absolute; + left: 0; + color: #777; + } + + /* change color of the stars according to rating data */ + .stars-0, + .stars-1, + .stars-2, + .stars-3, + .stars-4, + .stars-5 { + color: #b3b3b3; + } + + .stars-1 i:nth-child(-n + 1), + .stars-2 i:nth-child(-n + 2), + .stars-3 i:nth-child(-n + 3), + .stars-4 i:nth-child(-n + 4), + .stars-5 i:nth-child(-n + 5) { + color: #ffd700; + } + + .stars-0.half, + .stars-1.half, + .stars-2.half, + .stars-3.half, + .stars-4.half, + .stars-5.half { + color: #ffd700; + } + + .stars-0.half::before, + .stars-1.half::before, + .stars-2.half::before, + .stars-3.half::before, + .stars-4.half::before, + .stars-5.half::before { + color: #b3b3b3; + } + diff --git a/public/auth/script/front.js b/public/auth/script/front.js new file mode 100644 index 0000000..bc13f99 --- /dev/null +++ b/public/auth/script/front.js @@ -0,0 +1,44 @@ +let hotelurl; +hotelurl = 'http://localhost:3000/upload/'; +JavaScript: + +$(document).ready(() => { + // fetch data from API and generate hotel cards + fetch("http://localhost:3000/review/a",{ + method:'Put' + }) + .then(response => response.json()) + .then(data => { + const hotelCards = data.map(hotel => { + // calculate star rating data + const rating = hotel.averageRating; + const fullStars = Math.floor(rating); + const hasHalfStar = rating % 1 >= 0.5; + const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0); + // generate star rating HTML + const starRatingHtml = ` +
+ ${Array(fullStars).fill().map(() => '').join("")} + ${hasHalfStar ? '' : ""} + ${Array(emptyStars).fill().map(() => '').join("")} +
+ `; + // generate hotel card HTML + + return ` +
+
+ ${hotel.hotel_name} +
+
${hotel.hotel_name}
+

${hotel.user_name}

+
${starRatingHtml}
+
+
+
+ `; + }).join(""); + // insert hotel cards into DOM + $("#hotel-cards").html(hotelCards); + }); +}); diff --git a/src/main.ts b/src/main.ts index 6011280..d81542d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,7 @@ async function bootstrap() { const app = await NestFactory.create( AppModule, ); + app.enableCors(); app.useGlobalPipes( new ValidationPipe({ diff --git a/src/review/review.controller.ts b/src/review/review.controller.ts index 7845ef0..943758c 100644 --- a/src/review/review.controller.ts +++ b/src/review/review.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Req, UseGuards } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Put, Req, UseGuards } from '@nestjs/common'; import { JwtGuard } from 'src/auth/jwt.guard'; import { reviewDto, updateReviewDto } from './review.dto'; import { ReviewService } from './review.service'; @@ -52,6 +52,11 @@ export class ReviewController { return await this.reviewService.getAverageRating(hotelId) } + @Put('a') + async getAllHotels(){ + return await this.reviewService.getAllHotels() + } + } diff --git a/src/review/review.service.ts b/src/review/review.service.ts index a33a32a..b8304ac 100644 --- a/src/review/review.service.ts +++ b/src/review/review.service.ts @@ -35,9 +35,7 @@ export class ReviewService { } return { success: false, message: error.message }; } -} - - +} async getAllReviews(){ let AllReviews = await this.prisma.review.findMany(); return { @@ -126,6 +124,10 @@ export class ReviewService { return averageRating; } } + + async getAllHotels(){ + return this.prisma.hotel.findMany() + } } diff --git a/views/front.hbs b/views/front.hbs index 302065c..c9d812c 100644 --- a/views/front.hbs +++ b/views/front.hbs @@ -1,44 +1,83 @@ + - - - - - - - - - - - - Maderia - - -
-
- + + + +
+ + + + + + + "> + + + -
-

Find your next stay

-

- search low price on hotels, homes and much more... -

-
- -
- - \ No newline at end of file From 3cfa7e02154155a937d870af0b2fc7d7241ba10b Mon Sep 17 00:00:00 2001 From: Biniyamseid Date: Mon, 20 Feb 2023 10:43:12 +0300 Subject: [PATCH 4/4] remove @@unique from review schema --- prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b8ffa56..79f8a1d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -79,5 +79,5 @@ model review { rating Int text String createdAt DateTime @default(now()) - @@unique([authorId, hotelId]) + // @@unique([authorId, hotelId]) it should be unique }