diff --git a/.gitignore b/.gitignore
index bf70086c415482dea564c87fd3dc6e96e48133dc..7296ae5dcc5c81743786e9ba0c69368f365a9944 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,9 @@
 *.class
 *.jar
 target/
-**/*.DS_Store
\ No newline at end of file
+**/*.DS_Store
+*.project
+**.vscode/
+*.classpath
+**.settings
+*.iml
\ No newline at end of file
diff --git a/README.md b/README.md
index 846c531f866189e728ea7b4f8e8eb99b5b383b84..85398b35c9cc9060b2718436ce44dc646844b4fc 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
 # Travail Pratique 4
 Un travail de Alexis Durgnat, Michael El Kharroubi, Quentin Leblanc et Théo Pirkl
 
-
 ## Description
 Ce travail pratique consiste à proposer une application de réseau social permettant de savoir qui prend
 quel transport public. Certaines intégrations y seront ajoutées.
@@ -9,12 +8,31 @@ quel transport public. Certaines intégrations y seront ajoutées.
 Voici un mockup de l'application : 
 ![alt text](app.png)
 
+## Compilation
+L'application peut être compilée avec 
+```
+mvn compile exec:java
+```
+Les tests sont exécutés avec 
+```
+mvn test
+```
+
+## Utilisation
+Lors du lancement de l'application, un champs demande le nom d'utilisateur. Après avoir entré celui-ci, l'application principale s'ouvre.
+Il est possible de chercher une correspondance grâce à la barre supérieure. La première combobox est le départ, la seconde la destinations.
+Après avoir sélectionné le point de départ et d'arriver, un clic sur "Itinéraires" permet d'afficher les prochaines correspondances disponibles.
+
+La barre latérale droite contient les notifications et messages des utilisateurs connectés. Il est possible d'envoyer un message à tout le monde
+grâce au champ textuel en dessous.
+
 ## Technique
 Nous implémentons :
 * L'API OpenData Transport [(disponible ici)](https://transport.opendata.ch)
 * L'API Prévisions-Météo.ch [(disponible ici)](https://www.prevision-meteo.ch/uploads/pdf/recuperation-donnees-meteo.pdf)
-* Une interface graphique avec JavaFX, liée à un WebEngine
-* D'autres trucs (?)
+* Une interface graphique avec JavaFX
+* Un chat :cat:
+* Quelques surprises... :wink:
 
 ## Intégration
 Merci de suivre au possible le système suivant :
@@ -29,4 +47,4 @@ Schéma des branches (exemple) :
 
 Lors du merge :
 * gui —> devel et opendata-transport —> devel
-* devel -> master (une fois que ca fonctionne)
\ No newline at end of file
+* devel -> master (une fois que ca fonctionne)
diff --git a/pom.xml b/pom.xml
index 481cc24e6673f76f36b780c683ce38ddad706f75..effeb7eab5cb4cfb0b6381300c46c1f536a25898 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,87 @@
     <properties>
         <maven.compiler.source>11</maven.compiler.source>
         <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     </properties>
+    <dependencies>
+        <!-- JUnit -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.2.0</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- Java FX -->
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-controls</artifactId>
+            <version>11</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-fxml</artifactId>
+            <version>11</version>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>20180813</version>
+        </dependency>
 
