Ajout endpoint géolocalisation

This commit is contained in:
Emmy D'Anello 2024-12-07 15:11:47 +01:00
parent 1ae6b6634c
commit 86427bb41b
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
13 changed files with 240 additions and 21 deletions

View File

@ -0,0 +1,14 @@
/*
Warnings:
- Added the required column `accuracy` to the `Geolocation` table without a default value. This is not possible if the table is not empty.
- Added the required column `altitude` to the `Geolocation` table without a default value. This is not possible if the table is not empty.
- Added the required column `altitudeAccuracy` to the `Geolocation` table without a default value. This is not possible if the table is not empty.
- Added the required column `speed` to the `Geolocation` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Geolocation" ADD COLUMN "accuracy" DOUBLE PRECISION NOT NULL,
ADD COLUMN "altitude" DOUBLE PRECISION NOT NULL,
ADD COLUMN "altitudeAccuracy" DOUBLE PRECISION NOT NULL,
ADD COLUMN "speed" DOUBLE PRECISION NOT NULL;

View File

@ -21,11 +21,15 @@ model User {
model Geolocation { model Geolocation {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int userId Int
longitude Float longitude Float
latitude Float latitude Float
speed Float
accuracy Float
altitude Float
altitudeAccuracy Float
timestamp DateTime timestamp DateTime
user User @relation(fields: [userId], references: [id])
} }
model Challenge { model Challenge {
@ -38,17 +42,18 @@ model Challenge {
model ChallengeAction { model ChallengeAction {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int userId Int
challenge Challenge @relation(fields: [challengeId], references: [id])
challengeId Int @unique challengeId Int @unique
active Boolean @default(false) active Boolean @default(false)
success Boolean @default(false) success Boolean @default(false)
challenge Challenge @relation(fields: [challengeId], references: [id])
user User @relation(fields: [userId], references: [id])
moneyUpdate MoneyUpdate? moneyUpdate MoneyUpdate?
} }
model TrainTrip { model TrainTrip {
id String @id id String @id
user User @relation(fields: [userId], references: [id])
userId Int userId Int
distance Float distance Float
from String from String
@ -58,20 +63,19 @@ model TrainTrip {
infoJson Json infoJson Json
geometry String geometry String
moneyUpdate MoneyUpdate? moneyUpdate MoneyUpdate?
user User @relation(fields: [userId], references: [id])
} }
model MoneyUpdate { model MoneyUpdate {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int userId Int
before Int before Int
after Int after Int
reason MoneyUpdateType reason MoneyUpdateType
actionId Int? @unique
tripId String? @unique
action ChallengeAction? @relation(fields: [actionId], references: [id]) action ChallengeAction? @relation(fields: [actionId], references: [id])
actionId Int? @unique
trip TrainTrip? @relation(fields: [tripId], references: [id]) trip TrainTrip? @relation(fields: [tripId], references: [id])
user User @relation(fields: [userId], references: [id]) tripId String? @unique
} }
enum MoneyUpdateType { enum MoneyUpdateType {

View File

@ -3,9 +3,10 @@ import { PrismaService } from './prisma/prisma.service'
import { PrismaModule } from './prisma/prisma.module' import { PrismaModule } from './prisma/prisma.module'
import { UsersModule } from './users/users.module' import { UsersModule } from './users/users.module'
import { AuthModule } from './auth/auth.module' import { AuthModule } from './auth/auth.module'
import { GeolocationsModule } from './geolocations/geolocations.module'
@Module({ @Module({
imports: [PrismaModule, UsersModule, AuthModule], imports: [PrismaModule, UsersModule, AuthModule, GeolocationsModule],
providers: [PrismaService], providers: [PrismaService],
}) })
export class AppModule {} export class AppModule {}

View File

@ -1,5 +1,9 @@
import { Injectable } from '@nestjs/common' import { Injectable } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport' import { AuthGuard } from '@nestjs/passport'
import { User } from '@prisma/client'
import { Request } from 'express'
@Injectable() @Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {} export class JwtAuthGuard extends AuthGuard('jwt') {}
export type AuthenticatedRequest = Request & { user: User }

View File

@ -0,0 +1,24 @@
import { ApiProperty } from "@nestjs/swagger"
export class CreateGeolocationDto {
@ApiProperty({description: "Longitude en degrés"})
longitude: number
@ApiProperty({description: "Latitude en degrés"})
latitude: number
@ApiProperty({description: "Vitesse en mètres par seconde"})
speed: number
@ApiProperty({description: "Précision en mètres de la position obtenue"})
accuracy: number
@ApiProperty({description: "Altitude en mètres"})
altitude: number
@ApiProperty({description: "Précision de l'altitude en mètres"})
altitudeAccuracy: number
@ApiProperty({description: "Date et heure de capture de la géolocalisation"})
timestamp: Date
}

View File

@ -0,0 +1,35 @@
import { ApiProperty } from "@nestjs/swagger"
import { Geolocation } from "@prisma/client"
export class GeolocationEntity implements Geolocation {
constructor(partial: Partial<GeolocationEntity>) {
Object.assign(this, partial)
}
@ApiProperty({description: "Identifiant unique"})
id: number
@ApiProperty({description: "Utilisateur⋅rice ayant émis la géolocalisation"})
userId: number
@ApiProperty({description: "Longitude en degrés"})
longitude: number
@ApiProperty({description: "Latitude en degrés"})
latitude: number
@ApiProperty({description: "Vitesse en mètres par seconde"})
speed: number
@ApiProperty({description: "Précision en mètres de la position obtenue"})
accuracy: number
@ApiProperty({description: "Altitude en mètres"})
altitude: number
@ApiProperty({description: "Précision de l'altitude en mètres"})
altitudeAccuracy: number
@ApiProperty({description: "Date et heure de capture de la géolocalisation"})
timestamp: Date
}

View File

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing'
import { GeolocationsController } from './geolocations.controller'
import { GeolocationsService } from './geolocations.service'
describe('GeolocationsController', () => {
let controller: GeolocationsController
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [GeolocationsController],
providers: [GeolocationsService],
}).compile()
controller = module.get<GeolocationsController>(GeolocationsController)
})
it('should be defined', () => {
expect(controller).toBeDefined()
})
})

View File

@ -0,0 +1,63 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, ParseIntPipe, UseGuards, HttpCode, Req, NotFoundException } from '@nestjs/common'
import { GeolocationsService } from './geolocations.service'
import { CreateGeolocationDto } from './dto/create-geolocation.dto'
import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { ApiBearerAuth, ApiCreatedResponse, ApiForbiddenResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger'
import { GeolocationEntity } from './entities/geolocation.entity'
@Controller('geolocations')
export class GeolocationsController {
constructor(private readonly geolocationsService: GeolocationsService) {}
@Post()
@HttpCode(201)
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiCreatedResponse({ type: GeolocationEntity, description: "Objet créé avec succès" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Object non trouvé" })
async create(@Req() request: AuthenticatedRequest, @Body() createGeolocationDto: CreateGeolocationDto): Promise<GeolocationEntity> {
const user = request.user
const geolocation = await this.geolocationsService.create(user, createGeolocationDto)
return new GeolocationEntity(geolocation)
}
@Get()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: GeolocationEntity, isArray: true })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async findAll(): Promise<GeolocationEntity[]> {
const geolocations = await this.geolocationsService.findAll()
return geolocations.map(geolocation => new GeolocationEntity(geolocation))
}
@Get(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: GeolocationEntity })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async findOne(@Param('id', ParseIntPipe) id: number): Promise<GeolocationEntity> {
const geolocation = await this.geolocationsService.findOne(+id)
if (!geolocation)
throw new NotFoundException(`Géolocalisation inexistante avec l'identifiant ${id}`)
return new GeolocationEntity(geolocation)
}
@Delete(':id')
@HttpCode(204)
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiNoContentResponse({ description: "Objet supprimé avec succès" })
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" })
@ApiNotFoundResponse({ description: "Objet non trouvé" })
async remove(@Param('id', ParseIntPipe) id: number): Promise<void> {
await this.geolocationsService.remove(+id)
}
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common'
import { GeolocationsService } from './geolocations.service'
import { GeolocationsController } from './geolocations.controller'
import { PrismaModule } from 'src/prisma/prisma.module'
@Module({
controllers: [GeolocationsController],
providers: [GeolocationsService],
imports: [PrismaModule],
})
export class GeolocationsModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing'
import { GeolocationsService } from './geolocations.service'
describe('GeolocationsService', () => {
let service: GeolocationsService
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [GeolocationsService],
}).compile()
service = module.get<GeolocationsService>(GeolocationsService)
})
it('should be defined', () => {
expect(service).toBeDefined()
})
})

View File

@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common'
import { CreateGeolocationDto } from './dto/create-geolocation.dto'
import { PrismaService } from 'src/prisma/prisma.service'
import { Geolocation, User } from '@prisma/client'
@Injectable()
export class GeolocationsService {
constructor(private prisma: PrismaService) { }
async create(authenticatedUser: User, createGeolocationDto: CreateGeolocationDto): Promise<Geolocation> {
const data = { ...createGeolocationDto, userId: authenticatedUser.id }
return await this.prisma.geolocation.create({ data: data })
}
async findAll(): Promise<Geolocation[]> {
return await this.prisma.geolocation.findMany()
}
async findOne(id: number): Promise<Geolocation> {
return await this.prisma.geolocation.findUnique({ where: { id } })
}
async remove(id: number): Promise<Geolocation> {
return await this.prisma.geolocation.delete({ where: { id } })
}
}

View File

@ -1,13 +1,10 @@
import { Body, Controller, Get, HttpCode, NotFoundException, Param, ParseIntPipe, Patch, Post, Req, UseGuards } from '@nestjs/common' import { Body, Controller, Get, HttpCode, NotFoundException, Param, ParseIntPipe, Patch, Req, UseGuards } from '@nestjs/common'
import { UsersService } from './users.service' import { UsersService } from './users.service'
import { ApiBadRequestResponse, ApiBearerAuth, ApiForbiddenResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger' import { ApiBadRequestResponse, ApiBearerAuth, ApiForbiddenResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger'
import { UserEntity } from './entities/user.entity' import { UserEntity } from './entities/user.entity'
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard' import { AuthenticatedRequest, JwtAuthGuard } from 'src/auth/jwt-auth.guard'
import { User } from '@prisma/client'
import { UpdatePasswordDto } from './dto/user_password.dto' import { UpdatePasswordDto } from './dto/user_password.dto'
export type AuthenticatedRequest = Request & { user: User }
@Controller('users') @Controller('users')
export class UsersController { export class UsersController {
constructor(private readonly usersService: UsersService) {} constructor(private readonly usersService: UsersService) {}
@ -45,8 +42,8 @@ export class UsersController {
@ApiBadRequestResponse({description: "Erreur dans la saisie du nouveau mot de passe."}) @ApiBadRequestResponse({description: "Erreur dans la saisie du nouveau mot de passe."})
@ApiUnauthorizedResponse({ description: "Non authentifié⋅e" }) @ApiUnauthorizedResponse({ description: "Non authentifié⋅e" })
@ApiForbiddenResponse({ description: "Permission refusée" }) @ApiForbiddenResponse({ description: "Permission refusée" })
async updatePassword(@Req() request: AuthenticatedRequest, @Body() { password }: UpdatePasswordDto) { async updatePassword(@Req() request: AuthenticatedRequest, @Body() body: UpdatePasswordDto) {
const user = request.user const user = request.user
await this.usersService.updatePassword(user, password) await this.usersService.updatePassword(user, body)
} }
} }

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'
import { User } from '@prisma/client' import { User } from '@prisma/client'
import { PrismaService } from 'src/prisma/prisma.service' import { PrismaService } from 'src/prisma/prisma.service'
import * as bcrypt from 'bcrypt' import * as bcrypt from 'bcrypt'
import { UpdatePasswordDto } from './dto/user_password.dto'
@Injectable() @Injectable()
export class UsersService { export class UsersService {
@ -15,7 +16,7 @@ export class UsersService {
return await this.prisma.user.findUnique({ where: { id } }) return await this.prisma.user.findUnique({ where: { id } })
} }
async updatePassword(user: User, password: string) { async updatePassword(user: User, { password }: UpdatePasswordDto) {
const hashedPassword = await bcrypt.hash(password, 10) const hashedPassword = await bcrypt.hash(password, 10)
await this.prisma.user.update({ await this.prisma.user.update({
where: { id: user.id }, where: { id: user.id },