Skip to content

Conversation

@SHACRAM
Copy link
Collaborator

@SHACRAM SHACRAM commented Nov 30, 2025

Description

Create classes :

  • VelibResponseApi
  • VelibRecord
  • VelibFields

Create Interface StationRepository

Create the VelibService class for parsing the Api call.

  • add filter for verify if the station is already registred in database.

Create new migrations files to modify the table station

  • Delete address column
  • Modify the id type (before : integer -> after :String)

Related Issue

Closes #10

Type of Change

  • feat
  • fix
  • docs
  • refactor
  • chore
  • test
  • ci

How Has This Been Tested?

No test yet.

Checklist

  • I have followed the Conventional Commits naming rules
  • I have added tests where applicable

Comment

  • Need to modify row 66 in VelibService class to add more update to the existing Station.

@SHACRAM SHACRAM added this to the v0.1.0 - MVP milestone Nov 30, 2025
Copilot AI review requested due to automatic review settings November 30, 2025 19:31
Copilot finished reviewing on behalf of SHACRAM November 30, 2025 19:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements functionality to load Velib bike-sharing station data from the Paris Open Data API at application startup. It introduces API response models (VelibApiResponse, VelibRecord, VelibFields), a repository interface for station persistence, and a service that fetches and synchronizes station data with the database.

Key Changes:

  • Added VelibService to fetch station data from external API and persist/update stations in the database
  • Created data models for parsing the Velib API JSON responses
  • Modified database schema to support station record IDs and change ID types from integer to string

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 22 comments.

Show a summary per file
File Description
VelibService.java Core service for fetching and saving Velib station data with startup event listener
Station.java Updated entity model with new fields (recordId, stationCode) and removed address field
StationRepository.java JPA repository interface with custom finder method for station lookup
VelibApiResponse.java Response wrapper model for API data deserialization
VelibRecord.java Model representing individual station records from API
VelibFields.java Model for station field data including coordinates and metadata
WebClientConfig.java Configuration for Spring WebClient to call Paris Open Data API
V2__Alter_table_station_to_add_recordId_column.sql Migration adding recordId column to station table
V3__Alter_tables_users_and_station_to_change_id_type.sql Migration changing ID types from integer to VARCHAR
V4__Delete_address_column_in_table_station.sql Migration removing address column from station table
pom.xml Added spring-boot-starter-webflux dependency for reactive HTTP client
flyway.conf Flyway configuration with database connection details
.gitignore Added flyway.conf to ignored files

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

private String recordId;
private String name;
@JsonProperty("stationcode")
private String station_code;
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name station_code does not follow Java naming conventions. Field names should be in camelCase. It should be named stationCode to be consistent with the getter method getStation_code() and Java conventions.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +60
Station station = new Station(
fields.getName(),
record.getRecordId(),
fields.getStation_code(),
fields.getCity(),
latitude,
longitude,
description
);

return station;
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary variable assignment. The station variable is only used once and can be inlined. Replace lines 50-60 with:

return new Station(
    fields.getName(),
    record.getRecordId(),
    fields.getStation_code(),
    fields.getCity(),
    latitude,
    longitude,
    description
);

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +86
@Service
public class VelibService {
public 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();

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<Double> 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();

Station station = new Station(
fields.getName(),
record.getRecordId(),
fields.getStation_code(),
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());
//AJOUTER TOUTES LES MAJS
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
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VelibService class lacks test coverage. Consider adding tests for:

  • fetchAndSaveVelibData() with various API responses (success, empty, null)
  • Handling of new vs existing stations
  • Coordinate parsing edge cases
  • Error handling when the API is unavailable

Example test structure:

@ExtendWith(MockitoExtension.class)
class VelibServiceTest {
    @Mock
    private WebClient webClient;
    @Mock
    private StationRepository stationRepository;
    