+        <!-- Rabbit MQ -->
+        <dependency>
+            <groupId>com.rabbitmq</groupId>
+            <artifactId>amqp-client</artifactId>
+            <version>5.5.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>1.7.5</version>
+        </dependency>
+    </dependencies>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.0</version>
+                <configuration>
+                    <release>11</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>1.6.0</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>java</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <mainClass>ch.hepia.Main</mainClass>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.22.0</version>
+                <configuration>
+                    <excludes>
+                    <exclude>some test to exclude</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>
\ No newline at end of file
diff --git a/src/main/java/ch/hepia/Main.java b/src/main/java/ch/hepia/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..7efc6c084d0c9cf6eed99ac2af37113aa4d210d5
--- /dev/null
+++ b/src/main/java/ch/hepia/Main.java
@@ -0,0 +1,68 @@
+package ch.hepia;
+
+import ch.hepia.config.AppConfig;
+import ch.hepia.config.AppContext;
+import ch.hepia.mq.MessageManager;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+
+/**
+ * Starts up the app.
+ */
+public class Main extends Application {
+
+	private static AppContext appContext;
+
+	/**
+	 * Main starter
+	 * @param args The passed arguments
+	 */
+	public static void main(String[] args) throws Exception {
+		MessageManager m = new MessageManager(
+				AppConfig.RABBITMQ_HOSTNAME, AppConfig.RABBITMQ_USERNAME, AppConfig.RABBITMQ_PASSWORD,
+				AppConfig.RABBITMQ_EXCHANGE
+		);
+		appContext = new AppContext(m);
+		launch(args);
+	}
+
+	/**
+	 * Starts up the JavaFX app.
+	 * @param stage The stage to start
+	 * @throws IOException When goofed up with JavaFX
+	 */
+	@Override
+	public void start(Stage stage) throws IOException {
+		Parent root = FXMLLoader.load(Main.class.getResource("/fxml/ConnectionWindow.fxml"));
+		Scene scene = new Scene(root, AppConfig.APP_WIDTH, AppConfig.APP_HEIGHT);
+
+		stage.setScene(scene);
+		stage.setResizable(false);
+		stage.show();
+		stage.setTitle(AppConfig.APP_NAME);
+		stage.setOnCloseRequest(t -> {
+			Platform.exit();
+			try {
+				appContext.getMessageManager().close();
+			} catch (Exception e){
+				System.exit(1);
+			}
+			System.exit(0);
+		});
+	}
+
+	/**
+	 * Gets the app context of the app
+	 * @return the app context of the app
+	 */
+	public static AppContext getContext(){
+		return appContext;
+	}
+}
diff --git a/src/main/java/ch/hepia/api/HTTPUtils.java b/src/main/java/ch/hepia/api/HTTPUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..f0ce17e29536cfbe7c8e0ba59f1ca7fda476972c
--- /dev/null
+++ b/src/main/java/ch/hepia/api/HTTPUtils.java
@@ -0,0 +1,32 @@
+package ch.hepia.api;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class HTTPUtils {
+	/**
+	 * Fetch datas with an http GET request on the form of a string given the url path
+	 * @param path
+	 * @return content
+	 * @throws IOException
+	 */
+	public static String getContent(String path) throws IOException {
+		URL url = new URL(path);
+		HttpURLConnection http = (HttpURLConnection) url.openConnection();
+		http.setRequestMethod("GET");
+		http.connect();
+
+		BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));
+		String line;
+		StringBuilder content = new StringBuilder();
+		while((line = reader.readLine()) != null){
+			content.append(line);
+		}
+		reader.close();
+		http.disconnect();
+		return content.toString();
+	}
+}
diff --git a/src/main/java/ch/hepia/api/transport/Connection.java b/src/main/java/ch/hepia/api/transport/Connection.java
new file mode 100644
index 0000000000000000000000000000000000000000..4001bae6ef102a788c1a224cf3f7f293227df225
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Connection.java
@@ -0,0 +1,160 @@
+package ch.hepia.api.transport;
+
+import java.sql.Time;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import java.io.Serializable;
+
+public class Connection implements Serializable {
+    private static final long serialVersionUID = 0xAEF55565673L;
+    private Stop from;
+    private Stop to;
+    private Time duration;
+    private Service service;
+    private List<String> products;
+    private int capacity1st;
+    private int capacity2nd;
+    private List<Section> sections;
+
+    /**
+     * Empty Connection
+     */
+
+    public final static class EmptyConnection extends Connection{
+        private static final long serialVersionUID = 0xAEF55565672L;
+
+        /**
+         * Constructor of EmptyConnection All values are default ones An Empty
+         * Connection go from Geneva to Geneva
+         * 
+         * @throws ParseException
+         */
+        public EmptyConnection() throws ParseException {
+            super(new Stop.StopBuilder(new JSONObject()).build(),
+                    new Stop.StopBuilder(new JSONObject()).build(),
+                    new Time(new SimpleDateFormat("dd:kk:mm:ss").parse("00:00:00:00").getTime()),
+                    new Service.ServiceBuilder(new JSONObject()).build(), new ArrayList<>(), 0, 0, new ArrayList<>());
+        }
+    }
+
+    /**
+     * Constructor of Connection Object
+     * 
+     * @param from        stop object
+     * @param to          stop object
+     * @param duration    time of the connection
+     * @param service     how regular is the connection
+     * @param products    list the products you have to purchase for this connection
+     * @param capacity1st number of seats in 1st class
+     * @param capacity2nd number of seats in 2nd class
+     * @param sections    Where the connection will go through
+     */
+    private Connection(Stop from, Stop to, Time duration, Service service, List<String> products, int capacity1st,
+            int capacity2nd, List<Section> sections) {
+        this.from = from;
+        this.to = to;
+        this.duration = duration;
+        this.service = service;
+        this.products = new ArrayList<>(products);
+        this.capacity1st = capacity1st;
+        this.capacity2nd = capacity2nd;
+        this.sections = new ArrayList<>(sections);
+    }
+
+    public Section getInCommonSection(Connection connection) throws ParseException {
+        for (Section section : this.sections) {
+            if (connection.sections.contains(section)) {
+                return section;
+            }
+        }
+        return Section.empty();
+    }
+
+    /**
+     * Get all the sections of the connection
+     * 
+     * @return A list of Section objects
+     */
+    public List<Section> getSections() {
+        return new ArrayList<>(sections);
+    }
+
+    public Stop getFrom() {
+        return this.from;
+    }
+
+    public Stop getTo() {
+        return this.to;
+    }
+
+    /**
+     * Builder of Connection object
+     */
+    public final static class ConnectionBuilder {
+        private JSONObject datas;
+
+        /**
+         * Constructor of the class ConnectionBuilder Stock Datas from a JSON object
+         * given
+         * 
+         * @param datas contains datas to construct a Connection object
+         */
+        public ConnectionBuilder(JSONObject datas) {
+            this.datas = datas;
+        }
+
+        /**
+         * Build a Connection object from the datas obtained
+         * 
+         * @return a new Connection object
+         * @throws ParseException
+         */
+        public Connection build() throws ParseException {
+            // if the datas retrieved contain nothing, we have to prevent errors
+            if (datas.isEmpty()) {
+                return new EmptyConnection();
+            }
+
+            JSONObject fromJSON = datas.isNull("from") ? new JSONObject() : datas.getJSONObject("from");
+            Stop.StopBuilder from = new Stop.StopBuilder(fromJSON);
+
+            JSONObject toJSON = datas.isNull("to") ? new JSONObject() : datas.getJSONObject("to");
+            Stop.StopBuilder to = new Stop.StopBuilder(toJSON);
+
+            DateFormat formatter = new SimpleDateFormat("dd:kk:mm:ss");
+            String date = datas.isNull("duration") ? "00:00:00:00" : datas.getString("duration").replace('d', ':');
+            Time duration = new Time(formatter.parse(date).getTime());
+
+            JSONObject serviceJSON = datas.isNull("service") ? new JSONObject() : new JSONObject(datas.get("service"));
+            Service.ServiceBuilder service = new Service.ServiceBuilder(serviceJSON);
+
+            JSONArray productsJSON = datas.isNull("from") ? new JSONArray() : datas.getJSONArray("products");
+            List<String> products = new ArrayList<>();
+            productsJSON.forEach(k -> products.add(k.toString()));
+
+            int capacity1st = datas.isNull("capacity1st") ? 0 : datas.getInt("capacity1st");
+            int capacity2nd = datas.isNull("capacity2nd") ? 0 : datas.getInt("capacity2nd");
+
+            JSONArray sectionsJSON = datas.isNull("from") ? new JSONArray() : datas.getJSONArray("sections");
+            List<Section> sections = new ArrayList<>();
+            sectionsJSON.forEach(o -> {
+                JSONObject k = (JSONObject) o;
+                Section.SectionBuilder section = new Section.SectionBuilder(k);
+                try {
+                    sections.add(section.build());
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                }
+            });
+
+            return new Connection(from.build(), to.build(), duration, service.build(), products, capacity1st,
+                    capacity2nd, sections);
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/transport/Coordinates.java b/src/main/java/ch/hepia/api/transport/Coordinates.java
new file mode 100644
index 0000000000000000000000000000000000000000..63884acef945614c3e5a307f8bad74f91d129042
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Coordinates.java
@@ -0,0 +1,108 @@
+package ch.hepia.api.transport;
+
+import org.json.JSONObject;
+import java.io.Serializable;
+
+public class Coordinates implements Serializable {
+    private static final long serialVersionUID = 0xFFF34565673L;
+    private String type;
+    private double x;
+    private double y;
+
+    /**
+     * Default coordinates containing default values (Geneva is the default value)
+     * WGS84 is the default type value
+     */
+    private final static class DefaultCoordinates extends Coordinates{
+        private static final long serialVersionUID = 0xFFF34565670L;
+        /**
+         * Constructor of DefaultCoordinates, located in Geneva
+         */
+        private DefaultCoordinates(){
+            super("WGS84", 46.210237, 6.142422);
+        }
+    }
+
+    /**
+     * Constructor of the class Coordinates
+     * A coordinates is represented by a type of place and a point (x, y)
+     * @param type type of place we are a these coordinates
+     * @param x coordinate in x
+     * @param y coordinate in y
+     */
+    private Coordinates(String type, double x, double y){
+        this.type = type;
+        this.x = x;
+        this.y = y;
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder string = new StringBuilder();
+        return string.append("Coordonnées du type ").append(type).append(" au point (").append(x).append(", ").append(y).append(")").toString();
+    }
+
+    /**
+     * Get the x coordinate of the place (x, y)
+     * @return x
+     */
+    public double getX(){
+        return x;
+    }
+
+    /**
+     * Get the y coordinate of the place (x, y)
+     * @return y
+     */
+    public double getY(){
+        return y;
+    }
+
+    @Override
+    public boolean equals(Object o){
+        if (this == o) {
+            return true;
+        }
+        if (o == null || o.getClass() != this.getClass()) {
+            return false;
+        }
+        Coordinates c = (Coordinates) o;
+        return c.type.equals(this.type) &&
+            Math.abs(c.x - this.x) < Math.pow(10, -6) &&
+            Math.abs(c.y - this.y) < Math.pow(10, -6);
+    }
+
+    /**
+     * Builder of Coordinates object
+     */
+    public final static class CoordinatesBuilder{
+        private JSONObject datas;
+        private String type;
+
+        /**
+         * Constructor of the class CoordinatesBuilder
+         * Stock Datas from a JSON object given
+         * @param datas contains datas to construct a Coordinates object
+         */
+        public CoordinatesBuilder(JSONObject datas, String type){
+            this.datas = datas;
+            this.type = type;
+        }
+
+        /**
+         * Build a Coordinates object from the datas obtained
+         * @return a new Coordinates object
+         */
+        public Coordinates build(){
+            if(datas.isEmpty()){
+                return new DefaultCoordinates();
+            }
+
+            String type = datas.getString("type");
+            double x = datas.isNull("x") ? 0 : datas.getDouble("x");
+            double y = datas.isNull("y") ? 0 : datas.getDouble("y");
+
+            return new Coordinates(type, x, y);
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/transport/Journey.java b/src/main/java/ch/hepia/api/transport/Journey.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f1208126fc5f13b24b9b5c5183736f1fbf3ba24
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Journey.java
@@ -0,0 +1,173 @@
+package ch.hepia.api.transport;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.io.Serializable;
+
+public class Journey implements Serializable {
+    private static final long serialVersionUID = 0xCBF34565673L;
+    private String name;
+    private String category;
+    private int categoryCode;
+    private String number;
+    private String operator;
+    private String to;
+    private List<Stop> passList;
+    private int capacity1st;
+    private int capacity2nd;
+
+    /**
+     * Empty Journey
+     */
+    public final static class EmptyJourney extends Journey{
+        private static final long serialVersionUID = 0xCBF34565670L;
+        /**
+         * Constructor of EmptyJourney
+         * No Information and no Stops in the Journey
+         * @throws ParseException
+         */
+        private EmptyJourney() throws ParseException {
+            super("", "", 0, "", "", "",
+                    new ArrayList<>(), 0, 0);
+        }
+    }
+
+    /**
+     * Constructor of the Journey Object
+     * @param name
+     * @param category what transport category the journey is
+     * @param categoryCode
+     * @param number
+     * @param operator which company operates the journey
+     * @param to destination of the journey
+     * @param passList Where the journey will pass through
+     * @param capacity1st number of seats inst class
+     * @param capacity2nd number of seats in nd class
+     */
+    private Journey(String name, String category, int categoryCode, String number, String operator, String to, List<Stop> passList, int capacity1st, int capacity2nd){
+        this.name = name;
+        this.category = category;
+        this.categoryCode = categoryCode;
+        this.number = number;
+        this.operator = operator;
+        this.to = to;
+        this.passList = new ArrayList<>(passList);
+        this.capacity1st = capacity1st;
+        this.capacity2nd = capacity2nd;
+    }
+
+    /**
+     * Get the name of the journey
+     * @return name
+     */
+    public String getName(){
+        return name;
+    }
+
+    /**
+     * Get the Category of the transport of the Journey
+     * @return category
+     */
+    public String getCategory(){
+        return category;
+    }
+
+    /**
+     * Get the categoryCode affiliated with the Journey
+     * @return categoryCode
+     */
+    public int getCategoryCode(){
+        return categoryCode;
+    }
+
+    /**
+     * Get where the Journey is headed to
+     * @return to
+     */
+    public String getTo(){
+        return to;
+    }
+    /**
+     * Get the transport number
+     * @return number
+     */
+    public String getNumber(){
+        return number;
+    }
+
+    /**
+     * Get all the Stops where the journey will pass through
+     * @return passList
+     */
+    public List<Stop> getPassList(){
+        return new ArrayList<>(passList);
+    }
+
+    @Override
+    public boolean equals(Object o){
+        if (this == o) {
+            return true;
+        }
+        if (o == null || o.getClass() != this.getClass()) {
+            return false;
+        }
+        Journey j = (Journey) o;
+        return j.number.equals(this.number) && j.name.equals(this.name);
+    }
+
+    /**
+     * Builder of Journey object
+     */
+    public final static class JourneyBuilder{
+        private JSONObject datas;
+
+        /**
+         * Constructor of the class JourneyBuilder
+         * Stock Datas from a JSON object given
+         * @param datas contains datas to construct a Journey object
+         */
+        public JourneyBuilder(JSONObject datas){
+            this.datas = datas;
+        }
+
+        /**
+         * Build a new Journey object from the datas obtained
+         * @return a new Journey Object
+         * @throws ParseException
+         */
+        public Journey build() throws ParseException {
+            if(datas.isEmpty()){
+                return new EmptyJourney();
+            }
+
+            String name = datas.isNull("name") ? "" : datas.getString("name");
+            String category = datas.isNull("category") ? "" : datas.getString("category");
+            int categoryCode = datas.isNull("categoryCode") ? 0 : datas.getInt("categoryCode");
+            String number = datas.isNull("number") ? "" : datas.getString("number");
+            String operator = datas.isNull("operator") ? "" : datas.getString("operator");
+
+            String to = datas.isNull("to") ? "" : datas.getString("to");
+
+            JSONArray passListJSON = datas.isNull("from") ? new JSONArray() : datas.getJSONArray("passList");
+            List<Stop> passList = new ArrayList<>();
+            passListJSON.forEach( o -> {
+                JSONObject k = (JSONObject) o;
+                Stop.StopBuilder stop = new Stop.StopBuilder(k);
+                try {
+                    passList.add(stop.build());
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                }
+            });
+
+            int capacity1st = datas.isNull("capacity1st") ? 0 : datas.getInt("capacity1st");
+            int capacity2nd = datas.isNull("capacity2nd") ? 0 : datas.getInt("capacity2nd");
+
+            return new Journey(name, category, categoryCode, number, operator, to, passList, capacity1st, capacity2nd);
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/transport/LinkAPI.java b/src/main/java/ch/hepia/api/transport/LinkAPI.java
new file mode 100644
index 0000000000000000000000000000000000000000..780348437601453d6387de5f627ddf0bb5891512
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/LinkAPI.java
@@ -0,0 +1,98 @@
+package ch.hepia.api.transport;
+
+import ch.hepia.api.HTTPUtils;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LinkAPI {
+    private String baseURL = "http://transport.opendata.ch/v1/";
+
+    /**
+     * Get the stations containing the string query in their names
+     * This return a List of Station type Location Objects
+     * @param query
+     * @return a Lis<Location>
+     * @throws IOException
+     */
+    public List<Location> getStations(String query) throws IOException {
+        String path = baseURL + "locations?query=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
+        String content = HTTPUtils.getContent(path);
+
+        JSONArray stations = new JSONObject(content).getJSONArray("stations");
+        List<Location> stationsList = new ArrayList<>();
+        stations.forEach(o -> {
+            JSONObject k = (JSONObject) o;
+            Location l = new Location.LocationBuilder(k, "station").build();
+            stationsList.add(l);
+        });
+
+        return stationsList;
+    }
+
+    /**
+     * Get all the connections from an end point to another
+     * Return a List of Connection Objects
+     * @param from The origin
+     * @param to The destination
+     * @return a List<Connection>
+     * @throws IOException
+     */
+    public List<Connection> getConnections(String from, String to) throws IOException {
+        String path = baseURL + "connections?from=" +
+                URLEncoder.encode(from, StandardCharsets.UTF_8) + "&to=" +
+                URLEncoder.encode(to, StandardCharsets.UTF_8);
+        String content = HTTPUtils.getContent(path);
+
+        JSONArray connections =  new JSONObject(content).getJSONArray("connections");
+        List<Connection> connectionsList = new ArrayList<>();
+        connections.forEach(o->{
+            JSONObject k = (JSONObject) o;
+            try {
+                Connection c = new Connection.ConnectionBuilder(k).build();
+                connectionsList.add(c);
+            } catch(Exception e){
+                e.printStackTrace();
+            }
+        });
+
+        return connectionsList;
+    }
+
+    /**
+     * Get the Stationboard of the station given
+     * The StationBoard is a List of Journey Objects
+     * The limit is the maximum number of stations the Stationboard should fetch
+     * @param station The station to get
+     * @param limit The limit of transportations
+     * @return a List<Journey>
+     * @throws IOException
+     */
+    public List<Journey> getStationBoard(String station, int limit) throws IOException {
+        String path = baseURL + "stationboard?station=" + URLEncoder.encode(station, StandardCharsets.UTF_8);
+        if(limit != 0){
+            path += "&limit=" + limit;
+        }
+        String content = HTTPUtils.getContent(path);
+
+        JSONArray journeys = new JSONObject(content).getJSONArray("stationboard");
+        List<Journey> journeysList = new ArrayList<>();
+        journeys.forEach(o->{
+            JSONObject k = (JSONObject) o;
+            try {
+                Journey j = new Journey.JourneyBuilder(k).build();
+                journeysList.add(j);
+            } catch(Exception e){
+                e.printStackTrace();
+            }
+        });
+
+        return journeysList;
+    }
+
+}
diff --git a/src/main/java/ch/hepia/api/transport/Location.java b/src/main/java/ch/hepia/api/transport/Location.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae33c639daff7f8ece62458d4939e3156e58f8d7
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Location.java
@@ -0,0 +1,118 @@
+package ch.hepia.api.transport;
+
+import org.json.JSONObject;
+import java.io.Serializable;
+
+public class Location implements Serializable {
+    private static final long serialVersionUID = 0xAEF84565673L;
+    private int id;
+    private Type type;
+    private String name;
+    private double score;
+    private Coordinates where;
+    private double distance;
+
+    /**
+     * Default Location
+     */
+    private final static class DefaultLocation extends Location{
+        private static final long serialVersionUID = 0xAEF84565670L;
+        /**
+         * Constructor of DefaultLocation, centered in Geneva
+         * Type is REFINE, because it's a default choice
+         */
+        private DefaultLocation(){
+            //here building new empty coordinates will return a default coordinates corresponding to Geneva
+            super(8501008, Type.REFINE, "Genève", 0, new Coordinates.CoordinatesBuilder(new JSONObject(), "refine").build(), 0);
+        }
+    }
+
+    /**
+     * Constructor of the class Location
+     * @param id unique to the Location
+     * @param type type of place the Location is
+     * @param name
+     * @param score accuracy of the result (may not be used)
+     * @param where Localisation of the Location
+     * @param distance distance to the Location if there has been Coordinates (may not be used)
+     */
+    private Location(int id, Type type, String name, double score, Coordinates where, double distance){
+        this.id = id;
+        this.type = type;
+        this.name = name;
+        this.score = score;
+        this.where = where;
+        this.distance = distance;
+    }
+
+    /**
+     * Get the coordinates of the Location
+     * @return where
+     */
+    public Coordinates getCoordinates(){
+        return where;
+    }
+
+    /**
+     * Get the name of the location
+     * @return what
+     */
+    public String getName(){
+        return name;
+    }
+
+    @Override
+    public boolean equals(Object o){
+        if (this == o) {
+            return true;
+        }
+        if (o == null || o.getClass() != this.getClass()) {
+            return false;
+        }
+        Location l = (Location) o;
+        return l.type.equals(this.type) &&
+            l.id == this.id &&
+            l.name.equals(this.name) &&
+            l.where.equals(this.where);
+    }
+
+    /**
+     * Builder of Location objects
+     */
+    public final static class LocationBuilder{
+        private JSONObject datas;
+        private String type;
+
+        /**
+         * Constructor of the class LocationBuilder
+         * Stock Datas from a JSON object given
+         * @param datas contains datas to construct a Locatiop object
+         */
+        public LocationBuilder(JSONObject datas, String type){
+            this.datas = datas;
+            this.type = type;
+        }
+
+        /**
+         * Build a Location object with the datas obtained
+         * @return a new Location object
+         */
+        public Location build(){
+            if(datas.isEmpty()){
+                return new DefaultLocation();
+            }
+
+            int id = datas.isNull("id") ? 0 : datas.getInt("id");
+            Type type = Type.valueOf(this.type.toUpperCase());
+            String name = datas.isNull("name") ? "" : datas.getString("name");
+            double score = datas.isNull("score") ? 0 : datas.getDouble("score");
+
+            JSONObject coordinatesJSON = datas.isNull("coordinate") ? new JSONObject() : datas.getJSONObject("coordinate");
+            Coordinates.CoordinatesBuilder coordinates = new Coordinates.CoordinatesBuilder(coordinatesJSON, this.type);
+
+            double distance = datas.isNull("distance") ? 0 : datas.getDouble("distance");
+
+            return new Location(id, type, name, score, coordinates.build(), distance);
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/transport/Prognosis.java b/src/main/java/ch/hepia/api/transport/Prognosis.java
new file mode 100644
index 0000000000000000000000000000000000000000..58754adea2afaba99393e65b0328af0d59e4980e
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Prognosis.java
@@ -0,0 +1,95 @@
+package ch.hepia.api.transport;
+
+import org.json.JSONObject;
+
+import java.sql.Time;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.io.Serializable;
+
+public class Prognosis implements Serializable {
+    private static final long serialVersionUID = 0xAE034565673L;
+    private int plateform;
+    private Time departure;
+    private Time arrival;
+    private int capacity1st;
+    private int capacity2nd;
+
+    /**
+     * Empty Prognosis
+     */
+    private final static class EmptyPrognosis extends Prognosis{
+        private static final long serialVersionUID = 0xAE034565670L;
+        /**
+         * Constructor of EmptyPrognosis
+         * No departure and no Arrival Time
+         * No information on the capacity of 1st and 2nd class nor on the plateform
+         * @throws ParseException
+         */
+        private EmptyPrognosis() throws ParseException {
+            super(0,
+                    new Time(new SimpleDateFormat("dd:kk:mm:ss").parse("00:00:00:00").getTime()),
+                    new Time(new SimpleDateFormat("dd:kk:mm:ss").parse("00:00:00:00").getTime()),
+                    0, 0);
+        }
+    }
+
+    /**
+     * Constructor of the Prognosis class
+     * A Prognosis contains information on the status of a Stop Object
+     * @param plateform plateform where the transport should arrive and leave
+     * @param departure time of departure
+     * @param arrival time of arrival
+     * @param capacity1st number of seats in 1st class
+     * @param capacity2nd number of seats in 2nd class
+     */
+    private Prognosis(int plateform, Time departure, Time arrival, int capacity1st, int capacity2nd){
+        this.plateform = plateform;
+        this.departure = departure;
+        this.arrival = arrival;
+        this.capacity1st = capacity1st;
+        this.capacity2nd = capacity2nd;
+    }
+
+    /**
+     * Builder of Prognosis Objects
+     */
+    public final static class PrognosisBuilder{
+        private JSONObject datas;
+
+        /**
+         * Constructor of the class PrognosisBuilder
+         * Stock Datas from a JSON object given
+         * @param datas contains datas to construct a Prognosis object
+         */
+        public PrognosisBuilder(JSONObject datas){
+            this.datas = datas;
+        }
+
+        /**
+         * Build a Prognosis object from the datas obtained
+         * @return a new Prognosis object
+         * @throws ParseException
+         */
+        public Prognosis build() throws ParseException {
+            if(datas.isEmpty()){
+                return new EmptyPrognosis();
+            }
+
+            int plateform = datas.isNull("plateform") ? 0 : datas.getInt("plateform");
+
+            DateFormat formatter = new SimpleDateFormat("dd:kk:mm:ss");
+            String depatureString = datas.isNull("departure") ? "00:00:00:00" : datas.getString("departure").replace('d',':');
+            Time departure = new Time(formatter.parse(depatureString).getTime());
+
+            String arrivalString = datas.isNull("arrival") ? "00:00:00:00" : datas.getString("arrival").replace('d', ':');
+            Time arrival = new Time(formatter.parse(arrivalString).getTime());
+
+            int capacity1st = datas.isNull("capacity1st") ? 0 : datas.getInt("capacity1st");
+            int capacity2nd = datas.isNull("capacity2nd") ? 0 : datas.getInt("capacity2nd");
+
+            return new Prognosis(plateform, departure, arrival, capacity1st, capacity2nd);
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/transport/Section.java b/src/main/java/ch/hepia/api/transport/Section.java
new file mode 100644
index 0000000000000000000000000000000000000000..8214ea5ebf3781a1d8ad3c25e760ee7b8ef85811
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Section.java
@@ -0,0 +1,126 @@
+package ch.hepia.api.transport;
+
+import org.json.JSONObject;
+
+import java.text.ParseException;
+import java.io.Serializable;
+
+public class Section implements Serializable {
+    private static final long serialVersionUID = 0xAEF37565673L;
+    private Journey journey;
+    private double walktime;
+    private Stop departure;
+    private Stop arrival;
+
+    /**
+     * Empty Section
+     */
+    private final static class EmptySection extends Section{
+        private static final long serialVersionUID = 0xAEF37565670L;
+        /**
+         * Constructor of EmptySection
+         * An empty Section is a Journey From Geneva to Geneva
+         * @throws ParseException
+         */
+        private EmptySection() throws ParseException {
+            super(new Journey.JourneyBuilder(new JSONObject()).build(), 0,
+                    new Stop.StopBuilder(new JSONObject()).build(),
+                    new Stop.StopBuilder(new JSONObject()).build());
+        }
+    }
+
+
+    /**
+     * Constructor of the class Section
+     * @param journey what Journey this section is part of
+     * @param walkTime time to get to the Section by walk
+     * @param departure where the Section will depart from
+     * @param arrival where the Section will arrive to
+     */
+    private Section(Journey journey, double walkTime, Stop departure, Stop arrival){
+        this.journey = journey;
+        this.walktime = walkTime;
+        this.departure = departure;
+        this.arrival = arrival;
+    }
+
+    public static Section empty() throws ParseException{
+        return new EmptySection();
+    }
+
+    @Override
+    public boolean equals(Object o){
+        if (this == o) {
+            return true;
+        }
+        if (o == null || o.getClass() != this.getClass()) {
+            return false;
+        }
+        Section s = (Section) o;
+        return s.journey.equals(this.journey);
+    }
+
+    /**
+     * Get the journey this Section is part of
+     * @return journey
+     */
+    public Journey getJourney(){
+        return journey;
+    }
+
+    /**
+     * Get the Stop where the Section will depart from
+     * @return departure
+     */
+    public Stop getDeparture(){
+        return departure;
+    }
+
+    /**
+     * get the Stop where the Section is headed to
+     * @return arrival
+     */
+    public Stop getArrival(){
+        return arrival;
+    }
+
+    /**
+     * Builder of Section objects
+     */
+    public final static class SectionBuilder{
+        private JSONObject datas;
+
+        /**
+         * Constructor of the class SectionBuilder
+         * Stock Datas from a JSON object given
+         * @param datas contains datas to construct a Section object
+         */
+        public SectionBuilder(JSONObject datas){
+            this.datas = datas;
+        }
+
+        /**
+         * Build a Section object from the datas obtained
+         * @return a new Section object
+         * @throws ParseException
+         */
+        public Section build() throws ParseException {
+            if(datas.isEmpty()){
+                return new EmptySection();
+            }
+
+            JSONObject journeyJSON = datas.isNull("journey") ? new JSONObject() : datas.getJSONObject("journey");
+            Journey.JourneyBuilder journey = new Journey.JourneyBuilder(journeyJSON);
+
+            double walktime = datas.isNull("walktime") ? 0 : datas.getDouble("walktime");
+
+            JSONObject departureJSON = datas.isNull("departure") ? new JSONObject() : datas.getJSONObject("departure");
+            Stop.StopBuilder departure = new Stop.StopBuilder(departureJSON);
+
+            JSONObject arrivalJSON = datas.isNull("arrival") ? new JSONObject() : datas.getJSONObject("arrival");
+            Stop.StopBuilder arrival = new Stop.StopBuilder(arrivalJSON);
+
+            return new Section(journey.build(), walktime, departure.build(), arrival.build());
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/transport/Service.java b/src/main/java/ch/hepia/api/transport/Service.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf68f9a9fbf873a05c0eb5a03f2aa7be99935e58
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Service.java
@@ -0,0 +1,65 @@
+package ch.hepia.api.transport;
+
+import org.json.JSONObject;
+import java.io.Serializable;
+
+public class Service implements Serializable {
+    private static final long serialVersionUID = 0xACF34565673L;
+    private String regular;
+    private String irregular;
+
+    /**
+     * Empty Service
+     */
+    private final static class EmptyService extends Service{
+        private static final long serialVersionUID = 0xACF34565670L;
+        /**
+         * Constructor of EmptyService
+         * No informations given
+         */
+        private EmptyService(){
+            super("", "");
+        }
+    }
+
+    /**
+     * Constructor of Service class
+     * @param regular is the service regular ?
+     * @param irregular is the service disturbed ?
+     */
+    private Service(String regular, String irregular){
+        this.regular = regular;
+        this.irregular = irregular;
+    }
+
+    /**
+     * Builder of Service objects
+     */
+    public final static class ServiceBuilder{
+        private JSONObject datas;
+
+        /**
+         * Constructor of the class ServiceBuilder
+         * Stock Datas from a JSON object given
+         * @param datas contains datas to construct a Service object
+         */
+        public ServiceBuilder(JSONObject datas){
+            this.datas = datas;
+        }
+
+        /**
+         * Build a Service object from the datas obtained
+         * @return a new Service object
+         */
+        public Service build(){
+            if(datas.isEmpty()){
+                return new EmptyService();
+            }
+
+            String regular = datas.isNull("regular") ? "" : datas.getString("regular");
+            String irregular = datas.isNull("irregular") ? "" : datas.getString("irregular");
+
+            return new Service(regular, irregular);
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/transport/Stop.java b/src/main/java/ch/hepia/api/transport/Stop.java
new file mode 100644
index 0000000000000000000000000000000000000000..910a87672e33de3ebad98a0c31d93039a439dac4
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Stop.java
@@ -0,0 +1,146 @@
+package ch.hepia.api.transport;
+
+import org.json.JSONObject;
+
+import java.sql.Time;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.io.Serializable;
+
+public class Stop implements Serializable {
+    private static final long serialVersionUID = 0xABF34565673L;
+    private Location station;
+    private Time arrival;
+    private Time departure;
+    private int delay;
+    private int plateform;
+    private Prognosis prognosis;
+
+    /**
+     * Default Stop in Geneva
+     */
+    private final static class DefaultStop extends Stop{
+        private static final long serialVersionUID = 0xABF34565670L;
+        /**
+         * Constructor of DefaultStop
+         * DefaultStop is located in Geneva
+         * No Time of departure nor Arrival, no Prognosis, no plateform and no delay
+         * @throws ParseException
+         */
+        private DefaultStop() throws ParseException {
+            super(new Location.LocationBuilder(new JSONObject(), "refine").build(),
+                    new Time(new SimpleDateFormat("dd:kk:mm:ss").parse("00:00:00:00").getTime()),
+                    new Time(new SimpleDateFormat("dd:kk:mm:ss").parse("00:00:00:00").getTime()),
+                    0, 0,
+                    new Prognosis.PrognosisBuilder(new JSONObject()).build());
+        }
+    }
+
+    /**
+     * Constructor of the Stop class
+     * @param station Location where the Stop is
+     * @param arrival time of arrival at this Stop
+     * @param departure time of departure from this Stop
+     * @param delay duration time of unpredictated delay
+     * @param plateform where the Stop will arrive to and depart from
+     * @param prognosis complementary informations (See also {@link Prognosis})
+     */
+    private Stop(Location station, Time arrival, Time departure, int delay, int plateform, Prognosis prognosis){
+        this.station = station;
+        this.arrival = arrival;
+        this.departure = departure;
+        this.delay = delay;
+        this.plateform = plateform;
+        this.prognosis = prognosis;
+    }
+
+    /**
+     * Get the Location of the Stop
+     * @return station
+     */
+    public Location getLocation(){
+        return station;
+    }
+
+    /**
+     * Get the departure time of the transport from the Stop
+     * @return departure
+     */
+    public Time getDepartureTime(){
+        return departure;
+    }
+
+    /**
+     * Get the Arrival time of the transport to the Stop
+     * @return arrival
+     */
+    public Time getArrivalTime(){
+        return arrival;
+    }
+
+    @Override
+    public boolean equals(Object o){
+        if (this == o) {
+            return true;
+        }
+        if (o == null || o.getClass() != this.getClass()) {
+            return false;
+        }
+        Stop s = (Stop) o;
+        return s.arrival.equals(this.arrival) &&
+            s.departure.equals(this.departure) &&
+            s.plateform == this.plateform &&
+            s.station.equals(this.station) &&
+            s.delay == this.delay;
+    }
+
+    /**
+     * Builder of Stop objects
+     */
+    public final static class StopBuilder{
+        private JSONObject datas;
+
+        /**
+         * Constructor of the class StopBuilder
+         * Stock Datas from a JSON object given
+         * @param datas contains datas to construct a Stop object
+         */
+        public StopBuilder(JSONObject datas){
+            this.datas = datas;
+        }
+
+        /**
+         * Build a Stop object from the datas obtained
+         * @return a new Stop object
+         * @throws ParseException
+         */
+        public Stop build() throws ParseException {
+            if(datas.isEmpty()){
+                return new DefaultStop();
+            }
+
+            //getting the first key to know the type of the Location
+            String[] keys = new String[10];
+            datas.keySet().toArray(keys);
+            String type = keys[4];
+            JSONObject locationJSON = datas.isNull("location") ? new JSONObject() : datas.getJSONObject("location");
+            Location.LocationBuilder location = new Location.LocationBuilder(locationJSON, type);
+
+            DateFormat formatter = new SimpleDateFormat("kk:mm:ss");
+
+            String arrivalString = datas.isNull("arrival") ? "00:00:00" : datas.getString("arrival").substring(11, datas.getString("arrival").indexOf("+"));
+            Time arrival = new Time(formatter.parse(arrivalString).getTime());
+            String depatureString = datas.isNull("departure") ? "00:00:00" : datas.getString("departure").substring(11, datas.getString("departure").indexOf("+"));
+            Time departure = new Time(formatter.parse(depatureString).getTime());
+
+            int delay = datas.isNull("delay") ? 0 : datas.getInt("delay");
+            int plateform = datas.isNull("plateform") ? 0 : datas.getInt("plateform");
+
+            JSONObject prognosisJSON = datas.isNull("prognosis") ? new JSONObject() : new JSONObject(datas.get("prognosis"));
+            Prognosis.PrognosisBuilder prognosis = new Prognosis.PrognosisBuilder(prognosisJSON);
+
+            return new Stop(location.build(), arrival, departure, delay, plateform, prognosis.build());
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/transport/Type.java b/src/main/java/ch/hepia/api/transport/Type.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3e6db8e20f4bf491d01cb7692091fa4681c68ee
--- /dev/null
+++ b/src/main/java/ch/hepia/api/transport/Type.java
@@ -0,0 +1,5 @@
+package ch.hepia.api.transport;
+
+public enum Type {
+    STATION, POI, ADDRESS, REFINE
+}
diff --git a/src/main/java/ch/hepia/api/weather/CantFetchWeatherException.java b/src/main/java/ch/hepia/api/weather/CantFetchWeatherException.java
new file mode 100644
index 0000000000000000000000000000000000000000..d838c2715d3357e9e7abf88839207ada6c3ae1d1
--- /dev/null
+++ b/src/main/java/ch/hepia/api/weather/CantFetchWeatherException.java
@@ -0,0 +1,7 @@
+package ch.hepia.api.weather;
+
+import java.io.IOException;
+
+public class CantFetchWeatherException extends IOException { 
+    private static final long serialVersionUID = 0xDBF34565673L;
+}
diff --git a/src/main/java/ch/hepia/api/weather/CantReadWeatherException.java b/src/main/java/ch/hepia/api/weather/CantReadWeatherException.java
new file mode 100644
index 0000000000000000000000000000000000000000..0cc3b94a726b873776eabe8093f2f1100b3fb063
--- /dev/null
+++ b/src/main/java/ch/hepia/api/weather/CantReadWeatherException.java
@@ -0,0 +1,7 @@
+package ch.hepia.api.weather;
+
+import java.io.IOException;
+
+public class CantReadWeatherException extends IOException { 
+    private static final long serialVersionUID = 0xEBF34565673L;
+}
diff --git a/src/main/java/ch/hepia/api/weather/Meteo.java b/src/main/java/ch/hepia/api/weather/Meteo.java
new file mode 100644
index 0000000000000000000000000000000000000000..32258236835d81681048bb42babd28f1f1eb0c52
--- /dev/null
+++ b/src/main/java/ch/hepia/api/weather/Meteo.java
@@ -0,0 +1,115 @@
+package ch.hepia.api.weather;
+
+import ch.hepia.config.AppConfig;
+import org.json.JSONObject;
+
+public class Meteo {
+    private double temperature;
+    private String conditions;
+
+    /**
+     * Constructor of Meteo class to obtain basic information on the weather
+     * @param temperature La température actuelle
+     * @param conditions
+     */
+    private Meteo(double temperature, String conditions){
+        this.temperature = temperature;
+        this.conditions = conditions;
+    }
+
+    /**
+     * Get the temperature of the Meteo Object
+     * @return temperature
+     */
+    public double getTemperature(){ return temperature; }
+
+    /**
+     * Get the weather conditions of the Meteo Object
+     * @return conditions
+     */
+    public String getConditions(){ return conditions; }
+
+    /**
+     * Converts a meteo condition into one of our icons
+     * @return The path to our icon
+     * @apiNote Aussi appellé le switch des enfers.
+     * @apiNote Retourne sunny si ne trouve rien (politique du verre à moitié plein)
+     */
+    public String getConditionsIcon(){
+        switch (this.conditions) {
+            case "Stratus":
+            case "Nuit nuageuse":
+            case "Fortement nuageux":
+            case "Développement nuageux":
+            case "Nuit avec développement nuageux":
+                return AppConfig.WEATHER_ICON_CLOUDY;
+            case "Ciel voilé":
+            case "Nuit légèrement voilée":
+            case "Faibles passages nuageux":
+            case "Brouillard":
+            case "Stratus se dissipant":
+            case "Nuit claire et stratus":
+            case "Faiblement nuageux":
+                return AppConfig.WEATHER_ICON_FOGGY;
+            case "Averses de pluie faible":
+            case "Nuit avec averses":
+            case "Averses de pluie modérée":
+            case "Averses de pluie forte":
+            case "Couvert avec averses":
+            case "Pluie faible":
+            case "Pluie forte":
+            case "Pluie modérée":
+                return AppConfig.WEATHER_ICON_RAINY;
+            case "Averses de neige faible":
+            case "Nuit avec averses de neige faible":
+            case "Neige faible":
+            case "Neige modérée":
+            case "Neige forte":
+            case "Pluie et neige mêlée faible":
+            case "Pluie et neige mêlée modérée":
+            case "Pluie et neige mêlée forte":
+                return AppConfig.WEATHER_ICON_SNOWY;
+            case "Faiblement orageux":
+            case "Nuit faiblement orageuse":
+            case "Orage modéré":
+            case "Fortement orageux":
+                return AppConfig.WEATHER_ICON_STORMY;
+            case "Ensoleillé":
+            case "Nuit claire":
+            case "Nuit bien dégagée":
+            case "Eclaircies":
+            default:
+                return AppConfig.WEATHER_ICON_SUNNY;
+        }       
+    }
+
+    /**
+     * Builder of Meteo Object
+     */
+    public static class MeteoBuilder{
+        private JSONObject datas;
+
+        /**
+         * Constructor of the class MeteoBuilder
+         * Stock Datas from a JSON object given
+         * @param datas contains datas to construct a Meteo object
+         */
+        public MeteoBuilder(JSONObject datas){
+            this.datas = datas;
+        }
+
+        /**
+         * Build a Meteo object from the datas obtained
+         * @return a new Meteo object
+         * @throws CantReadWeatherException
+         */
+        public Meteo build() throws CantReadWeatherException {
+            double temperature = datas.isNull("tmp") ? -2000 : datas.getDouble("tmp");
+            String conditions = datas.getString("condition");
+            if(temperature != -2000 && conditions != null){
+                return new Meteo(temperature, conditions);
+            }
+            throw new CantReadWeatherException();
+        }
+    }
+}
diff --git a/src/main/java/ch/hepia/api/weather/WeatherAPI.java b/src/main/java/ch/hepia/api/weather/WeatherAPI.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfc6bd35f4140c07db51f9e7e876f5aca51fdfd7
--- /dev/null
+++ b/src/main/java/ch/hepia/api/weather/WeatherAPI.java
@@ -0,0 +1,52 @@
+package ch.hepia.api.weather;
+
+import ch.hepia.api.HTTPUtils;
+import ch.hepia.api.transport.Coordinates;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+public class WeatherAPI {
+    private String baseURL = "https://www.prevision-meteo.ch/services/json/";
+
+    /**
+     * Processes the data received by the API
+     * @param path The URL to process
+     * @return The processed data
+     * @throws IOException When the processed data cannot be processed.
+     */
+    private Meteo processData(String path) throws IOException {
+        String content = HTTPUtils.getContent(path);
+
+        JSONObject informations = new JSONObject(content);
+        if(informations.isNull("errors")) {
+            return new Meteo.MeteoBuilder(informations.getJSONObject("current_condition")).build();
+        }
+        throw new CantFetchWeatherException();
+    }
+
+    /**
+     * Get the informations on the weather in the location searched
+     * @param city where we want to display the weather from
+     * @return a Meteo Object
+     * @throws IOException
+     */
+    public Meteo getWeatherFrom(String city) throws IOException {
+        String path = baseURL + URLEncoder.encode(city, StandardCharsets.UTF_8);
+        return processData(path);
+    }
+
+    /**
+     * Get the informations on the weather in the location searched
+     * @param coordinates where we want to display the weather from
+     * @return a Meteo Object
+     * @throws IOException
+     */
+    public Meteo getWeatherFrom(Coordinates coordinates) throws IOException {
+        String path = baseURL + "lat="+coordinates.getX()+"lng="+coordinates.getY();
+        return processData(path);
+    }
+
+}
diff --git a/src/main/java/ch/hepia/config/AppConfig.java b/src/main/java/ch/hepia/config/AppConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..56bc22c8f721de82cf45e514499b8a23766aab2f
--- /dev/null
+++ b/src/main/java/ch/hepia/config/AppConfig.java
@@ -0,0 +1,67 @@
+package ch.hepia.config;
+
+import javafx.scene.paint.Color;
+
+import java.util.List;
+
+/**
+ * The variables of the app
+ */
+public final class AppConfig {
+	public static final String APP_NAME = "TransportWave";
+	public static final Integer APP_WIDTH = 1000;
+	public static final Integer APP_HEIGHT = 565;
+
+	public static final Integer APP_MAIN_VIEW_WIDTH = 622;
+
+	public static final String RABBITMQ_HOSTNAME = "redgrave.science";
+	public static final String RABBITMQ_USERNAME = "frog";
+	public static final String RABBITMQ_PASSWORD = "poney1234";
+	public static final String RABBITMQ_EXCHANGE = "broadcaster";
+	public static final Integer RABBITMQ_PORT = 5672; // The MQ uses the default port, so this value is not used.
+
+	public static final String WEATHER_DESTINATION = "Météo à destination";
+
+	public static final String ERROR_API_WEATHER = "Impossible de contacter les services météo.";
+	public static final String ERROR_API_UNREACHABLE = "Impossible de contacter les services de transport Suisses.";
+	public static final String ERROR_API_MQ = "Une erreur s'est produite lors de la publication de cet évènement.";
+	public static final String DEFAULT_JOURNEY_TEXT = "Vous n'avez prévu aucun voyage pour le moment.";
+
+	public static final String COMMON_ITINERARY_TEXT = "Vous allez croiser %s à %s ! Pensez à vous saluer !";
+
+	public static List<String> CHAT_COMMANDS = List.of(
+        "help",
+        "block",
+		"unblock",
+		"blacklist"
+    );
+
+	/**
+	 * Resources
+	 */
+	public static final String CHAT_TRAIN_ICON = "/img/train.png";
+	public static final String CHAT_TRAIN_ICON_SELF = "/img/train_self.png";
+	public static final String CHAT_MESSAGE_ICON = "/img/bubble.png";
+	public static final String CHAT_MESSAGE_ICON_SELF = "/img/bubble_self.png";
+	public static final String HELP_MESSAGE_ICON = "/img/help.png";
+	public static final String WEATHER_ICON_CLOUDY = "/img/cloudy.png";
+	public static final String WEATHER_ICON_FOGGY = "/img/foggy.png";
+	public static final String WEATHER_ICON_RAINY = "/img/rainy.png";
+	public static final String WEATHER_ICON_SNOWY = "/img/snowy.png";
+	public static final String WEATHER_ICON_STORMY = "/img/stormy.png";
+	public static final String WEATHER_ICON_SUNNY = "/img/sunny.png";
+	public static final String JOURNEY_ICON_COMMON_ITINERARY = "/img/friends.png";
+	public static final String EASTER_EGG_23DBM = "/img/javafx_res1";
+	public static final String EASTER_EGG_CAFE = "/img/javafx_res2";
+	public static final String EASTER_EGG_EFFET_DE_BORD = "/img/javafx_res3";
+	public static final String EASTER_EGG_SCRUM = "/img/javafx_res4";
+
+	/**
+	 * Style
+	 */
+	public static final Color COLOR_BLUE_10_OPACITY = Color.color(0.207, 0.694, 0.933, 0.1);
+	public static final Color COLOR_BLUE_50_OPACITY = Color.color(0.207, 0.694, 0.933, 0.5);
+	public static final Color COLOR_BLUE_100_OPACITY = Color.color(0.207, 0.694, 0.933);
+
+	public static final Color COLOR_GREEN_20_OPACITY = Color.color(1.0, 0.9, 0.5, 0.2);
+}
diff --git a/src/main/java/ch/hepia/config/AppContext.java b/src/main/java/ch/hepia/config/AppContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1db3272275c6b9bf947cdac9ae1f52cb8e800a1
--- /dev/null
+++ b/src/main/java/ch/hepia/config/AppContext.java
@@ -0,0 +1,47 @@
+package ch.hepia.config;
+
+import ch.hepia.models.User;
+import ch.hepia.mq.MessageManager;
+
+import java.util.Optional;
+
+/**
+ * Represents the current config of the app.
+ */
+public class AppContext {
+	private MessageManager messageManager;
+	private Optional<User> user;
+
+	/**
+	 * Main constructor
+	 * @param messageManager the message manager instance
+	 */
+	public AppContext(MessageManager messageManager){
+		this.messageManager = messageManager;
+		this.user = Optional.empty();
+	}
+
+	/**
+	 * Gets the message manager
+	 * @return The message manager
+	 */
+	public MessageManager getMessageManager() {
+		return messageManager;
+	}
+
+	/**
+	 * Gets the user
+	 * @return The current user
+	 */
+	public Optional<User> getUser() {
+		return user;
+	}
+
+	/**
+	 * Sets the user
+	 * @param user the user
+	 */
+	public void setUser(User user) {
+		this.user = Optional.of(user);
+	}
+}
diff --git a/src/main/java/ch/hepia/events/ChatMessage.java b/src/main/java/ch/hepia/events/ChatMessage.java
new file mode 100644
index 0000000000000000000000000000000000000000..8fd7251a180c88081caa8b14d785453d3345594a
--- /dev/null
+++ b/src/main/java/ch/hepia/events/ChatMessage.java
@@ -0,0 +1,32 @@
+package ch.hepia.events;
+
+import ch.hepia.models.User;
+
+import java.io.Serializable;
+
+/**
+ * Implement a chat message event
+ */
+public class ChatMessage implements Event, Serializable {
+    private static final long serialVersionUID = 0xAEF34565631L;
+    private User user;
+    private String chatMessage;
+
+    /**
+     * Main constructor
+     * @param user The user
+     * @param chatMessage The message
+     */
+    public ChatMessage(User user, String chatMessage) {
+        this.user = user;
+        this.chatMessage = chatMessage;
+    }
+
+    public User getUser() {
+        return this.user;
+    }
+
+    public String getMessage() {
+        return this.chatMessage;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ch/hepia/events/Event.java b/src/main/java/ch/hepia/events/Event.java
new file mode 100644
index 0000000000000000000000000000000000000000..8504935660b525040bac4cf5d05fbad904a2a146
--- /dev/null
+++ b/src/main/java/ch/hepia/events/Event.java
@@ -0,0 +1,14 @@
+package ch.hepia.events;
+
+import ch.hepia.models.User;
+
+/**
+ * Represents an event send into the MQ.
+ */
+public interface Event {
+	/**
+	 * Gets the user
+	 * @return the user
+	 */
+	User getUser();
+}
diff --git a/src/main/java/ch/hepia/events/JoinedJourney.java b/src/main/java/ch/hepia/events/JoinedJourney.java
new file mode 100644
index 0000000000000000000000000000000000000000..251ab55e9c3539888d4e66eb75c79783ac418f12
--- /dev/null
+++ b/src/main/java/ch/hepia/events/JoinedJourney.java
@@ -0,0 +1,60 @@
+package ch.hepia.events;
+
+import ch.hepia.api.transport.Connection;
+import ch.hepia.models.User;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * Represents an event where a user has selected (and joined) a journey. A
+ * journey is composed of several sections (Genève-Vauderens : Genève-Morges is
+ * the first section, Morges-Vauderens is the second.)
+ */
+public class JoinedJourney implements Event, Serializable {
+    private static final long serialVersionUID = 0xAEF34565673L;
+    private User user;
+    private Connection connection;
+    private String weatherToDestination;
+
+    /**
+     * Main constructor
+     * 
+     * @param user     The user triggering the event
+     * @param connection The sections of the journey
+     */
+    public JoinedJourney(User user, Connection connection, String weatherToDestination) {
+        this.user = user;
+        this.connection = connection;
+        this.weatherToDestination = weatherToDestination;
+    }
+
+    public User getUser() {
+        return this.user;
+    }
+
+    public Connection getConnection() {
+        return this.connection;
+    }
+
+    public String getWeatherToDestination(){
+        return weatherToDestination;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != this.getClass()) {
+            return false;
+        }
+        JoinedJourney jj = (JoinedJourney) obj;
+        return jj.connection.equals(this.connection) && jj.user.equals(this.user);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.user, this.connection);
+    }
+}
diff --git a/src/main/java/ch/hepia/events/LeftJourney.java b/src/main/java/ch/hepia/events/LeftJourney.java
new file mode 100644
index 0000000000000000000000000000000000000000..ee30b6185995a2992ddacab5d16bf4303270b6eb
--- /dev/null
+++ b/src/main/java/ch/hepia/events/LeftJourney.java
@@ -0,0 +1,41 @@
+package ch.hepia.events;
+
+import ch.hepia.api.transport.Connection;
+import ch.hepia.models.User;
+
+import java.io.Serializable;
+
+/**
+ * Represents an event where a user has left a journey.
+ * A journey is composed of several sections
+ * (Genève-Vauderens : Genève-Morges is the first section, Morges-Vauderens is the second.)
+ */
+public class LeftJourney implements Event, Serializable {
+    private static final long serialVersionUID = 0xAEF34565674L;
+    private User user;
+    private Connection connection;
+    private String weatherToDestination;
+
+    /**
+     * Main constructor
+     * @param user The user triggering the event
+     * @param connection The sections of the journey
+     */
+    public LeftJourney(User user, Connection connection, String weatherToDestination){
+        this.user = user;
+        this.connection = connection;
+        this.weatherToDestination = weatherToDestination;
+    }
+
+    public User getUser() {
+        return this.user;
+    }
+
+    public Connection getConnection() {
+        return this.connection;
+    }
+
+    public String getWeatherToDestination(){
+        return weatherToDestination;
+    }
+}
diff --git a/src/main/java/ch/hepia/events/Meeting.java b/src/main/java/ch/hepia/events/Meeting.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d56620c096fed346a9ec74da3b6c9600f4b9b45
--- /dev/null
+++ b/src/main/java/ch/hepia/events/Meeting.java
@@ -0,0 +1,52 @@
+package ch.hepia.events;
+
+import ch.hepia.api.transport.Section;
+import ch.hepia.models.User;
+
+import java.io.Serializable;
+
+/**
+ * Implements a meeting event, that is when two users meet somewhere.
+ */
+public class Meeting implements Serializable, Event {
+	private static final long serialVersionUID = 0xAEF34565679L;
+	private User user; //
+	private User partner;
+	private Section section;
+
+
+	/**
+	 * Main constructor
+	 * @param userOne The first user to meet the other
+	 * @param userTwo The other user to meet the first one
+	 * @param section The section where both meet each other
+	 */
+	public Meeting(User userOne, User userTwo, Section section){
+		this.user = userOne;
+		this.partner = userTwo;
+		this.section = section;
+	}
+
+
+	@Override
+	public User getUser() {
+		return user;
+	}
+
+	/**
+	 * Gets the user two
+	 * @return The second user
+	 */
+	public User getPartner(){
+		return partner;
+	}
+
+	/**
+	 * Gets the section where the two users meet
+	 * @return The section where the two users meet
+	 */
+	public Section getSection(){
+		return section;
+	}
+
+}
diff --git a/src/main/java/ch/hepia/models/User.java b/src/main/java/ch/hepia/models/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f70f2ce79f740be850df054f5641414aadee3ad
--- /dev/null
+++ b/src/main/java/ch/hepia/models/User.java
@@ -0,0 +1,72 @@
+package ch.hepia.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+/**
+ * Represents an user.
+ */
+public class User implements Serializable {
+	private static final long serialVersionUID = 0xAEF34564613L;
+	private String username;
+	private transient List<User> ignoredUserList;
+
+	/**
+	 * Main constructor
+	 * @param username the username
+	 */
+	public User(String username){
+		this.username = username;
+		this.ignoredUserList = new ArrayList<>();
+	}
+
+	/** 
+	 * Add a user to the ignored list
+	 * @param user User to add
+	*/
+	public void addIgnoredUser(User user) {
+		this.ignoredUserList.add(user); 
+	}
+
+	/** 
+	 * Remove a user to the ignored list
+	 * @param user User to remove
+	*/	
+	public void removeIgnoredUser(User user) {
+		this.ignoredUserList.remove(user);
+	}
+
+	/** 
+	 * Get the ignored list
+	 * @return The Ingored user list to remove
+	*/	
+	public List<User> getIgnoredUserList(){
+		return this.ignoredUserList;
+	}
+
+	/** 
+	 * Get the name
+	 * @return The username
+	*/
+	public String getName(){
+		return this.username;
+	}
+
+	@Override
+	public String toString(){
+		return this.username;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if((o == null) || (this.getClass() != o.getClass())){
+			return false;
+		} else {
+			User u = (User)o;
+			if (this.username.equals(u.username)){
+				return true;
+			}
+		}
+		return false;
+	}
+}
diff --git a/src/main/java/ch/hepia/mq/Message.java b/src/main/java/ch/hepia/mq/Message.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2a23c37e44ed0c85a1f8b133de8f4231ff028d3
--- /dev/null
+++ b/src/main/java/ch/hepia/mq/Message.java
@@ -0,0 +1,39 @@
+package ch.hepia.mq;
+import java.io.Serializable;
+
+/**
+ * Represents a message sent to the message queue.
+ */
+public final class Message implements Serializable {
+    public static enum Type {
+        JoinedJourney, LeftJourney, ChatMessage, Meeting
+    }
+    
+    private static final long serialVersionUID = 0xAEF34565673L;
+    private final Type type;
+    private byte[] data;
+
+    /**
+     * Standard constructor
+     * @param type The type of the message
+     * @param object Payload to serialize
+     */
+    public <T extends Serializable> Message(Type type, T object) {
+        this.type = type;
+        this.data = MessageQueue.serialize(object);
+    }
+    /**
+     * Get the message type
+     * @return The type of the message
+     */
+    public Type getMessageType() {
+        return this.type;
+    }
+    /**
+     * Get the payload of the message
+     * @return The payload of the message
+     */
+    public <T> T getData() {
+        return MessageQueue.unserialize(this.data);
+    }
+}
diff --git a/src/main/java/ch/hepia/mq/MessageManager.java b/src/main/java/ch/hepia/mq/MessageManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..541cc700ee22ae8ca631af8c4ac566b679e0b8ce
--- /dev/null
+++ b/src/main/java/ch/hepia/mq/MessageManager.java
@@ -0,0 +1,155 @@
+package ch.hepia.mq;
+
+import ch.hepia.events.ChatMessage;
+import ch.hepia.events.JoinedJourney;
+import ch.hepia.events.LeftJourney;
+import ch.hepia.events.Meeting;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Represents the wrapper for the message queue.
+ */
+public class MessageManager extends MessageQueue {
+
+
+    public MessageManager(String host, String username, String password, String exchange) throws Exception {
+        super(host, username, password, exchange);
+    }
+
+    /*
+     * Private functions
+     */
+
+    /**
+     * Send an event to the queue
+     * @param type The type of the message
+     * @param event The event
+     */
+    private <T extends Serializable> void sendEvent(Message.Type type, T event) {
+        Message m = new Message(type, event);
+        try {
+            this.sendBytes(MessageQueue.serialize(m));
+        } catch (final Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Add a consumer for a specific type of message
+     * @param type The type of the message
+     * @param eventHandler Consumer to add
+     */
+    private <T extends Serializable> void conditionalSubscribe(Message.Type type, Consumer<T> eventHandler,
+            Predicate<T> condition) {
+        Consumer<byte[]> consumer = (bytes) -> {
+            Message receivedMessage = MessageQueue.unserialize(bytes);
+            if (receivedMessage.getMessageType() == type) {
+                T event = receivedMessage.getData();
+                if (condition.test(event)) {
+                    eventHandler.accept(event);
+                }
+            }
+        };
+        this.addConsumer(consumer);
+    }
+
+    /*
+     * Public functions
+     */
+
+    /**
+     * Subscribe to all JoinedJourney events
+     * @param eventHandler JoinedJourney consumer
+     */
+    public void subscribeJoinedJourney(Consumer<JoinedJourney> eventHandler) {
+        this.conditionalSubscribeJoinedJourney(eventHandler, (event) -> true);
+    }
+
+    /**
+     * Subscribe to all LeftJourney events
+     * @param eventHandler LeftJourney consumer
+     */
+    public void subscribeLeftJourney(Consumer<LeftJourney> eventHandler) {
+        this.conditionalSubscribeLeftJourney(eventHandler, (event) -> true);
+    }
+
+    /**
+     * Subscribe to all ChatMessage events
+     * @param eventHandler ChatMessage consumer
+     */
+    public void subscribeChatMessage(Consumer<ChatMessage> eventHandler) {
+        this.conditionalSubscribeChatMessage(eventHandler, (event) -> true);
+    }
+    
+    /**
+     * Subscribe to JoinedJourney events validating the condition
+     * @param eventHandler JoinedJourney consumer
+     * @param condition JoinedJourney predicate
+     */
+    public void conditionalSubscribeJoinedJourney(Consumer<JoinedJourney> eventHandler,
+            Predicate<JoinedJourney> condition) {
+        this.conditionalSubscribe(Message.Type.JoinedJourney, eventHandler, condition);
+    }
+
+    /**
+     * Subscribe to LeftJourney events validating the condition
+     * @param eventHandler LeftJourney consumer
+     * @param condition LeftJourney predicate
+     */
+    public void conditionalSubscribeLeftJourney(Consumer<LeftJourney> eventHandler, Predicate<LeftJourney> condition) {
+        this.conditionalSubscribe(Message.Type.LeftJourney, eventHandler, condition);
+    }
+    
+    /**
+     * Subscribe to ChatMessage events validating the condition
+     * @param eventHandler ChatMessage consumer
+     * @param condition ChatMessage predicate
+     */
+    public void conditionalSubscribeChatMessage(Consumer<ChatMessage> eventHandler, Predicate<ChatMessage> condition) {
+        this.conditionalSubscribe(Message.Type.ChatMessage, eventHandler, condition);
+    }
+
+    /**
+     * Subscribe to Meetings events
+     * @param eventHandler Meeting consumer
+     * @param condition Meeting predicate
+     */
+    public void conditionalSubscribeMeeting(Consumer<Meeting> eventHandler, Predicate<Meeting> condition) {
+        this.conditionalSubscribe(Message.Type.Meeting, eventHandler, condition);
+    }
+
+    /**
+     * Send a JoinedJourney event
+     * @param event JoinedJourney event
+     */
+    public void sendJoinedJourney(JoinedJourney event) {
+        this.sendEvent(Message.Type.JoinedJourney, event);
+    }
+
+    /**
+     * Send a LeftJourney event
+     * @param event LeftJourney event
+     */
+    public void sendLeftJourney(LeftJourney event) {      
+        this.sendEvent(Message.Type.LeftJourney, event);
+    }
+
+    /**
+     * Send a ChatMessage event
+     * @param event ChatMessage event
+     */
+    public void sendChatMessage(ChatMessage event) {
+        this.sendEvent(Message.Type.ChatMessage, event);
+    }
+
+    /**
+     * Send a Meeting event
+     * @param event Meeting event
+     */
+    public void sendMeeting(Meeting event) {
+        this.sendEvent(Message.Type.Meeting, event);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ch/hepia/mq/MessageQueue.java b/src/main/java/ch/hepia/mq/MessageQueue.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6823d7c892282fecc1447d684039e9057a02a3e
--- /dev/null
+++ b/src/main/java/ch/hepia/mq/MessageQueue.java
@@ -0,0 +1,111 @@
+package ch.hepia.mq;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.DeliverCallback;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Implementation of the message queue.
+ */
+public abstract class MessageQueue {
+	private final String exchange;
+	private final Connection mqConnection;
+	private final Channel mqChannel;
+    private final List<Consumer<byte[]>> consumers;
+    
+    /** 
+     * Construct a temporary RabbitMQ queue, bind it to an exchange
+     * on the server
+     * @param host Hostname of the rabbitmq server
+     * @param username RabbitMQ user
+     * @param password RabbitMQ password
+     * @param exchange Exchange to bind
+    */
+	public MessageQueue(String host, String username, String password, String exchange) throws Exception {
+		this.consumers = new ArrayList<>();
+		this.exchange = exchange;
+		ConnectionFactory queueConnector = new ConnectionFactory();
+		queueConnector.setHost(host);
+		queueConnector.setUsername(username);
+		queueConnector.setPassword(password);
+
+		this.mqConnection = queueConnector.newConnection();
+		this.mqChannel = this.mqConnection.createChannel();
+		String queueName = this.mqChannel.queueDeclare().getQueue();
+		this.mqChannel.queueBind(queueName, this.exchange, "");
+		DeliverCallback deliverCallback = (consumerTag, delivery) -> {
+			for (Consumer<byte[]> consumer : this.consumers) {
+				consumer.accept(delivery.getBody());
+			}
+		};
+		this.mqChannel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
+		});
+	}
+
+    /** 
+     * Add a consumer to the queue
+     * @param messageHandler Message consumer
+    */
+	protected void addConsumer(Consumer<byte[]> messageHandler) {
+		this.consumers.add(messageHandler);
+	}
+
+    /** 
+     * Send a byte array in the queue. 
+     * @param bytes The byte array
+    */
+	protected void sendBytes(byte[] bytes) throws Exception {
+		this.mqChannel.basicPublish(this.exchange, "", null, bytes);
+	}
+
+    /** 
+     * Terminate the connection with rabbitMQ
+    */
+	public void close() throws Exception {
+		this.mqChannel.close();
+		this.mqConnection.close();
+	}
+
+    /** 
+     * Serialize an object, return a byte array.
+     * @param object Object to serialize
+    */
+	public static <T extends Serializable> byte[] serialize(T object){
+		try (
+				ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+				ObjectOutputStream oos = new ObjectOutputStream(byteStream)
+		) {
+			oos.writeObject(object);
+			oos.flush();
+			return byteStream.toByteArray();
+		} catch (final Exception e) {
+			e.printStackTrace();
+			System.exit(1);
+		}
+		return null;
+    }
+    
+    /** 
+     * Deserialize a byte array
+     * @param serializedData serialized object
+    */
+	public static <T extends Serializable> T unserialize(byte[] serializedData){
+		T result = null;
+		try (
+				ByteArrayInputStream inputStream = new ByteArrayInputStream(serializedData);
+				ObjectInputStream ois = new ObjectInputStream(inputStream)
+		) {
+			result = (T) ois.readObject();
+		} catch (final Exception e) {
+			e.printStackTrace();
+			System.exit(1);
+		}
+		return result;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/ch/hepia/ui/ConnectionController.java b/src/main/java/ch/hepia/ui/ConnectionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c86154d3b1df55d5eb326da8ddefd64f59bfabd
--- /dev/null
+++ b/src/main/java/ch/hepia/ui/ConnectionController.java
@@ -0,0 +1,84 @@
+package ch.hepia.ui;
+
+import ch.hepia.Main;
+import ch.hepia.config.AppConfig;
+import ch.hepia.models.User;
+import javafx.animation.FadeTransition;
+import javafx.application.Platform;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.fxml.Initializable;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.stage.Stage;
+import javafx.util.Duration;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+/**
+ * Implements the login screen
+ */
+public class ConnectionController implements Initializable {
+
+	@FXML
+	private Label appNameLabel;
+
+	@FXML
+	private Label appConnectionStatusLabel;
+
+	@FXML
+	private TextField usernameSelectionTextField;
+
+	@FXML
+	private Label errorLabel;
+
+	@FXML
+	private Button handleConfirmButton;
+
+	@FXML
+	public void handleConfirmButton(ActionEvent event) throws Exception {
+		handleConfirmButton.setDisable(true);
+		String oldText = handleConfirmButton.getText();
+		handleConfirmButton.setText("Veuillez patienter...");
+		if (usernameSelectionTextField.getText().isEmpty()){
+			FadeTransition fadeIn = new FadeTransition(Duration.millis(3000));
+			errorLabel.setText("Veuillez rentrer un nom d'utilisateur.");
+			errorLabel.setVisible(true);
+			fadeIn.setNode(errorLabel);
+			fadeIn.setFromValue(1.0);
+			fadeIn.setToValue(0.0);
+			fadeIn.setCycleCount(1);
+			fadeIn.setAutoReverse(false);
+			fadeIn.playFromStart();
+		} else {
+			User user = new User(usernameSelectionTextField.getText());
+			Main.getContext().setUser(user);
+			Stage stage = (Stage) handleConfirmButton.getScene().getWindow();
+			Parent root = FXMLLoader.load(Main.class.getResource("/fxml/MainWindow.fxml"));
+			Scene scene = new Scene(root, AppConfig.APP_WIDTH, AppConfig.APP_HEIGHT);
+
+			stage.close();
+			stage.setTitle(AppConfig.APP_NAME);
+			stage.setScene(scene);
+			stage.show();
+
+
+		}
+		handleConfirmButton.setText(oldText);
+		handleConfirmButton.setDisable(false);
+	}
+
+	@Override
+	public void initialize(URL location, ResourceBundle resources) {
+		// Faire les initialisations avant affichage ici.
+		appNameLabel.setText(AppConfig.APP_NAME);
+		appConnectionStatusLabel.setText("Merci de rentrer votre nom. Il permettra de vous identifier.");
+		Platform.runLater(() -> usernameSelectionTextField.requestFocus());
+		UiUtils.buttonWhenEnter(usernameSelectionTextField, handleConfirmButton);
+	}
+}
diff --git a/src/main/java/ch/hepia/ui/MainWindowController.java b/src/main/java/ch/hepia/ui/MainWindowController.java
new file mode 100644
index 0000000000000000000000000000000000000000..579bd51f070c506c72a9a6b246b669df44b75b43
--- /dev/null
+++ b/src/main/java/ch/hepia/ui/MainWindowController.java
@@ -0,0 +1,612 @@
+package ch.hepia.ui;
+
+import ch.hepia.Main;
+import ch.hepia.api.transport.*;
+import ch.hepia.api.weather.WeatherAPI;
+import ch.hepia.config.AppConfig;
+import ch.hepia.config.AppContext;
+import ch.hepia.events.*;
+import ch.hepia.models.User;
+import javafx.animation.TranslateTransition;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.geometry.Insets;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.control.*;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.*;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.CycleMethod;
+import javafx.scene.paint.LinearGradient;
+import javafx.scene.paint.Stop;
+import javafx.util.Duration;
+
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.Clip;
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.function.Predicate;
+
+/**
+ * Implements all of the logic, UI and events for the main window.
+ * It is heavy but it's the price for a nice UI
+ */
+public class MainWindowController implements Initializable {
+
+    @FXML
+    private Label currentJourneyLabel;
+
+    @FXML
+    private Label startStopLabel;
+
+    @FXML
+    private ComboBox<String> originComboBox;
+
+    @FXML
+    private ComboBox<String> destinationComboBox;
+
+    @FXML
+    private Canvas connectionCanvas;
+
+    @FXML
+    private Button searchOriginButton;
+
+    @FXML
+    private Button searchDestinationButton;
+
+    @FXML
+    private Button launchItineraryButton;
+
+    @FXML
+    private Button sendMessageButton;
+
+    @FXML
+    private TextField messageTextBox;
+
+    @FXML
+    private Pane chatContainer;
+
+    @FXML
+    private Pane connectionContainer;
+
+    // OWN ATTRIBUTES
+
+    private List<Connection> displayedConnections;
+
+    private Connection currentJourney;
+
+    /**
+     * Shows a sad message when the API/app crashes.
+     */
+    private void showSadMessage(String text){
+        UiUtils.dialog(Alert.AlertType.ERROR, "Erreur",
+            "Une erreur est survenue.", text);
+    }
+
+    /**
+     * Searches stops that matches the query.
+     * @param newValue The stop name query.
+     * @param transportApi The API to connect to
+     * @param target The combo box where to put the results.
+     */
+    private void searchStops(String newValue, LinkAPI transportApi, ComboBox<String> target){
+        if (!newValue.isEmpty() && newValue.length() > 3 && !target.getItems().contains(newValue)){
+            try {
+                List<Location> locations = transportApi.getStations(newValue);
+                ArrayList<String> results = new ArrayList<>();
+                locations.forEach((location) -> results.add(location.getName()));
+                target.getItems().clear();
+                target.getItems().addAll(results);
+                target.show();
+            } catch (IOException e) {
+                showSadMessage(AppConfig.ERROR_API_UNREACHABLE);
+            }
+        }
+    }
+
+    private void drawWeather(Connection connection) {
+        Coordinates coords = connection.getTo().getLocation().getCoordinates();
+        WeatherAPI api = new WeatherAPI();
+        try {
+            String wth = api.getWeatherFrom(coords).getConditionsIcon();
+            Image weatherImg = new Image(Main.class.getResourceAsStream(wth));
+            ImageView image = new ImageView(weatherImg);
+            image.setTranslateX(580);
+            image.setTranslateY(30);
+            connectionContainer.getChildren().add(image);
+            GraphicsContext gcx = connectionCanvas.getGraphicsContext2D();
+            gcx.setFill(Color.BLACK);
+            gcx.fillText(AppConfig.WEATHER_DESTINATION, 450, 10);
+        } catch (IOException e) {
+            e.printStackTrace();
+            showSadMessage(AppConfig.ERROR_API_WEATHER);
+        }
+    }
+
+
+    /**
+     * Draws a connection between two places. Basically lines.
+     * @param connection The connection to draw
+     * @param x Where to draw
+     * @param y Where to draw but vertically
+     */
+    private void drawConnection(Connection connection, int x, int y){
+        List<Section> sections = connection.getSections();
+        GraphicsContext gcx = connectionCanvas.getGraphicsContext2D();
+        drawWeather(connection);
+        //Draw starting station
+        gcx.setFill(Color.RED);
+        gcx.fillText(sections.get(0).getDeparture().getLocation().getName(), x, y - 20);
+        gcx.setFill(Color.BLACK);
+        gcx.fillText(sections.get(0).getDeparture().getDepartureTime().toString(), x, y - 40);
+        gcx.setFill(Color.RED);
+        gcx.fillOval(x - 5, y - 5, 10, 10);
+        gcx.strokeLine(x, y, x + 622 / (sections.size() + 1), y);
+		for (int i = 0; i < sections.size(); i++){
+            //Draw each step
+            drawConnectionStep(sections.get(i), sections.size(), x, y, i);
+		}
+    }
+    /**
+     * Draws a connection step between two places.
+     * @param section The section to draw
+     * @param size Number of section in the connection
+     * @param x Where to draw
+     * @param y Where to draw but vertically
+     * @param cnt Section position in connection list.
+     */
+    private void drawConnectionStep(Section section, int size, int x, int y, int cnt) {
+        GraphicsContext gcx = connectionCanvas.getGraphicsContext2D();
+        int xSize = (AppConfig.APP_MAIN_VIEW_WIDTH / (size + 1));
+        gcx.strokeLine( x + xSize * (cnt + 1), y, AppConfig.APP_MAIN_VIEW_WIDTH / (size + 1), y);
+        gcx.setFill(Color.RED);
+        gcx.fillOval(x + xSize * (cnt + 1) - 5, y - 5, 10, 10);
+        gcx.fillText(section.getArrival().getLocation().getName(), x + xSize * (cnt + 1), y - 20);
+        gcx.setFill(Color.BLACK);
+        gcx.fillText(section.getArrival().getArrivalTime().toString(), x + xSize * (cnt + 1), y - 40);
+        Journey jrn = section.getJourney();
+            String transportType = "MARCHE"; //Draw the type of transport used on previous step
+            if (!(jrn instanceof Journey.EmptyJourney)) {
+                transportType = jrn.getCategory() + ", " + jrn.getNumber();
+            }
+        gcx.setFill(Color.BLUE);
+        gcx.fillText(transportType,x + xSize * (cnt), y - 55);
+        gcx.setFill(Color.RED);
+    }
+
+    /**
+     * Draws a newly received message
+     * @param message The message to draw
+     */
+    private void drawMessage(String message, String image, Color color){
+        Pane p = new Pane();
+        setChatPanelStyle(p, color);
+        Image lblImg = new Image(Main.class.getResourceAsStream(image));
+        Label msg = new Label();
+        msg.setWrapText(true);
+        msg.setStyle("-fx-padding: 8px");
+        msg.setText(message);
+        msg.setMaxWidth(310);
+        msg.setGraphic(new ImageView(lblImg));
+        p.getChildren().add(msg);
+        if (chatContainer.getChildren().size() >= 7) {
+            chatContainer.getChildren().remove(0);
+        }
+        insertMessageIntoQueue();
+        chatContainer.getChildren().add(p);
+    }
+
+    /**
+     * Style a chat panel before pushing
+     * @param p Panel to style
+     * @param color Background color of the gradient
+     */
+    private void setChatPanelStyle(Pane p, Color color) {
+        p.setBackground(
+            new Background(
+                new BackgroundFill(
+                    new LinearGradient(0, 0, 0, 1, true,
+                    CycleMethod.NO_CYCLE,
+                    new Stop(1, color),
+                    new Stop(0, Color.WHITE)
+                    ),
+                new CornerRadii(5),
+                Insets.EMPTY)));
+        p.setBorder(new Border(new BorderStroke(
+                Color.color(0.6, 0.6, 0.6), BorderStrokeStyle.SOLID, new CornerRadii(5),
+                BorderWidths.DEFAULT)));
+        p.setPrefWidth(312);
+        p.setMaxWidth(320);
+    }
+
+    /**
+     * Style a connection panel
+     * @param p Panel to style
+     */
+    private void setConnectionPanelStyle(Pane p, Color color) {
+        p.setBackground(
+            new Background(
+                new BackgroundFill(
+                    new LinearGradient(0, 0, 0, 1, true, 
+                    CycleMethod.NO_CYCLE,
+                    new Stop(1, color), 
+                    new Stop(0, Color.TRANSPARENT)
+                    ),
+                new CornerRadii(5), 
+                Insets.EMPTY)));
+        p.setBorder(new Border(new BorderStroke(Color.color(0.6, 0.6, 0.6), BorderStrokeStyle.SOLID,
+                new CornerRadii(5), BorderWidths.DEFAULT)));
+        p.setPrefWidth(AppConfig.APP_MAIN_VIEW_WIDTH + 10);
+        p.setPrefHeight(90);
+    }
+
+    /**
+     * Wrapper for local chat message
+     * @param message Message to print
+     */
+    private void drawHelpMessage(String message) {
+        drawMessage(message, AppConfig.HELP_MESSAGE_ICON, Color.color(1, 0.9, 0.5, 0.3));
+    }
+
+    /**
+     * Print available commands in the chat
+     */
+    private void drawCommands(){
+        StringBuilder buffer = new StringBuilder("/"+AppConfig.CHAT_COMMANDS.get(0));
+        for(int i = 1; i < AppConfig.CHAT_COMMANDS.size(); i++){
+            buffer.append(", /").append(AppConfig.CHAT_COMMANDS.get(i));
+        }
+        drawHelpMessage(buffer.toString());
+    }
+
+    /**
+     * Moves the older messages downwards
+     */
+    private void insertMessageIntoQueue(){
+        for (int i = 0; i < chatContainer.getChildren().size(); i++){
+            Pane sp = (Pane) chatContainer.getChildren().get(i);
+            TranslateTransition t = new TranslateTransition(Duration.seconds(0.25), sp);
+
+            t.setToY((chatContainer.getChildren().size() - i) * (sp.getHeight() + 4));
+            t.play();
+        }
+    }
+
+    /**
+     * Leaves a journey and broadcast it
+     * @param app The app context
+     */
+    private void leaveJourney(AppContext app, String weatherIcon){
+        app.getMessageManager().sendLeftJourney(new LeftJourney(app.getUser().get(), currentJourney, weatherIcon));
+        currentJourneyLabel.setText(AppConfig.DEFAULT_JOURNEY_TEXT);
+        try {
+            currentJourney = new Connection.EmptyConnection();
+        } catch (ParseException e){
+            showSadMessage("Quelque chose s'est mal passé en voulant quitter votre trajet.");
+            e.printStackTrace();
+        }
+        currentJourneyLabel.setUnderline(false);
+    }
+
+    /**
+     * Handle current connection UI reactivity events
+     * @param app The app context
+     * @param api The Weather API
+     * @throws IOException Whenever the Weather API goofs up
+     */
+    private void setupCurrentConnection(AppContext app, WeatherAPI api) throws IOException {
+        String wtcd = api.getWeatherFrom(currentJourney.getTo().getLocation().getCoordinates()).getConditionsIcon();
+        currentJourneyLabel.setText("Vous voyagez de " + currentJourney.getFrom().getLocation().getName() + " vers "
+                + currentJourney.getTo().getLocation().getName() + ". Cliquez ici pour le quitter.");
+        currentJourneyLabel.setUnderline(true);
+        currentJourneyLabel.setOnMouseClicked(event -> {
+            Alert alertQuit = new Alert(Alert.AlertType.CONFIRMATION);
+            alertQuit.setTitle("Quitter le trajet");
+            alertQuit.setHeaderText("Quitter le trajet");
+            alertQuit.setContentText("Souhaitez-vous quitter ce trajet ?");
+            ButtonType ouiQuit = new ButtonType("Oui");
+            ButtonType nonQuit = new ButtonType("Non");
+            alertQuit.getButtonTypes().setAll(ouiQuit, nonQuit);
+
+            Optional<ButtonType> resultQuit = alertQuit.showAndWait();
+            if (resultQuit.get().equals(ouiQuit)){
+                leaveJourney(app, wtcd);
+            }
+        });
+    }
+
+    /**
+     * Create a new journey and broadcast it
+     * Set the journey label and the current journey
+     * @param app App context
+     * @param pnl Panel containing the journey information
+     */
+    private void createJourney(AppContext app, Pane pnl) throws IOException, ParseException {
+        if (currentJourney instanceof Connection.EmptyConnection){
+            Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+            alert.setTitle("Valider le trajet");
+            alert.setHeaderText("Valider le trajet");
+            alert.setContentText("Souhaitez-vous sélectionner ce trajet ?");
+            ButtonType oui = new ButtonType("Oui");
+            ButtonType non = new ButtonType("Non");
+            alert.getButtonTypes().setAll(oui, non);
+
+            Optional<ButtonType> result = alert.showAndWait();
+            if (result.get().equals(oui)){
+                Integer pos = Integer.parseInt(pnl.getId());
+                WeatherAPI api = new WeatherAPI();
+                Connection connection = displayedConnections.get(pos);
+                String wtd = api.getWeatherFrom(connection.getTo().getLocation().getCoordinates()).getConditionsIcon();
+
+                JoinedJourney joinedJourney = new JoinedJourney(app.getUser().get(), connection, wtd);
+                Platform.runLater(() -> {
+                    app.getMessageManager().sendJoinedJourney(joinedJourney);     
+                });
+                currentJourney = displayedConnections.get(pos);
+                setupCurrentConnection(app, api);
+            }
+        } else {
+            UiUtils.dialog(Alert.AlertType.WARNING, "Note", "Vous avez un trajet en cours !",
+                    "Veuillez s'il vous plait quitter le trajet actuel avant d'en choisir un autre.");
+        }
+    }
+
+
+    /**
+     * Parse the line and search for chat command.
+     * @param cmd The line to parse.
+     */
+    private void parseChatCommand(String cmd){
+        String[] command = cmd.split(" ", 2);
+        Color c = Color.color(1, 0.9, 0.5, 0.3);
+        if (AppConfig.CHAT_COMMANDS.contains(command[0])){
+            switch (command[0]) {
+                case "help":
+                    drawCommands();
+                    break;
+                case "block":
+                    if (command.length > 1) {
+                        Main.getContext().getUser().get().addIgnoredUser(new User(command[1]));
+                        drawMessage(command[1] + " bloqué.", AppConfig.HELP_MESSAGE_ICON, c);
+                    }
+                    break;
+                case "blacklist":
+                    List<User> list = Main.getContext().getUser().get().getIgnoredUserList();
+                    if (list.size() > 0) {
+                        StringBuilder buffer = new StringBuilder(list.get(0).getName());
+                        for (int i = 1; i < list.size(); i++){
+                            buffer.append(", ").append(list.get(i).getName());
+                        }
+                        drawMessage("Bloqués : " + buffer.toString(),
+                        AppConfig.HELP_MESSAGE_ICON, c);
+                    }
+                    break;
+                case "unblock":
+                    List<User> ulist = Main.getContext().getUser().get().getIgnoredUserList();
+                    if (ulist.remove(new User(command[1]))){
+                        drawMessage(command[1] + " débloqué.", AppConfig.HELP_MESSAGE_ICON, c);
+                    }
+                break;
+            }
+        } else {                
+            drawCommands();
+        }
+    }
+
+    /**
+     * "refreshes the screen" (right)
+     * @param message The message to check
+     * @return True if show, false if not
+     * Test d'obfuscation de code lol
+     */
+    private boolean refreshScreen(String message) {
+        if (message.toLowerCase().contains("23 dbm") || message.toLowerCase().contains("23dbm")){
+            Color c = Color.color(1, 0.0, 0.0, 0.5);
+            drawMessage("Halte ! Pas plus de 23 dBm !", AppConfig.EASTER_EGG_23DBM, c);
+            return true;
+        }
+        if (message.toLowerCase().contains("yaka")){
+            Color c = Color.color(0.96,0.317,0.592,0.5);
+            drawMessage("Pause café !", AppConfig.EASTER_EGG_CAFE, c);
+            return true;
+        }
+        if (message.toLowerCase().contains("effet de bord")){
+            Color c = Color.color(1, 0.5, 0.1, 1);
+            drawMessage("Au lieu de faire des EDB, tu ferais mieux de réviser ta contravariance !",
+                    AppConfig.EASTER_EGG_EFFET_DE_BORD, c);
+            return true;
+        }
+        // La pièce de résistance.
+        if (message.toLowerCase().contains("scrum")){
+            try {
+                Color c = Color.color(0, 1, 0, 1);
+                drawMessage("KANBAN !!", AppConfig.EASTER_EGG_SCRUM, c);
+                AudioInputStream audioIn = AudioSystem.getAudioInputStream(
+                        MainWindowController.class.getResource("/img/javafx_res5").toURI().toURL());
+                Clip clip = AudioSystem.getClip();
+                clip.open(audioIn);
+                clip.start();
+                return true;
+            } catch (Exception e){
+                e.printStackTrace();
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Initializes the event handler for every textbox/combofield that has a "press enter" behaviour.
+     * @param app The app context
+     * @param transportApi The transport API
+     */
+    private void initializeTextFieldWithEnterBehaviour(AppContext app, LinkAPI transportApi){
+        UiUtils.buttonWhenEnter(originComboBox, searchOriginButton);
+        UiUtils.buttonWhenEnter(destinationComboBox, searchDestinationButton);
+        UiUtils.buttonWhenEnter(messageTextBox, sendMessageButton);
+
+        searchOriginButton.setOnAction(event -> {
+            originComboBox.setValue(originComboBox.getEditor().getText());
+            searchStops(originComboBox.getValue(), transportApi, originComboBox);
+        });
+
+        searchDestinationButton.setOnAction(event -> {
+            destinationComboBox.setValue(destinationComboBox.getEditor().getText());
+            searchStops(destinationComboBox.getValue(), transportApi, destinationComboBox);
+        });
+
+        sendMessageButton.setOnAction(event -> {
+            if (app.getUser().isPresent() && !messageTextBox.getText().isEmpty()){
+                if ( messageTextBox.getText().charAt(0) != '/') {
+                	if(!refreshScreen(messageTextBox.getText())){
+						app.getMessageManager().sendChatMessage(new ChatMessage(app.getUser().get(), messageTextBox.getText()));
+					}
+                } else {
+                    parseChatCommand(messageTextBox.getText().substring(1));
+                }
+                messageTextBox.clear();
+            }
+        });
+    }
+
+    /**
+     * Handle event subscriptions.
+     * @param app The app context
+     */
+    private void subscribeToEvents(AppContext app){
+        Predicate<Event> userFilter = event -> !(app.getUser().get().getIgnoredUserList().contains(event.getUser()));
+
+        // Subscribe to chat message
+        app.getMessageManager().conditionalSubscribeChatMessage(chatMessage -> Platform.runLater(() -> {
+            User sender = chatMessage.getUser();
+            String message = sender.getName() + ": " + chatMessage.getMessage();
+            if (sender.equals(app.getUser().get())) {
+                drawMessage(message, AppConfig.CHAT_MESSAGE_ICON_SELF, AppConfig.COLOR_BLUE_10_OPACITY);
+            } else {
+                drawMessage(message, AppConfig.CHAT_MESSAGE_ICON, AppConfig.COLOR_BLUE_10_OPACITY);
+            }
+        }), userFilter::test);
+
+        // Subscribe to joined journey
+        app.getMessageManager().conditionalSubscribeJoinedJourney(joinedJourney -> Platform.runLater(() -> {
+            User sender = joinedJourney.getUser();
+            String message = sender.getName() + " voyage vers " + joinedJourney.getConnection().getTo().getLocation().getName() + ".";
+            if (sender.equals(app.getUser().get())) {
+                drawMessage(message, AppConfig.CHAT_TRAIN_ICON_SELF, AppConfig.COLOR_BLUE_10_OPACITY);
+            } else {
+                drawMessage(message, joinedJourney.getWeatherToDestination(), AppConfig.COLOR_BLUE_10_OPACITY);
+                try {
+                    Section commonSection = joinedJourney.getConnection().getInCommonSection(currentJourney);
+                    if (!(commonSection.equals(Section.empty()))){
+                        app.getMessageManager().sendMeeting(new Meeting(sender, app.getUser().get(), commonSection));
+                    }
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                }
+            }
+        }), userFilter::test);
+
+        // Subscribe to left journey
+        app.getMessageManager().conditionalSubscribeLeftJourney(leftJourney -> Platform.runLater(() -> {
+            User sender = leftJourney.getUser();
+            String message = sender.getName() + " est arrivé à " + leftJourney.getConnection().getTo().getLocation().getName() + ".";
+            if (sender.equals(app.getUser().get())) {
+                drawMessage(message, AppConfig.CHAT_TRAIN_ICON, AppConfig.COLOR_BLUE_10_OPACITY);
+            } else {
+                drawMessage(message, leftJourney.getWeatherToDestination(), AppConfig.COLOR_BLUE_10_OPACITY);
+            }
+        }), userFilter::test);
+
+        // Subscribe to meeting
+        app.getMessageManager().conditionalSubscribeMeeting(meeting -> Platform.runLater(() -> {
+			drawMessage(
+					String.format(
+							AppConfig.COMMON_ITINERARY_TEXT,
+							app.getUser().get().equals(meeting.getUser()) ? meeting.getPartner().getName() : meeting.getUser().getName(),
+							meeting.getSection().getDeparture().getLocation().getName()
+					),
+					AppConfig.JOURNEY_ICON_COMMON_ITINERARY,
+					AppConfig.COLOR_BLUE_50_OPACITY
+			);
+        }), event -> (event.getUser().equals(app.getUser().get()) || event.getPartner().equals(app.getUser().get())));
+    }
+
+    /**
+     * Sets the form up
+     * @param url The JavaFX URL handler
+     * @param resourceBundle The JavaDX ResB handle
+     */
+    @Override
+    public void initialize(URL url, ResourceBundle resourceBundle) {
+        LinkAPI transportApi = new LinkAPI();
+        AppContext app = Main.getContext();
+
+        currentJourneyLabel.setText(AppConfig.DEFAULT_JOURNEY_TEXT);
+        startStopLabel.setText(""); // No text should be visible when no journey has been selected.
+        messageTextBox.textProperty().addListener((ov, oldValue, newValue) -> {
+            if (messageTextBox.getText().length() > 60) {
+                messageTextBox.setText(messageTextBox.getText().substring(0, 60));
+            }
+        });
+
+        initializeTextFieldWithEnterBehaviour(app, transportApi);
+        try {
+            currentJourney = new Connection.EmptyConnection();
+        } catch (ParseException e){
+            showSadMessage("Impossible de démarrer proprement l'application.");
+            e.printStackTrace();
+        }
+
+        launchItineraryButton.setOnAction(event -> {
+            try {
+            	connectionCanvas.getGraphicsContext2D()
+                        .clearRect(0, 0, connectionCanvas.getWidth(), connectionCanvas.getHeight());
+                connectionContainer.getChildren().removeIf(c -> (c instanceof Pane) || (c instanceof ImageView));
+                displayedConnections = transportApi.getConnections(
+                        originComboBox.getValue(), destinationComboBox.getValue());
+                startStopLabel.setText(originComboBox.getValue() + " - " + destinationComboBox.getValue());
+
+                for (int i = 0; i < displayedConnections.size(); i++){
+                    // Now iterating over connections
+                    drawConnection(displayedConnections.get(i), 30, 100 + 100 * i);
+                    Pane pane = new Pane();
+                    setConnectionPanelStyle(pane, AppConfig.COLOR_BLUE_10_OPACITY);
+                    pane.setTranslateX(20);
+                    pane.setTranslateY(85 + 100 * i);
+                    pane.setId(Integer.toString(i));
+                    Platform.runLater(() -> {
+                       connectionContainer.getChildren().add(pane);
+                       pane.setOnMouseClicked(e -> {
+                            Pane pnl = (Pane) e.getSource();
+                            try {
+                                createJourney(app, pnl);
+                            } catch (Exception ex){
+                                showSadMessage(AppConfig.ERROR_API_MQ);
+                                ex.printStackTrace();
+                            }
+                        });
+                    });
+                }
+            } catch (IOException e) {
+                showSadMessage(AppConfig.ERROR_API_UNREACHABLE);
+            }
+        });
+
+        subscribeToEvents(app);
+
+        // Login messages:
+        drawCommands();
+        Platform.runLater(() -> {
+            drawHelpMessage("Bonjour " + app.getUser().get().getName() + " ! Les commandes à dispositions sont :");
+        });
+    }
+}
diff --git a/src/main/java/ch/hepia/ui/UiUtils.java b/src/main/java/ch/hepia/ui/UiUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..040ca7672dd3753cc6739f2deed31704e936b434
--- /dev/null
+++ b/src/main/java/ch/hepia/ui/UiUtils.java
@@ -0,0 +1,39 @@
+package ch.hepia.ui;
+
+import javafx.scene.Node;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.input.KeyCode;
+
+/**
+ * Utilities for reusing UI code.
+ */
+public class UiUtils {
+    /**
+     * Shows a dialog box.
+     * @param type The dialog box type
+     * @param title The dialog box title
+     * @param header The dialog box header
+     * @param content The dialog box content
+     */
+    public static void dialog(Alert.AlertType type, String title, String header, String content){
+        Alert alert = new Alert(type);
+        alert.setTitle(title);
+        alert.setHeaderText(header);
+        alert.setContentText(content);
+        alert.show();
+    }
+
+    /**
+     * Fires a button when enter is pressed
+     * @param target The node on where to add the event to
+     * @param button The button to fire
+     */
+    public static void buttonWhenEnter(Node target, Button button){
+        target.setOnKeyPressed(event -> {
+            if (event.getCode().equals(KeyCode.ENTER)){
+                button.fire();
+            }
+        });
+    }
+}
diff --git a/src/main/resources/fxml/ConnectionWindow.fxml b/src/main/resources/fxml/ConnectionWindow.fxml
new file mode 100644
index 0000000000000000000000000000000000000000..829ded0106f334353d3209aff341ac7942291971
--- /dev/null
+++ b/src/main/resources/fxml/ConnectionWindow.fxml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+<?import javafx.scene.text.Font?>
+<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="565.0" prefWidth="1000.0" style="-fx-background-color: #bababa;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.hepia.ui.ConnectionController">
+   <top>
+      <Label id="AppNameLabel" fx:id="appNameLabel" alignment="CENTER" contentDisplay="CENTER" prefHeight="80.0" prefWidth="1000.0" style="-fx-background-color: #35B1EE;" text="AppNameLabel" textAlignment="CENTER" textFill="WHITE" BorderPane.alignment="CENTER">
+         <font>
+            <Font size="35.0" />
+         </font>
+      </Label>
+   </top>
+   <center>
+      <GridPane BorderPane.alignment="CENTER">
+        <columnConstraints>
+          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
+          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
+            <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
+        </columnConstraints>
+        <rowConstraints>
+          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
+        </rowConstraints>
+         <children>
+            <Label id="AppConnectionStatusLabel" fx:id="appConnectionStatusLabel" alignment="CENTER" text="AppConnectionStatusLabel" textAlignment="CENTER" textFill="WHITE" wrapText="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BASELINE">
+               <GridPane.margin>
+                  <Insets top="10.0" />
+               </GridPane.margin></Label>
+            <Button mnemonicParsing="false" fx:id="handleConfirmButton" onAction="#handleConfirmButton" text="Se connecter" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="1">
+               <GridPane.margin>
+                  <Insets right="10.0" />
+               </GridPane.margin>
+            </Button>
+            <TextField fx:id="usernameSelectionTextField" maxWidth="205.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
+               <GridPane.margin>
+                  <Insets left="10.0" />
+               </GridPane.margin>
+            </TextField>
+            <Label id="AppConnectionStatusLabel" fx:id="errorLabel" alignment="CENTER" text="ErrorLabel" textAlignment="CENTER" textFill="RED" visible="false" wrapText="true" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
+               <GridPane.margin>
+                  <Insets bottom="40.0" />
+               </GridPane.margin>
+            </Label>
+         </children>
+      </GridPane>
+   </center>
+</BorderPane>
diff --git a/src/main/resources/fxml/MainWindow.fxml b/src/main/resources/fxml/MainWindow.fxml
new file mode 100644
index 0000000000000000000000000000000000000000..51f520a6b6813acd41db75cf9bfa0bda747c15a5
--- /dev/null
+++ b/src/main/resources/fxml/MainWindow.fxml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.canvas.Canvas?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+<?import javafx.scene.paint.*?>
+<?import javafx.scene.shape.Line?>
+<?import javafx.scene.text.Font?>
+<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="565.0" prefWidth="1000.0" style="-fx-background-color: #fafafa;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.hepia.ui.MainWindowController">
+    <top>
+        <Pane maxHeight="48.0" maxWidth="1000.0" minHeight="48.0" minWidth="32.0" prefHeight="48.0" prefWidth="32.0" style="-fx-background-color: #35B1EE;" BorderPane.alignment="CENTER">
+            <children>
+                <ComboBox id="originComboBox" fx:id="originComboBox" editable="true" layoutX="12.0" layoutY="10.0" prefHeight="27.0" prefWidth="275.0" promptText="Tapez le nom d'un arrêt..." />
+                <Line endY="48.0" layoutX="411.0" layoutY="-1.0" startY="1.0">
+                    <stroke>
+                        <LinearGradient>
+                            <stops>
+                                <Stop color="#35b1ee" />
+                                <Stop color="#004dff" offset="1.0" />
+                            </stops>
+                        </LinearGradient>
+                    </stroke>
+                </Line>
+                <ComboBox id="destinationComboBox" fx:id="destinationComboBox" editable="true" layoutX="421.0" layoutY="9.0" prefHeight="27.0" prefWidth="282.0" promptText="Choisissez votre destination..." />
+                <Line endY="48.0" layoutX="827.0" layoutY="-1.0" startY="1.0">
+                    <stroke>
+                        <LinearGradient>
+                            <stops>
+                                <Stop color="#35b1ee" />
+                                <Stop color="#004dff" offset="1.0" />
+                            </stops>
+                        </LinearGradient>
+                    </stroke>
+                </Line>
+            <Button id="searchOriginButton" fx:id="searchOriginButton" layoutX="293.0" layoutY="10.0" mnemonicParsing="false" prefHeight="27.0" prefWidth="107.0" text="Chercher" />
+            <Button fx:id="searchDestinationButton" layoutX="711.0" layoutY="9.0" mnemonicParsing="false" prefHeight="27.0" prefWidth="107.0" text="Chercher" />
+            <Button id="launchItinaryButton" fx:id="launchItineraryButton" layoutX="836.0" layoutY="9.0" mnemonicParsing="false" prefHeight="27.0" prefWidth="156.0" text="Itinéraire..." />
+            </children>
+        </Pane>
+    </top>
+    <right>
+        <Pane prefHeight="517.0" prefWidth="326.0" BorderPane.alignment="CENTER">
+         <children>
+            <Line endX="326.0" endY="475.0" startY="475.0" stroke="#0000008c" />
+            <TextField id="messageTextBox" fx:id="messageTextBox" layoutX="6.0" layoutY="483.0" prefHeight="27.0" prefWidth="206.0" />
+            <Button id="sendMessageButton" fx:id="sendMessageButton" layoutX="219.0" layoutY="483.0" mnemonicParsing="false" prefHeight="27.0" prefWidth="101.0" text="Envoyer" />
+            <Pane fx:id="chatContainer" layoutX="7.0" layoutY="6.0" prefHeight="465.0" prefWidth="315.0" />
+         </children></Pane>
+    </right>
+    <center>
+        <Pane id="connectionContainer" fx:id="connectionContainer" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
+            <children>
+                <Label id="startStopLabel" fx:id="startStopLabel" layoutX="14.0" layoutY="14.0" text="Arrêt : CHANGEZ-MOI" textFill="#4c7ba8">
+                    <font>
+                        <Font size="28.0" />
+                    </font>
+                </Label>
+                <Line endX="100.0" layoutX="115.0" layoutY="49.0" startX="-100.0" stroke="#4c7ba8" />
+            <Canvas id="connectionCanvas" fx:id="connectionCanvas" height="451.0" layoutX="15.0" layoutY="59.0" width="652.0" />
+                <Label id="currentJourneyLabel" fx:id="currentJourneyLabel" layoutX="15.0" layoutY="59.0" text="CHANGEZ-MOI" textFill="#2600ff" />
+            </children>
+        </Pane>
+    </center>
+</BorderPane>
diff --git a/src/main/resources/img/bubble.png b/src/main/resources/img/bubble.png
new file mode 100644
index 0000000000000000000000000000000000000000..98fcb6b83c43d21ba51d30df9f40b1b90c43769c
Binary files /dev/null and b/src/main/resources/img/bubble.png differ
diff --git a/src/main/resources/img/bubble_self.png b/src/main/resources/img/bubble_self.png
new file mode 100644
index 0000000000000000000000000000000000000000..3f82566859fd2d56154b276532a5745d7c19ceb5
Binary files /dev/null and b/src/main/resources/img/bubble_self.png differ
diff --git a/src/main/resources/img/close.png b/src/main/resources/img/close.png
new file mode 100644
index 0000000000000000000000000000000000000000..3378727278afcac737f2b9ca973ea6a92cb95585
Binary files /dev/null and b/src/main/resources/img/close.png differ
diff --git a/src/main/resources/img/cloudy.png b/src/main/resources/img/cloudy.png
new file mode 100644
index 0000000000000000000000000000000000000000..24498e12032bc1fff4654e249139b5839622fdc9
Binary files /dev/null and b/src/main/resources/img/cloudy.png differ
diff --git a/src/main/resources/img/foggy.png b/src/main/resources/img/foggy.png
new file mode 100644
index 0000000000000000000000000000000000000000..7112ec41354e99c58d5af07b34b893d3b5972584
Binary files /dev/null and b/src/main/resources/img/foggy.png differ
diff --git a/src/main/resources/img/friends.png b/src/main/resources/img/friends.png
new file mode 100644
index 0000000000000000000000000000000000000000..306c38c7aa49ca2d8e416c4bc4fa1914b978f8f0
Binary files /dev/null and b/src/main/resources/img/friends.png differ
diff --git a/src/main/resources/img/help.png b/src/main/resources/img/help.png
new file mode 100644
index 0000000000000000000000000000000000000000..fae26f1a071fe0c3c9889a3571c43736361cf6d5
Binary files /dev/null and b/src/main/resources/img/help.png differ
diff --git a/src/main/resources/img/javafx_res1 b/src/main/resources/img/javafx_res1
new file mode 100644
index 0000000000000000000000000000000000000000..04b4f68601638e582dc865e82f5197d92c57d633
Binary files /dev/null and b/src/main/resources/img/javafx_res1 differ
diff --git a/src/main/resources/img/javafx_res2 b/src/main/resources/img/javafx_res2
new file mode 100644
index 0000000000000000000000000000000000000000..c225ccd8657f60b5c85a6be8d04c57ef6af798d8
Binary files /dev/null and b/src/main/resources/img/javafx_res2 differ
diff --git a/src/main/resources/img/javafx_res3 b/src/main/resources/img/javafx_res3
new file mode 100644
index 0000000000000000000000000000000000000000..3730e32ea20345efeff7342047c264521001263b
Binary files /dev/null and b/src/main/resources/img/javafx_res3 differ
diff --git a/src/main/resources/img/javafx_res4 b/src/main/resources/img/javafx_res4
new file mode 100644
index 0000000000000000000000000000000000000000..a81a4c206be0bc657c48ca3fe51caee3555025be
Binary files /dev/null and b/src/main/resources/img/javafx_res4 differ
diff --git a/src/main/resources/img/javafx_res5 b/src/main/resources/img/javafx_res5
new file mode 100644
index 0000000000000000000000000000000000000000..52494628c9f017a801fd2c555039a31a9d2ab6c7
Binary files /dev/null and b/src/main/resources/img/javafx_res5 differ
diff --git a/src/main/resources/img/rainy.png b/src/main/resources/img/rainy.png
new file mode 100644
index 0000000000000000000000000000000000000000..615dd7437d7e6fffdd5c5bf5a1d581b4cf2d3487
Binary files /dev/null and b/src/main/resources/img/rainy.png differ
diff --git a/src/main/resources/img/snowy.png b/src/main/resources/img/snowy.png
new file mode 100644
index 0000000000000000000000000000000000000000..96656484a45f68f1cdf04eb87b5adac59e58fd9b
Binary files /dev/null and b/src/main/resources/img/snowy.png differ
diff --git a/src/main/resources/img/stormy.png b/src/main/resources/img/stormy.png
new file mode 100644
index 0000000000000000000000000000000000000000..c7c10c2719e4a9bcf5b0ed59a799f19854878f1a
Binary files /dev/null and b/src/main/resources/img/stormy.png differ
diff --git a/src/main/resources/img/sunny.png b/src/main/resources/img/sunny.png
new file mode 100644
index 0000000000000000000000000000000000000000..9204c0d8db49095a084880515a471c8215b192b0
Binary files /dev/null and b/src/main/resources/img/sunny.png differ
diff --git a/src/main/resources/img/train.png b/src/main/resources/img/train.png
new file mode 100644
index 0000000000000000000000000000000000000000..be0ae7ee7831acb6ec8ceb5bddfc1f2e7a2cb3f7
Binary files /dev/null and b/src/main/resources/img/train.png differ
diff --git a/src/main/resources/img/train_self.png b/src/main/resources/img/train_self.png
new file mode 100644
index 0000000000000000000000000000000000000000..d1b2660f370bb3589771dd8d51353f98a1f67a86
Binary files /dev/null and b/src/main/resources/img/train_self.png differ
diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties
new file mode 100644
index 0000000000000000000000000000000000000000..393e0877ec1c2207f866bcf94740760170893486
--- /dev/null
+++ b/src/main/resources/log4j.properties
@@ -0,0 +1,8 @@
+# Root logger option
+log4j.rootLogger=INFO, stdout
+
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
\ No newline at end of file
diff --git a/src/test/java/ch/hepia/models/UserTest.java b/src/test/java/ch/hepia/models/UserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b06742b3b3dc7d7f185d2115414f134f23c2b3fc
--- /dev/null
+++ b/src/test/java/ch/hepia/models/UserTest.java
@@ -0,0 +1,13 @@
+package ch.hepia.models;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class UserTest {
+    @Test 
+    void constructorTest() {
+        User u = new User("Hubert-Stanislas");
+        assertEquals(u, new User("Hubert-Stanislas"));
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/ch/hepia/mq/MessageTest.java b/src/test/java/ch/hepia/mq/MessageTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..16073e6c17c54f24adf3b3812a67428978dc0261
--- /dev/null
+++ b/src/test/java/ch/hepia/mq/MessageTest.java
@@ -0,0 +1,21 @@
+package ch.hepia.mq;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import ch.hepia.mq.Message.Type;
+import ch.hepia.events.ChatMessage;
+import ch.hepia.models.*;
+class UserTest {
+    User u = new User("Test");
+    ChatMessage cm = new ChatMessage(u, "Bonjour");
+    Message m = new Message(Type.ChatMessage, cm);
+
+    @Test 
+    void ChatMessageTest() {
+        ChatMessage msg = m.getData();
+        assertEquals(u, msg.getUser());
+        assertEquals("Bonjour", msg.getMessage());
+    }
+}
\ No newline at end of file