add location logging, display last location on map

Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
This commit is contained in:
Nicolas Froger 2024-07-26 15:36:19 +02:00
commit 130581a411
No known key found for this signature in database
13 changed files with 386 additions and 3 deletions

View file

@ -0,0 +1,22 @@
package fr.kektus.summer2024.converters;
import fr.kektus.summer2024.data.model.Location;
import fr.kektus.summer2024.domain.entity.LocationEntity;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class LocationConverter implements Converter<Location, LocationEntity> {
@Override public Location toLeft(LocationEntity locationEntity) {
return new Location().withId(locationEntity.id)
.withLatitude(locationEntity.latitude)
.withLongitude(locationEntity.longitude)
.withTimestamp(locationEntity.timestamp);
}
@Override public LocationEntity toRight(Location location) {
return new LocationEntity().withId(location.id)
.withLatitude(location.latitude)
.withLongitude(location.longitude)
.withTimestamp(location.timestamp);
}
}

View file

@ -0,0 +1,20 @@
package fr.kektus.summer2024.data.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.With;
import java.time.ZonedDateTime;
@Entity
@AllArgsConstructor @NoArgsConstructor @With
public class Location {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long id;
public ZonedDateTime timestamp;
public Float latitude;
public Float longitude;
}

View file

@ -0,0 +1,9 @@
package fr.kektus.summer2024.data.repository;
import fr.kektus.summer2024.data.model.Location;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class LocationRepository implements PanacheRepository<Location> {
}

View file

@ -0,0 +1,15 @@
package fr.kektus.summer2024.domain.entity;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.With;
import java.time.ZonedDateTime;
@AllArgsConstructor @NoArgsConstructor @With
public class LocationEntity {
public Long id;
public ZonedDateTime timestamp;
public Float latitude;
public Float longitude;
}

View file

@ -0,0 +1,35 @@
package fr.kektus.summer2024.domain.service;
import fr.kektus.summer2024.converters.LocationConverter;
import fr.kektus.summer2024.data.model.Location;
import fr.kektus.summer2024.data.repository.LocationRepository;
import fr.kektus.summer2024.domain.entity.LocationEntity;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.time.ZonedDateTime;
@ApplicationScoped
public class LocationService {
@Inject LocationRepository locationRepository;
@Inject LocationConverter locationConverter;
@Transactional
public LocationEntity createLocation(Float lat, Float lon) {
final Location location = new Location().withLatitude(lat)
.withLongitude(lon)
.withTimestamp(ZonedDateTime.now());
locationRepository.persist(location);
return locationConverter.toRight(location);
}
public LocationEntity getLatestLocation() {
final Location lastLocation = locationRepository.findAll(Sort.descending("id")).firstResult();
if (lastLocation == null)
throw new NotFoundException();
return locationConverter.toRight(lastLocation);
}
}

View file

@ -0,0 +1,47 @@
package fr.kektus.summer2024.presentation.rest;
import fr.kektus.summer2024.Validators;
import fr.kektus.summer2024.domain.entity.LocationEntity;
import fr.kektus.summer2024.domain.service.AuthService;
import fr.kektus.summer2024.domain.service.LocationService;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.With;
import org.jboss.resteasy.reactive.RestHeader;
@Path("/admin/location")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class AdminLocationApi {
@Inject AuthService authService;
@Inject LocationService locationService;
@POST
public LocationEntity createPost(@RestHeader("X-admin-token") final String adminToken,
CreateLocation.Request request) {
if (!authService.isAdminTokenValid(adminToken))
throw new NotAuthorizedException("provided admin token is invalid");
Validators.assertNotNull(request, new BadRequestException());
Validators.assertNotNull(request.latitude, new BadRequestException());
Validators.assertNotNull(request.longitude, new BadRequestException());
return locationService.createLocation(request.latitude, request.longitude);
}
public static class CreateLocation {
@NoArgsConstructor @AllArgsConstructor @With
public static class Request {
public Float latitude;
public Float longitude;
}
}
}

View file

@ -0,0 +1,21 @@
package fr.kektus.summer2024.presentation.rest;
import fr.kektus.summer2024.domain.entity.LocationEntity;
import fr.kektus.summer2024.domain.service.LocationService;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/location")
@Produces(MediaType.APPLICATION_JSON)
public class LocationApi {
@Inject LocationService locationService;
@GET @Path("/last")
public LocationEntity getLastLocation() {
return locationService.getLatestLocation();
}
}

View file

@ -0,0 +1,7 @@
create table Location
(
id bigserial not null primary key,
latitude float4,
longitude float4,
timestamp timestamp(6) with time zone
);