    @Test
    void fetchAndSaveVelibData_shouldSaveNewStations() {
        // test implementation
    }
}

Copilot uses AI. Check for mistakes.
stationRepository.save(station);
} else {
existingStation.setName(station.getName());
//AJOUTER TOUTES LES MAJS
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete update logic for existing stations. Currently only the name field is updated, but the station object has other fields that should also be updated (recordId, city, latitude, longitude, description). The incomplete update could lead to stale data. Either implement all updates as mentioned in the TODO comment, or document why only the name needs updating:

existingStation.setName(station.getName());
existingStation.setRecordId(station.getRecordId());
existingStation.setCity(station.getCity());
existingStation.setLatitude(station.getLatitude());
existingStation.setLongitude(station.getLongitude());
existingStation.setDescription(station.getDescription());
stationRepository.save(existingStation);
Suggested change
//AJOUTER TOUTES LES MAJS
existingStation.setRecordId(station.getRecordId());
existingStation.setCity(station.getCity());
existingStation.setLatitude(station.getLatitude());
existingStation.setLongitude(station.getLongitude());
existingStation.setDescription(station.getDescription());

Copilot uses AI. Check for mistakes.
public String getCity(){
return this.city;
}
public void setCity(String city){}
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setCity method has an empty implementation and does not actually set the city field. This should be implemented as:

public void setCity(String city) {
    this.city = city;
}
Suggested change
public void setCity(String city){}
public void setCity(String city){
this.city = city;
}

Copilot uses AI. Check for mistakes.
stationRepository.save(station);
} else {
existingStation.setName(station.getName());
//AJOUTER TOUTES LES MAJS
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] French comment in code. Consider using English for code comments to maintain consistency and accessibility for international developers:

//ADD ALL UPDATES
Suggested change
//AJOUTER TOUTES LES MAJS
//ADD ALL UPDATES

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
flyway.url=jdbc:postgresql://185.166.39.209:5432/vlib_db
flyway.user=vlibadmin
flyway.password=T5tUb8DTh2dtMaRymzMyRKwT5kg3xqamdZJjqkLr
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Database credentials (URL, username, and password) are exposed in this configuration file. Even though flyway.conf is being added to .gitignore, the file itself is being committed to the repository in this PR, which means the credentials are now in the Git history and publicly accessible. These credentials should be rotated immediately and the file should be removed from the commit history. Use environment variables or a secure secrets management system instead.

Suggested change
flyway.url=jdbc:postgresql://185.166.39.209:5432/vlib_db
flyway.user=vlibadmin
flyway.password=T5tUb8DTh2dtMaRymzMyRKwT5kg3xqamdZJjqkLr
# Database credentials have been removed for security.
# Set the following environment variables before running Flyway:
# FLYWAY_URL
# FLYWAY_USER
# FLYWAY_PASSWORD

Copilot uses AI. Check for mistakes.

private VelibFields fields;
@JsonProperty("recordid")
public String recordId;
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recordId field is declared as public, which violates encapsulation. It should be private like the other fields in this class, since there are already getter and setter methods for it.

Suggested change
public String recordId;
private String recordId;

Copilot uses AI. Check for mistakes.
.block();

if (apiResponse == null || apiResponse.getRecords() == null || apiResponse.getRecords().isEmpty()) {
System.out.println("Aucune donnée Velib reçue.");
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using System.out.println and System.err.println for logging is not recommended in production applications. Consider using a proper logging framework like SLF4J with Logback (already included in Spring Boot). Replace with:

private static final Logger logger = LoggerFactory.getLogger(VelibService.class);
logger.info("Aucune donnée Velib reçue.");

Copilot uses AI. Check for mistakes.
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();
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential NullPointerException: If fields.getDuedate() returns null (i.e., duedate is null), calling toString() on it will throw a NullPointerException. Add a null check:

String description = fields.getName() + ". Dernière mise à jour: " + 
    (fields.getDuedate() != null ? fields.getDuedate() : "inconnue");
Suggested change
String description = fields.getName() + ". Dernière mise à jour: " + fields.getDuedate();
String description = fields.getName() + ". Dernière mise à jour: " + (fields.getDuedate() != null ? fields.getDuedate() : "inconnue");

Copilot uses AI. Check for mistakes.
@DCodeProg DCodeProg changed the title Feat/10 load stations data at startup feat: load stations data at startup Dec 1, 2025
@Bean
public WebClient webClient(){
return WebClient.builder()
.baseUrl("https://opendata.paris.fr/api/records/1.0/search/")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass the URL through an environment variable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Load stations data at startup

3 participants