From f442b741afe8e19014becc115eea4100ae7c522a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Iv=C3=A1n?= <80365304+Diego-lvan@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:37:02 -0600 Subject: [PATCH 1/7] agregando resouce visited --- backend/src/app.module.ts | 4 ++ backend/src/visited/dto/create-visited.dto.ts | 12 +++++ backend/src/visited/dto/update-visited.dto.ts | 4 ++ .../src/visited/entities/visited.entity.ts | 23 +++++++++ backend/src/visited/visited.controller.ts | 40 +++++++++++++++ backend/src/visited/visited.module.ts | 21 ++++++++ backend/src/visited/visited.service.ts | 50 +++++++++++++++++++ 7 files changed, 154 insertions(+) create mode 100644 backend/src/visited/dto/create-visited.dto.ts create mode 100644 backend/src/visited/dto/update-visited.dto.ts create mode 100644 backend/src/visited/entities/visited.entity.ts create mode 100644 backend/src/visited/visited.controller.ts create mode 100644 backend/src/visited/visited.module.ts create mode 100644 backend/src/visited/visited.service.ts diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 84f2a6b0..0792d3cc 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -30,6 +30,8 @@ import { RouteModule } from './route/route.module'; import { Route } from './route/entities/route.entity'; import { TravelPlaceModule } from './travel-place/travel-place.module'; import { TravelPlace } from './travel-place/entities/travel-place.entity'; +import { VisitedModule } from './visited/visited.module'; +import { Visited } from './visited/entities/visited.entity'; @Module({ imports: [ @@ -54,6 +56,7 @@ import { TravelPlace } from './travel-place/entities/travel-place.entity'; Category, Route, TravelPlace, + Visited, ], synchronize: DbConstants.DB_SYNC, logging: false, @@ -73,6 +76,7 @@ import { TravelPlace } from './travel-place/entities/travel-place.entity'; CategoryModule, RouteModule, TravelPlaceModule, + VisitedModule, ], controllers: [AppController], providers: [AppService, DatabaseSeederModule], diff --git a/backend/src/visited/dto/create-visited.dto.ts b/backend/src/visited/dto/create-visited.dto.ts new file mode 100644 index 00000000..60f86a2f --- /dev/null +++ b/backend/src/visited/dto/create-visited.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateVisitedDto { + @ApiProperty() + idPlace: number; + @ApiProperty() + email: string; + @ApiProperty({ type: 'number', description: 'Rating from 1 to 5' }) + rating: number; + @ApiProperty() + date: Date; +} diff --git a/backend/src/visited/dto/update-visited.dto.ts b/backend/src/visited/dto/update-visited.dto.ts new file mode 100644 index 00000000..1c9af490 --- /dev/null +++ b/backend/src/visited/dto/update-visited.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateVisitedDto } from './create-visited.dto'; + +export class UpdateVisitedDto extends PartialType(CreateVisitedDto) {} diff --git a/backend/src/visited/entities/visited.entity.ts b/backend/src/visited/entities/visited.entity.ts new file mode 100644 index 00000000..65a92aff --- /dev/null +++ b/backend/src/visited/entities/visited.entity.ts @@ -0,0 +1,23 @@ +import { Place } from 'src/place/entities/place.entity'; +import { User } from 'src/user/entities/user.entity'; +import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; + +@Entity() +export class Visited { + @PrimaryGeneratedColumn() + idVisited: number; + + @ManyToOne(() => Place, (place) => place.idPlace, { eager: true }) + @JoinColumn({ name: 'place' }) + place: Place; + + @ManyToOne(() => User, (user) => user.userId) + @JoinColumn({ name: 'user' }) + user: User; + + @Column({ nullable: false }) + rating: number; + + @Column({ nullable: false }) + date: Date; +} diff --git a/backend/src/visited/visited.controller.ts b/backend/src/visited/visited.controller.ts new file mode 100644 index 00000000..ea96456f --- /dev/null +++ b/backend/src/visited/visited.controller.ts @@ -0,0 +1,40 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { VisitedService } from './visited.service'; +import { CreateVisitedDto } from './dto/create-visited.dto'; +import { ApiTags } from '@nestjs/swagger'; +// import { UpdateVisitedDto } from './dto/update-visited.dto'; + +@Controller('visited') +@ApiTags('Visited places') +export class VisitedController { + constructor(private readonly visitedService: VisitedService) {} + + @Post() + async create(@Body() createVisitedDto: CreateVisitedDto) { + try { + return await this.visitedService.create(createVisitedDto); + } catch (e) { + return e; + } + } + + @Get() + findAll() { + return this.visitedService.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.visitedService.findOne(+id); + } + + // @Patch(':id') + // update(@Param('id') id: string, @Body() updateVisitedDto: UpdateVisitedDto) { + // return this.visitedService.update(+id, updateVisitedDto); + // } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.visitedService.remove(+id); + } +} diff --git a/backend/src/visited/visited.module.ts b/backend/src/visited/visited.module.ts new file mode 100644 index 00000000..022aaf56 --- /dev/null +++ b/backend/src/visited/visited.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; +import { VisitedService } from './visited.service'; +import { VisitedController } from './visited.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserService } from 'src/user/user.service'; +import { PlaceService } from 'src/place/place.service'; +import { User } from 'src/user/entities/user.entity'; +import { Place } from 'src/place/entities/place.entity'; +import { Visited } from './entities/visited.entity'; +import { CategoryService } from 'src/category/category.service'; +import { Category } from 'src/category/entities/category.entity'; +import { AvailableDate } from 'src/place/entities/available-date.entity'; +import { PlaceTraduction } from 'src/place/entities/place-traduction.entity'; +import { Town } from 'src/town/entities/town.entity'; + +@Module({ + controllers: [VisitedController], + providers: [VisitedService, UserService, PlaceService, CategoryService], + imports: [TypeOrmModule.forFeature([User, Place, Visited, Category, AvailableDate, PlaceTraduction, Town])], +}) +export class VisitedModule {} diff --git a/backend/src/visited/visited.service.ts b/backend/src/visited/visited.service.ts new file mode 100644 index 00000000..c4a4f686 --- /dev/null +++ b/backend/src/visited/visited.service.ts @@ -0,0 +1,50 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Visited } from './entities/visited.entity'; +import { Repository } from 'typeorm'; +import { User } from 'src/user/entities/user.entity'; +import { UserService } from 'src/user/user.service'; +import { CreateVisitedDto } from './dto/create-visited.dto'; +import { Place } from 'src/place/entities/place.entity'; +import { PlaceService } from 'src/place/place.service'; +import { UpdateVisitedDto } from './dto/update-visited.dto'; + +@Injectable() +export class VisitedService { + constructor( + @InjectRepository(Visited) private visitedRepository: Repository, + private readonly userService: UserService, + private readonly placeService: PlaceService, + ) {} + + async create(createVisitedDto: CreateVisitedDto) { + const place: Place = await this.placeService.findOne(createVisitedDto.idPlace); + const user: User = await this.userService.findOne(createVisitedDto.email); + if (!place || !user) throw new BadRequestException('Place or user not found'); + await this.visitedRepository.save({ place, user, rating: createVisitedDto.rating, date: createVisitedDto.date }); + } + + async getVisitedByUser(email: string): Promise { + const user: User = await this.userService.findOne(email); + return this.visitedRepository.find({ + where: { user }, + relations: ['place', 'place.categories'], + }); + } + + findAll() { + return `This action returns all visited`; + } + + findOne(id: number) { + return `This action returns a #${id} visited`; + } + + // update(id: number, updateVisitedDto: UpdateVisitedDto) { + // return `This action updates a #${id} visited`; + // } + + remove(id: number) { + return `This action removes a #${id} visited`; + } +} -- GitLab From d1ded5547dc9dccc1606c201bf89b6e48bbe6958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Iv=C3=A1n?= <80365304+Diego-lvan@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:37:21 -0600 Subject: [PATCH 2/7] renombrando grade a rating --- backend/src/route/dto/recommend-route.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/route/dto/recommend-route.dto.ts b/backend/src/route/dto/recommend-route.dto.ts index 66e99e40..eddd3c78 100644 --- a/backend/src/route/dto/recommend-route.dto.ts +++ b/backend/src/route/dto/recommend-route.dto.ts @@ -3,5 +3,5 @@ export interface RecommendPlace { openAt: number; closeAt: number; categories: number[]; - grade: number; + rating: number; } -- GitLab From 9f0ed4edaae259e5e0b8486a46fbcecc70b1f43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Iv=C3=A1n?= <80365304+Diego-lvan@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:37:48 -0600 Subject: [PATCH 3/7] renombrando grade a rating --- backend/src/route/utils/recommendations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/route/utils/recommendations.ts b/backend/src/route/utils/recommendations.ts index 5835dcec..88ee597c 100644 --- a/backend/src/route/utils/recommendations.ts +++ b/backend/src/route/utils/recommendations.ts @@ -27,7 +27,7 @@ export class RecommendationsSystem { rankVisited(visited: RecommendPlace[]): Series { const visitedCategories = this.getCategories(visited); const visitedEncoded = this.oneHotEncode(visitedCategories, visited); - const grades = visited.map((place) => place.grade); + const grades = visited.map((place) => place.rating); const dataframe: DataFrame = visitedEncoded; @@ -56,7 +56,7 @@ export class RecommendationsSystem { }, 0); }); - let ranked: [number, number][] = candidates.map((place, i) => [place.idPlace, result[i]]); + const ranked: [number, number][] = candidates.map((place, i) => [place.idPlace, result[i]]); ranked.sort(customSort); return ranked; -- GitLab From 81627054827ca3fbc3c47c900d909b6030361d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Iv=C3=A1n?= <80365304+Diego-lvan@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:38:08 -0600 Subject: [PATCH 4/7] agregando servicio que recomienda una ruta --- backend/src/route/route.service.ts | 47 ++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/backend/src/route/route.service.ts b/backend/src/route/route.service.ts index c5da153b..5d359bfe 100644 --- a/backend/src/route/route.service.ts +++ b/backend/src/route/route.service.ts @@ -9,6 +9,12 @@ import { Town } from 'src/town/entities/town.entity'; import { TravelPlaceService } from 'src/travel-place/travel-place.service'; import { TravelPlace } from 'src/travel-place/entities/travel-place.entity'; import { RecommendationsSystem } from './utils/recommendations'; +import { PlaceService } from 'src/place/place.service'; +import { LANGUAGES } from 'src/shared/enum/languages.enum'; +import { VisitedService } from 'src/visited/visited.service'; +import { GetPlaceDto } from 'src/place/dto/get-place.dto'; +import { Visited } from 'src/visited/entities/visited.entity'; +import { RecommendPlace } from './dto/recommend-route.dto'; @Injectable() export class RouteService { @@ -18,10 +24,12 @@ export class RouteService { @InjectRepository(Town) private townRepository: Repository, private readonly travelPlaceService: TravelPlaceService, @InjectDataSource() private dataSource: DataSource, + private readonly placeService: PlaceService, + private readonly visitedService: VisitedService, ) {} - async create() { - const user: User = await this.userRepository.findOneBy({ userId: 1 }); - const town: Town = await this.townRepository.findOneBy({ townId: 1 }); + private async createRoute(idUser: number, idTown: number) { + const user: User = await this.userRepository.findOneBy({ userId: idUser }); + const town: Town = await this.townRepository.findOneBy({ townId: idTown }); await this.routeRepository.save({ user, town, startDate: new Date(), endDate: new Date() }); await this.travelPlaceService.create({ idRoute: 1, @@ -54,10 +62,37 @@ export class RouteService { return `This action removes a #${id} route`; } - recommend() { + async recommend(idTown: number, email: string, language: LANGUAGES) { // Obtener los visitados y los candidatos + const places: GetPlaceDto[] = await this.placeService.findAllByTown(idTown, language); + const visited: Visited[] = await this.visitedService.getVisitedByUser(email); + const placesMapped: RecommendPlace[] = places.map((place) => { + return { + idPlace: place.idPlace, + openAt: place.openAt, + closeAt: place.closeAt, + categories: place.categories.map((category) => category.idCategory), + rating: 0, + }; + }); + + const visitedMapped: RecommendPlace[] = visited.map((visit) => { + return { + idPlace: visit.place.idPlace, + openAt: visit.place.openAt, + closeAt: visit.place.closeAt, + categories: visit.place.categories.map((category) => category.idCategory), + rating: visit.rating, + }; + }); + const system = new RecommendationsSystem(); - // system.recommend(visited, candidates); + const chosen: [number, number][] = system.recommend(visitedMapped, placesMapped); + const placesChooen: GetPlaceDto[] = []; + for (const [index] of chosen) { + placesChooen.push(await this.placeService.findOneAndTradAndAvailable(index, LANGUAGES.EN)); + } + + return placesChooen; } - } -- GitLab From 70714ad53d79fb61b5a3090d02d5e2039274bfe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Iv=C3=A1n?= <80365304+Diego-lvan@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:38:25 -0600 Subject: [PATCH 5/7] agregando imports a route module --- backend/src/route/route.module.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/backend/src/route/route.module.ts b/backend/src/route/route.module.ts index a9bacc34..e3d69a67 100644 --- a/backend/src/route/route.module.ts +++ b/backend/src/route/route.module.ts @@ -14,6 +14,11 @@ import { Town } from 'src/town/entities/town.entity'; import { TravelPlace } from 'src/travel-place/entities/travel-place.entity'; import { TravelPlaceService } from 'src/travel-place/travel-place.service'; import { Place } from 'src/place/entities/place.entity'; +import { PlaceService } from 'src/place/place.service'; +import { AvailableDate } from 'src/place/entities/available-date.entity'; +import { PlaceTraduction } from 'src/place/entities/place-traduction.entity'; +import { VisitedService } from 'src/visited/visited.service'; +import { Visited } from 'src/visited/entities/visited.entity'; @Module({ controllers: [RouteController], @@ -25,7 +30,21 @@ import { Place } from 'src/place/entities/place.entity'; EncryptionService, CategoryService, TravelPlaceService, + PlaceService, + VisitedService, + ], + imports: [ + TypeOrmModule.forFeature([ + Route, + User, + Category, + Town, + TravelPlace, + Place, + AvailableDate, + PlaceTraduction, + Visited, + ]), ], - imports: [TypeOrmModule.forFeature([Route, User, Category, Town, TravelPlace, Place])], }) export class RouteModule {} -- GitLab From 8dafe7a6e210da015b4c6b2d6f6566515e6d152b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Iv=C3=A1n?= <80365304+Diego-lvan@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:39:21 -0600 Subject: [PATCH 6/7] agregando tipo de dato de lo que retorna la funcion findAllByTown --- backend/src/place/place.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/place/place.service.ts b/backend/src/place/place.service.ts index 904aaa4c..784232d0 100644 --- a/backend/src/place/place.service.ts +++ b/backend/src/place/place.service.ts @@ -79,7 +79,7 @@ export class PlaceService { } } - async findAllByTown(idTown: number, lang: LANGUAGES) { + async findAllByTown(idTown: number, lang: LANGUAGES): Promise { const res: any[] = await this.dataSource .getRepository(Place) .createQueryBuilder('place') -- GitLab From 70681b6138078d3d0b1620d6067ad84fcaaf4a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Iv=C3=A1n?= <80365304+Diego-lvan@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:39:39 -0600 Subject: [PATCH 7/7] agregando endpoint que recomienda una ruta --- backend/src/route/route.controller.ts | 61 ++++++++++++++------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/backend/src/route/route.controller.ts b/backend/src/route/route.controller.ts index 0d8c512f..b3162fc3 100644 --- a/backend/src/route/route.controller.ts +++ b/backend/src/route/route.controller.ts @@ -1,43 +1,46 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common'; +import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Req, Query } from '@nestjs/common'; import { RouteService } from './route.service'; import { CreateRouteDto } from './dto/create-route.dto'; import { UpdateRouteDto } from './dto/update-route.dto'; -import { ApiConsumes, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiConsumes, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; +import { LANGUAGES } from 'src/shared/enum/languages.enum'; +import { AuthUserGuard } from 'src/auth/user/authUser.guard'; +import { CustomUserRequest } from 'src/auth/user/interface/customUserReq'; @Controller('route') -@ApiTags('routes') +@ApiTags('route') export class RouteController { constructor(private readonly routeService: RouteService) {} - @Post() - @ApiConsumes('multipart/form-data') - // @UseGuards(AuthUserGuard) - async create(@Body() createRouteDto: CreateRouteDto) { - return await this.routeService.create(); - } - - @Get() - findAll() { - return this.routeService.findAll(); - } + // @Post() + // @ApiConsumes('multipart/form-data') + // // @UseGuards(AuthUserGuard) + // async create(@Body() createRouteDto: CreateRouteDto) { + // return await this.routeService.recommend(); + // } - @Get(':idRoute') - async findOne(@Param('idRoute') idRoute: number) { - return await this.routeService.findOne(idRoute); - } + // @Get(':idRoute') + // async findOne(@Param('idRoute') idRoute: number) { + // return await this.routeService.findOne(idRoute); + // } - @Patch(':id') - update(@Param('id') id: string, @Body() updateRouteDto: UpdateRouteDto) { - return this.routeService.update(+id, updateRouteDto); - } + // @Patch(':id') + // update(@Param('id') id: string, @Body() updateRouteDto: UpdateRouteDto) { + // return this.routeService.update(+id, updateRouteDto); + // } - @Delete(':id') - remove(@Param('id') id: string) { - return this.routeService.remove(+id); - } + // @Delete(':id') + // remove(@Param('id') id: string) { + // return this.routeService.remove(+id); + // } - @Get('recommend') - recommendRoute() { - return this.routeService.recommend(); + @Get('recommend:idTown') + @ApiQuery({ name: 'lang', type: String }) + @ApiParam({ name: 'idTown', type: Number }) + @ApiBearerAuth('jwt') + @UseGuards(AuthUserGuard) + async recommendRoute(@Req() req: CustomUserRequest, @Query('lang') lang: string, @Param('idTown') idTown: number) { + const { email } = req.user; + return await this.routeService.recommend(idTown, email, lang as LANGUAGES); } } -- GitLab