diff --git a/flyway.conf b/flyway.conf new file mode 100644 index 0000000..be3336f --- /dev/null +++ b/flyway.conf @@ -0,0 +1,4 @@ +flyway.url=jdbc:postgresql://185.166.39.209:5432/vlib_db +flyway.user=vlibadmin +flyway.password=T5tUb8DTh2dtMaRymzMyRKwT5kg3xqamdZJjqkLr +flyway.locations=filesystem:src/main/resources/db/migration \ No newline at end of file diff --git a/pom.xml b/pom.xml index 70f2782..cf02062 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-webflux + org.postgresql postgresql diff --git a/src/main/java/fr/host_dcode/vlib/config/WebClientConfig.java b/src/main/java/fr/host_dcode/vlib/config/WebClientConfig.java new file mode 100644 index 0000000..77dd77a --- /dev/null +++ b/src/main/java/fr/host_dcode/vlib/config/WebClientConfig.java @@ -0,0 +1,19 @@ +package fr.host_dcode.vlib.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class WebClientConfig { + @Value("${velib.api.base-url}") + private String baseUrl; + + @Bean + public WebClient webClient() { + return WebClient.builder() + .baseUrl(java.util.Objects.requireNonNull(baseUrl, "velib.api.base-url must not be null")) + .build(); + } +} diff --git a/src/main/java/fr/host_dcode/vlib/model/Station.java b/src/main/java/fr/host_dcode/vlib/model/Station.java index 1859b59..8d72a86 100644 --- a/src/main/java/fr/host_dcode/vlib/model/Station.java +++ b/src/main/java/fr/host_dcode/vlib/model/Station.java @@ -1,10 +1,7 @@ package fr.host_dcode.vlib.model; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; +import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; @@ -13,65 +10,71 @@ public class Station { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; + @Column(name = "recordid") private String recordId; private String name; - private String station_code; + @Column(name = "station_code") + private String stationCode; private double latitude; private double longitude; - private String address; private String city; private String description; - private LocalDateTime last_update; - + @UpdateTimestamp + @Column(name = "last_update") + private LocalDateTime lastUpdate; - public Station(){} + public Station() { + } - public Station(String name, String recordId, String stationCode, double latitude, double longitude, String description) { + public Station(String name, String recordId, String stationCode, String city, double latitude, double longitude, + String description) { this.name = name; this.recordId = recordId; - this.station_code = stationCode; + this.stationCode = stationCode; + this.city = city; this.latitude = latitude; this.longitude = longitude; this.description = description; } - public String getStationCode(){ - return this.station_code; + public String getStationCode() { + return this.stationCode; } - public String getName(){ + public String getName() { return this.name; } - public void setName(String name){ + public void setName(String name) { this.name = name; } - public String getRecordId(){ + public String getRecordId() { return this.recordId; } - public String getDescription(){ + public String getDescription() { return this.description; } - public void setDescription(String description){ + public void setDescription(String description) { this.description = description; } - public Double getLatitude(){ + + public Double getLatitude() { return this.latitude; } - public Double getLongitude(){ + + public Double getLongitude() { return this.longitude; } - public String getAddress(){ - return this.address; + public String getCity() { + return this.city; } - public void setAddress(String address){ - this.address = address; + public void setCity(String city) { + this.city = city; } - } diff --git a/src/main/java/fr/host_dcode/vlib/model/User.java b/src/main/java/fr/host_dcode/vlib/model/User.java index f097a9d..cd015ad 100644 --- a/src/main/java/fr/host_dcode/vlib/model/User.java +++ b/src/main/java/fr/host_dcode/vlib/model/User.java @@ -20,7 +20,7 @@ public enum Status { private String password; @Enumerated(EnumType.STRING) private Status status; - private LocalDateTime created_at; + private LocalDateTime createdAt; public User(){} diff --git a/src/main/java/fr/host_dcode/vlib/model/VelibApiResponse.java b/src/main/java/fr/host_dcode/vlib/model/VelibApiResponse.java new file mode 100644 index 0000000..e9f61c8 --- /dev/null +++ b/src/main/java/fr/host_dcode/vlib/model/VelibApiResponse.java @@ -0,0 +1,17 @@ +package fr.host_dcode.vlib.model; + +import java.util.List; + +public class VelibApiResponse { + + private List records; + + public List getRecords() { + return records; + } + + public void setRecords(List records) { + this.records = records; + } + +} \ No newline at end of file diff --git a/src/main/java/fr/host_dcode/vlib/model/VelibFields.java b/src/main/java/fr/host_dcode/vlib/model/VelibFields.java new file mode 100644 index 0000000..b83510e --- /dev/null +++ b/src/main/java/fr/host_dcode/vlib/model/VelibFields.java @@ -0,0 +1,42 @@ +package fr.host_dcode.vlib.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.ZonedDateTime; +import java.util.List; + + +public class VelibFields { + + @JsonProperty("recordid") + private String recordId; + private String name; + @JsonProperty("stationcode") + private String stationCode; + @JsonProperty("nom_arrondissement_communes") + private String city; + private List coordonnees_geo; + private ZonedDateTime duedate; + + + public String getName(){ + return this.name; + } + public String getStationCode(){ + return this.stationCode; + } + + public String getDuedate(){ + return this.duedate != null ? this.duedate.toString() : null; + } + + public List getCoordonnees_geo(){ + return this.coordonnees_geo; + } + + public String getCity(){ + return this.city; + } + + +} \ No newline at end of file diff --git a/src/main/java/fr/host_dcode/vlib/model/VelibRecord.java b/src/main/java/fr/host_dcode/vlib/model/VelibRecord.java new file mode 100644 index 0000000..d98d6ae --- /dev/null +++ b/src/main/java/fr/host_dcode/vlib/model/VelibRecord.java @@ -0,0 +1,27 @@ +package fr.host_dcode.vlib.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class VelibRecord { + + private VelibFields fields; + @JsonProperty("recordid") + private String recordId; + + public VelibFields getFields() { + return fields; + } + + public void setFields(VelibFields fields) { + this.fields = fields; + } + + public String getRecordId(){ + return this.recordId; + } + + public void setRecordId(String recordId) { + this.recordId = recordId; + } + +} \ No newline at end of file diff --git a/src/main/java/fr/host_dcode/vlib/repository/StationRepository.java b/src/main/java/fr/host_dcode/vlib/repository/StationRepository.java new file mode 100644 index 0000000..7c334f9 --- /dev/null +++ b/src/main/java/fr/host_dcode/vlib/repository/StationRepository.java @@ -0,0 +1,12 @@ +package fr.host_dcode.vlib.repository; + +import fr.host_dcode.vlib.model.Station; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface StationRepository extends JpaRepository { + + Station findByStationCode(String station_code); + +} diff --git a/src/main/java/fr/host_dcode/vlib/service/VelibService.java b/src/main/java/fr/host_dcode/vlib/service/VelibService.java new file mode 100644 index 0000000..514d604 --- /dev/null +++ b/src/main/java/fr/host_dcode/vlib/service/VelibService.java @@ -0,0 +1,85 @@ +package fr.host_dcode.vlib.service; + +import fr.host_dcode.vlib.model.Station; +import fr.host_dcode.vlib.model.VelibApiResponse; +import fr.host_dcode.vlib.model.VelibFields; +import fr.host_dcode.vlib.repository.StationRepository; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.List; + +@Service +public class VelibService { + private final WebClient webClient; + private final StationRepository stationRepository; + + public VelibService(WebClient webClient, StationRepository stationRepository) { + this.webClient = webClient; + this.stationRepository = stationRepository; + } + + public void fetchAndSaveVelibData() { + + String queryUri = "?dataset=velib-disponibilite-en-temps-reel&rows=50"; + + VelibApiResponse apiResponse = webClient.get() + .uri(queryUri) + .retrieve() + .bodyToMono(VelibApiResponse.class) + .block(java.time.Duration.ofSeconds(30)); + + if (apiResponse == null || apiResponse.getRecords() == null || apiResponse.getRecords().isEmpty()) { + System.out.println("Aucune donnée Velib reçue."); + return; + } + + apiResponse.getRecords().stream() + .map(record -> { + VelibFields fields = record.getFields(); + + List coords = fields.getCoordonnees_geo(); + double latitude = (coords != null && coords.size() >= 1) ? coords.get(0) : 0.0; + double longitude = (coords != null && coords.size() >= 2) ? coords.get(1) : 0.0; + + String description = fields.getName() + ". Dernière mise à jour: " + (fields.getDuedate() != null ? fields.getDuedate() : "inconnue"); + + Station station = new Station( + fields.getName(), + record.getRecordId(), + fields.getStationCode(), + fields.getCity(), + latitude, + longitude, + description + ); + + return station; + }) + .forEach(station ->{ + Station existingStation = stationRepository.findByStationCode(station.getStationCode()); + if(existingStation == null){ + stationRepository.save(station); + } else { + existingStation.setName(station.getName()); + //ADD ALL UPDATES + stationRepository.save(existingStation); + } + }); + } + + + @EventListener(ApplicationReadyEvent.class) + public void runAfterStartup() { + System.out.println("🚀 Démarrage de l'application détecté. Lancement de la récupération des données Velib..."); + + try { + fetchAndSaveVelibData(); + System.out.println("✅ Récupération et sauvegarde initiales terminées."); + } catch (Exception e) { + System.err.println("❌ Erreur critique lors de la récupération initiale des données Velib : " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V2__Alter_table_station_to_add_recordId_column.sql b/src/main/resources/db/migration/V2__Alter_table_station_to_add_recordId_column.sql new file mode 100644 index 0000000..52033f8 --- /dev/null +++ b/src/main/resources/db/migration/V2__Alter_table_station_to_add_recordId_column.sql @@ -0,0 +1,2 @@ +ALTER TABLE station + ADD COLUMN recordId VARCHAR(100) NOT NULL; \ No newline at end of file diff --git a/src/main/resources/db/migration/V3__Alter_tables_users_and_station_to_change_id_type.sql b/src/main/resources/db/migration/V3__Alter_tables_users_and_station_to_change_id_type.sql new file mode 100644 index 0000000..7044121 --- /dev/null +++ b/src/main/resources/db/migration/V3__Alter_tables_users_and_station_to_change_id_type.sql @@ -0,0 +1,6 @@ +ALTER TABLE users +ALTER COLUMN id TYPE VARCHAR(40) USING id::VARCHAR(40); + + +ALTER TABLE station +ALTER COLUMN id TYPE VARCHAR(40) USING id::VARCHAR(40); \ No newline at end of file diff --git a/src/main/resources/db/migration/V4__Delete_address_column_in_table_station.sql b/src/main/resources/db/migration/V4__Delete_address_column_in_table_station.sql new file mode 100644 index 0000000..1c111e0 --- /dev/null +++ b/src/main/resources/db/migration/V4__Delete_address_column_in_table_station.sql @@ -0,0 +1,2 @@ +ALTER TABLE station +DROP COLUMN address; \ No newline at end of file