diff --git a/README.md b/README.md
index 676a872e4698e726ceae6fdb6b8224527a2e9405..c4e0639291cafbeb71bd66b9f6ea103df2e0b4e5 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,17 @@ This will delete existing images.
 ### To stop the application (all microservices)
     $ podman-compose down
 
+## Observations
+
+### Prometheus
+
+The prometheus collects the metrics from all 4 microservices. The UI runs at the http://localhost:9090.
+
+### Grafana
+
+Grafana runs at http://localhost:3000. To Log in, use the username `admin` and the password `admin`.
+There is already one minimalistic dashboard imported. However, feel free to experiment and visualize another attributes.
+
 ## Usage
 ### Used ports
 + Authorization - port 8083
diff --git a/authorization/pom.xml b/authorization/pom.xml
index 3bd224e0f1fcb72f33309323e745bdff5fdf4886..27ba936bdd041e57c72ca922662106cdfab1334d 100644
--- a/authorization/pom.xml
+++ b/authorization/pom.xml
@@ -21,6 +21,18 @@
             <scope>runtime</scope>
         </dependency>
 
+        <!-- Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <!-- Prometheus -->
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>
diff --git a/authorization/src/main/resources/application.yml b/authorization/src/main/resources/application.yml
index 86820bd5b40c31e85ed3ec1d44cc4cba01a5cfd4..315986cde4c4506667763774d0d1555d23be738a 100644
--- a/authorization/src/main/resources/application.yml
+++ b/authorization/src/main/resources/application.yml
@@ -7,4 +7,24 @@ spring:
   jpa:
     database-platform: org.hibernate.dialect.H2Dialect
 server:
-  port: 8083
\ No newline at end of file
+  port: 8083
+management:
+  endpoints:
+    web:
+      exposure:
+        include: 'info,health,metrics,prometheus'
+  info:
+    env:
+      enabled: true
+  endpoint:
+    health:
+      show-details: always
+      show-components: always
+      probes:
+        enabled: true
+info:
+  app:
+    encoding: 'UTF-8'
+    java:
+      source: '17'
+      target: '17'
diff --git a/compose.yaml b/compose.yaml
index ac0b2752b1b9eaf650eb4758ae364732253dd54b..5cf3f15a109c22369e52128ea03ef1ae1895cb8a 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -1,4 +1,9 @@
 version: "3"
+
+networks:
+  grafana-prometheus:
+    driver: bridge
+
 services:
   core:
     build: "./core"
@@ -18,4 +23,24 @@ services:
   weather:
     build: "./weather"
     ports:
-      - "8088:8088"
\ No newline at end of file
+      - "8088:8088"
+
+  prometheus:
+    image: prom/prometheus:v2.43.0
+    volumes:
+      - ./prometheus.yml:/etc/prometheus/prometheus.yml
+    command: --config.file=/etc/prometheus/prometheus.yml
+    networks:
+      - grafana-prometheus
+    ports:
+      - '9090:9090'
+
+  grafana:
+    image: grafana/grafana:9.1.7
+    volumes:
+      - ./datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml
+      - ./dashboards:/etc/grafana/provisioning/dashboards
+    networks:
+      - grafana-prometheus
+    ports:
+      - '3000:3000'
diff --git a/core/pom.xml b/core/pom.xml
index 362c646dcfbe14deef9d7656d0b78a2f8d55b0df..d1513243a72cfa6d0ef1c552b121056003ba2d4b 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -21,6 +21,18 @@
             <scope>runtime</scope>
         </dependency>
 
+        <!-- Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <!-- Prometheus -->
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>
diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml
index 1af7b28b0c5756033a40a681f58474c86d43602a..9824e82b44b8bea2e2041ad0c98de43c5b71b6fd 100644
--- a/core/src/main/resources/application.yml
+++ b/core/src/main/resources/application.yml
@@ -12,3 +12,23 @@ spring:
     database-platform: org.hibernate.dialect.H2Dialect
     hibernate:
       ddl-auto: update
+management:
+  endpoints:
+    web:
+      exposure:
+        include: 'info,health,metrics,prometheus'
+  info:
+    env:
+      enabled: true
+  endpoint:
+    health:
+      show-details: always
+      show-components: always
+      probes:
+        enabled: true
+info:
+  app:
+    encoding: 'UTF-8'
+    java:
+      source: '17'
+      target: '17'
diff --git a/dashboards/dashboard.yaml b/dashboards/dashboard.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b5df708530ea53752bf1bf1b926acc851d3d83b9
--- /dev/null
+++ b/dashboards/dashboard.yaml
@@ -0,0 +1,7 @@
+apiVersion: 1
+
+providers:
+  - name: 'Default'
+    folder: 'Services'
+    options:
+      path: /etc/grafana/provisioning/dashboards
diff --git a/dashboards/grafana.json b/dashboards/grafana.json
new file mode 100644
index 0000000000000000000000000000000000000000..c035a36562fb1b1c2185b4bab5f66d7f6a53c35b
--- /dev/null
+++ b/dashboards/grafana.json
@@ -0,0 +1,109 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": {
+          "type": "grafana",
+          "uid": "-- Grafana --"
+        },
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "target": {
+          "limit": 100,
+          "matchAny": false,
+          "tags": [],
+          "type": "dashboard"
+        },
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "fiscalYearStartMonth": 0,
+  "graphTooltip": 0,
+  "id": 1,
+  "links": [],
+  "liveNow": false,
+  "panels": [
+    {
+      "datasource": {},
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 12,
+        "x": 0,
+        "y": 9
+      },
+      "id": 8,
+      "options": {
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true
+      },
+      "pluginVersion": "9.1.7",
+      "targets": [
+        {
+          "datasource": {
+            "type": "prometheus",
+            "uid": "p5SIZnEVk"
+          },
+          "editorMode": "code",
+          "expr": "http_server_requests_seconds_count",
+          "legendFormat": "__auto",
+          "range": true,
+          "refId": "A"
+        }
+      ],
+      "title": "HTTP Server Requests counter",
+      "type": "gauge"
+    }
+  ],
+  "refresh": "5s",
+  "schemaVersion": 37,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-6h",
+    "to": "now"
+  },
+  "timepicker": {},
+  "timezone": "",
+  "title": "Airport-Manager",
+  "uid": "YDzAhnEVz",
+  "version": 3,
+  "weekStart": ""
+}
diff --git a/datasource.yml b/datasource.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d96153aa026474ed8aa7882e1586d0334623d4e1
--- /dev/null
+++ b/datasource.yml
@@ -0,0 +1,7 @@
+apiVersion: 1
+
+datasources:
+  - name: Prometheus
+    type: prometheus
+    access: proxy
+    url: http://prometheus:9090
diff --git a/prometheus.yml b/prometheus.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fadedee9716b5b5de22dbd23a934a34becb77993
--- /dev/null
+++ b/prometheus.yml
@@ -0,0 +1,16 @@
+global:
+  scrape_interval: 1s
+  external_labels:
+    monitor: 'my-monitor'
+
+scrape_configs:
+  - job_name: 'prometheus'
+    static_configs:
+      - targets: ['localhost:9090']
+  - job_name: 'microservices'
+    metrics_path: /actuator/prometheus
+    static_configs:
+      - targets: ['localhost:8080']
+      - targets: ['localhost:8083']
+      - targets: ['localhost:8085']
+      - targets: ['localhost:8088']
diff --git a/report/pom.xml b/report/pom.xml
index 80b8165a84a93096adacc4489b59d6dac5cbb72c..dc247ce2039a49158919f914a51d55810fa19efc 100644
--- a/report/pom.xml
+++ b/report/pom.xml
@@ -21,6 +21,18 @@
             <scope>runtime</scope>
         </dependency>
 
+        <!-- Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <!-- Prometheus -->
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>
diff --git a/report/src/main/resources/application.yaml b/report/src/main/resources/application.yaml
index 0abcd741f27e93ab0d76b682b58e413b874d2c92..26f8a4475b57efeec939c295d78edc73022d77a5 100644
--- a/report/src/main/resources/application.yaml
+++ b/report/src/main/resources/application.yaml
@@ -10,3 +10,23 @@ spring:
 # for now to not collide with another microservices
 server:
   port: 8085
+management:
+  endpoints:
+    web:
+      exposure:
+        include: 'info,health,metrics,prometheus'
+  info:
+    env:
+      enabled: true
+  endpoint:
+    health:
+      show-details: always
+      show-components: always
+      probes:
+        enabled: true
+info:
+  app:
+    encoding: 'UTF-8'
+    java:
+      source: '17'
+      target: '17'
diff --git a/weather/openapi.yaml b/weather/openapi.yaml
index 458c08ad7f59e878060030fc07240ce579b479f9..d36f8a8feaae9ebb626d49993ab295a3e0cb680e 100644
--- a/weather/openapi.yaml
+++ b/weather/openapi.yaml
@@ -72,7 +72,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/FlightCreationAdvice'
+                $ref: '#/components/schemas/FlightCreationAdviceDto'
         "400":
           description: Input data not correct
           content:
@@ -107,7 +107,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/WeatherForecast'
+                $ref: '#/components/schemas/WeatherForecastDto'
         "400":
           description: Input data not correct
           content:
@@ -116,34 +116,24 @@ paths:
                 $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
 components:
   schemas:
-    WeatherReason:
-      title: Weather reason
+    FlightCreationAdviceDto:
+      title: FlightCreationAdviceDto
       description: |
-        Enumeration of weather reasons, e.g. whether it is ok
-        to fly or the reason why not
-      type: string
-      example: ok-everything
-      enum:
-      - ok-everything
-      - nok-wind
-      - nok-temperature
-      - nok-rain
-    FlightCreationAdvice:
-      title: Flight creation advice
-      description: |
-        Response of '/api/isSafeToCreateFlight' call.
+        Flight Creation Advice DTO
       type: object
       properties:
         result:
           type: boolean
           description: Result whether it's safe to create such a flight
         reason:
-          $ref: '#/components/schemas/WeatherReason'
-    WeatherForecast:
-      title: Weather forecast
+          $ref: '#/components/schemas/WeatherReasonDto'
+      required:
+        - result
+        - reason
+    WeatherForecastDto:
+      title: WeatherForecastDto
       description: |
-        Information about weather forecast including basic attributes,
-        e.g. wind speed
+        Weather Forecast DTO
       type: object
       properties:
         temperature:
@@ -160,7 +150,26 @@ components:
           type: number
           format: double
           description: Sum of rain for preceding hour in millimeters
+          example: 0.3
         visibility:
           type: number
           format: double
           description: Visibility in kilometers
+          example: 12
+      required:
+        - temperature
+        - windSpeed
+        - rain
+        - visibility
+    WeatherReasonDto:
+      title: WeatherReasonDto
+      description: |
+        Weather Reason DTO
+      type: string
+      example: ok-everything
+      enum:
+        - ok-everything
+        - nok-temperature
+        - nok-wind
+        - nok-rain
+        - nok-visibility
diff --git a/weather/pom.xml b/weather/pom.xml
index 5ac0386510bd1147f866e0edc455cdf3da5190aa..ed218727319ef5e614bebbf8a1c49abba97ee07a 100644
--- a/weather/pom.xml
+++ b/weather/pom.xml
@@ -14,6 +14,18 @@
     <description>Weather forecast service for Airport Manager</description>
 
     <dependencies>
+        <dependency>
+            <groupId>cz.muni.fi.pa165</groupId>
+            <artifactId>core-client</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>5.3.1</version>
+            <scope>test</scope>
+        </dependency>
 
         <dependency>
             <groupId>com.h2database</groupId>
@@ -21,6 +33,18 @@
             <scope>runtime</scope>
         </dependency>
 
+        <!-- Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <!-- Prometheus -->
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>
@@ -67,11 +91,16 @@
             <artifactId>spring-data-commons</artifactId>
         </dependency>
 
+        <!-- Bean validator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
         <!-- for testing -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
         </dependency>
     </dependencies>
 
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/WeatherController.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/WeatherController.java
deleted file mode 100644
index 867ead5308c16f2829a8922975b082bbe4f7b328..0000000000000000000000000000000000000000
--- a/weather/src/main/java/cz/muni/fi/pa165/weather/server/WeatherController.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package cz.muni.fi.pa165.weather.server;
-
-import cz.muni.fi.pa165.weather.server.api.WeatherApiDelegate;
-import cz.muni.fi.pa165.weather.server.model.FlightCreationAdvice;
-import cz.muni.fi.pa165.weather.server.model.WeatherForecast;
-import cz.muni.fi.pa165.weather.server.model.WeatherReason;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.time.OffsetDateTime;
-
-@RestController
-public class WeatherController implements WeatherApiDelegate {
-
-    @Override
-    public ResponseEntity<WeatherForecast> getWeatherForecast(Double latitude, Double longitude) {
-        var weatherForecast = new WeatherForecast()
-                .temperature(3.14)
-                .windSpeed(15.92)
-                .rain(0.8)
-                .visibility(6.7);
-        return ResponseEntity.ok(weatherForecast);
-    }
-
-    @Override
-    public ResponseEntity<FlightCreationAdvice> isSafeToCreateFlight(Long departureAirportId, Long arrivalAirportId, OffsetDateTime departureTime, OffsetDateTime arrivalTime) {
-        var flightCreationAdvice = new FlightCreationAdvice()
-                .result(true)
-                .reason(WeatherReason.OK_EVERYTHING);
-        return ResponseEntity.ok(flightCreationAdvice);
-    }
-}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/FlightCreationAdvice.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/FlightCreationAdvice.java
new file mode 100644
index 0000000000000000000000000000000000000000..c9df61d2f13be6b0fc433b45e7efc1adfe840871
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/FlightCreationAdvice.java
@@ -0,0 +1,15 @@
+package cz.muni.fi.pa165.weather.server.data;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@RequiredArgsConstructor
+public class FlightCreationAdvice {
+
+    private boolean result;
+
+    private WeatherReason weatherReason;
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/HourlyWeatherForecast.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/HourlyWeatherForecast.java
new file mode 100644
index 0000000000000000000000000000000000000000..912393b4521ca89a3d4134725db30ae07c62e750
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/HourlyWeatherForecast.java
@@ -0,0 +1,43 @@
+package cz.muni.fi.pa165.weather.server.data;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Entity representation of the data from our Weather API we use,
+ * see: <a href="https://open-meteo.com/">open-meteo</a>.
+ */
+@Getter
+@Setter
+@RequiredArgsConstructor
+@ToString
+public class HourlyWeatherForecast {
+
+    private double latitude;
+
+    private double longitude;
+
+    @JsonProperty("generationtime_ms")
+    private double generationTimeMs;
+
+    @JsonProperty("utc_offset_seconds")
+    private long utcOffsetSeconds;
+
+    private String timezone;
+
+    @JsonProperty("timezone_abbreviation")
+    private String timezoneAbbreviation;
+
+    private double elevation;
+
+    @JsonProperty("hourly_units")
+    private Map<String, String> hourlyUnits;
+
+    private Map<String, List<Object>> hourly;
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/WeatherForecast.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/WeatherForecast.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8f7da6e5045e76357182852db9fe76e5db3f8d8
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/WeatherForecast.java
@@ -0,0 +1,48 @@
+package cz.muni.fi.pa165.weather.server.data;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import org.springframework.stereotype.Component;
+
+@Component
+@Getter
+@Setter
+@RequiredArgsConstructor
+public class WeatherForecast {
+
+    private double temperature;
+
+    private double windSpeed;
+
+    private double rain;
+
+    private double visibility;
+
+    private static final double MIN_SAFE_TEMPERATURE = -40.0;
+
+    private static final double MAX_SAFE_TEMPERATURE = 40.0;
+
+    private static final double MAX_SAFE_WINDSPEED = 75;
+
+    private static final double MAX_SAFE_RAIN = 3.0;
+
+    private static final double MIN_SAFE_VISIBILITY = 1000.0;
+
+    public boolean temperatureInSafeBounds() {
+        return MIN_SAFE_TEMPERATURE <= temperature &&
+                temperature <= MAX_SAFE_TEMPERATURE;
+    }
+
+    public boolean windSpeedInSafeBounds() {
+        return windSpeed <= MAX_SAFE_WINDSPEED;
+    }
+
+    public boolean rainInSafeBounds() {
+        return rain <= MAX_SAFE_RAIN;
+    }
+
+    public boolean visibilityInSafeBounds() {
+        return MIN_SAFE_VISIBILITY <= visibility;
+    }
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/WeatherReason.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/WeatherReason.java
new file mode 100644
index 0000000000000000000000000000000000000000..26564c67ed23f90d7b17fef7b970e89ffd480c12
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/data/WeatherReason.java
@@ -0,0 +1,10 @@
+package cz.muni.fi.pa165.weather.server.data;
+
+public enum WeatherReason {
+
+    OK_EVERYTHING,
+    NOK_TEMPERATURE,
+    NOK_WIND,
+    NOK_RAIN,
+    NOK_VISIBILITY
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacade.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a176f54d58f85f1aaa2307966e9cd12a4dd5aa1
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacade.java
@@ -0,0 +1,20 @@
+package cz.muni.fi.pa165.weather.server.facade;
+
+import cz.muni.fi.pa165.weather.server.model.FlightCreationAdviceDto;
+import cz.muni.fi.pa165.weather.server.model.WeatherForecastDto;
+import org.springframework.stereotype.Service;
+
+import java.time.OffsetDateTime;
+
+@Service
+public interface WeatherFacade {
+
+    WeatherForecastDto getWeatherForecast(Double latitude, Double longitude);
+
+    FlightCreationAdviceDto isSafeToCreateFlight(
+            Long departureAirportId,
+            Long arrivalAirportId,
+            OffsetDateTime departureTime,
+            OffsetDateTime arrivalTime
+    );
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacadeImpl.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c0cc770af5d2bc44a21701c42008d06c5cd9ed6
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacadeImpl.java
@@ -0,0 +1,55 @@
+package cz.muni.fi.pa165.weather.server.facade;
+
+import cz.muni.fi.pa165.weather.server.data.FlightCreationAdvice;
+import cz.muni.fi.pa165.weather.server.data.WeatherForecast;
+import cz.muni.fi.pa165.weather.server.mapper.FlightCreationAdviceMapper;
+import cz.muni.fi.pa165.weather.server.mapper.WeatherForecastMapper;
+import cz.muni.fi.pa165.weather.server.model.FlightCreationAdviceDto;
+import cz.muni.fi.pa165.weather.server.model.WeatherForecastDto;
+import cz.muni.fi.pa165.weather.server.service.WeatherService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.OffsetDateTime;
+
+@Service
+public class WeatherFacadeImpl implements WeatherFacade {
+
+    private final WeatherService weatherService;
+    private final WeatherForecastMapper weatherForecastMapper;
+    private final FlightCreationAdviceMapper flightCreationAdviceMapper;
+
+    @Autowired
+    public WeatherFacadeImpl(
+            WeatherService weatherService,
+            WeatherForecastMapper weatherForecastMapper,
+            FlightCreationAdviceMapper flightCreationAdviceMapper
+    ) {
+        this.weatherService = weatherService;
+        this.weatherForecastMapper = weatherForecastMapper;
+        this.flightCreationAdviceMapper = flightCreationAdviceMapper;
+    }
+
+    @Override
+    public WeatherForecastDto getWeatherForecast(Double latitude, Double longitude) {
+        WeatherForecast weatherForecast = weatherService.getWeatherForecast(latitude, longitude);
+        return weatherForecastMapper.toDto(weatherForecast);
+    }
+
+    @Override
+    public FlightCreationAdviceDto isSafeToCreateFlight(
+            Long departureAirportId,
+            Long arrivalAirportId,
+            OffsetDateTime departureTime,
+            OffsetDateTime arrivalTime
+    ) {
+        FlightCreationAdvice flightCreationAdvice = weatherService
+                .isSafeToCreateFlight(
+                        departureAirportId,
+                        arrivalAirportId,
+                        departureTime,
+                        arrivalTime
+                );
+        return flightCreationAdviceMapper.toDto(flightCreationAdvice);
+    }
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/FlightCreationAdviceMapper.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/FlightCreationAdviceMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac8e77d1ed7c8b2c1fe5f3fb349f0ec15fb8fa5a
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/FlightCreationAdviceMapper.java
@@ -0,0 +1,19 @@
+package cz.muni.fi.pa165.weather.server.mapper;
+
+import cz.muni.fi.pa165.weather.server.data.FlightCreationAdvice;
+import cz.muni.fi.pa165.weather.server.model.FlightCreationAdviceDto;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+@Mapper(componentModel = "spring")
+public abstract class FlightCreationAdviceMapper {
+
+    @Autowired
+    protected WeatherReasonMapper weatherReasonMapper;
+
+    @Mapping(target = "reason", expression = "java(weatherReasonMapper.toDto(flightCreationAdvice.getWeatherReason()))")
+    public abstract FlightCreationAdviceDto toDto(FlightCreationAdvice flightCreationAdvice);
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/WeatherForecastMapper.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/WeatherForecastMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..a58f996094daa4defa4ea0466284151532993ddd
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/WeatherForecastMapper.java
@@ -0,0 +1,13 @@
+package cz.muni.fi.pa165.weather.server.mapper;
+
+import cz.muni.fi.pa165.weather.server.data.WeatherForecast;
+import cz.muni.fi.pa165.weather.server.model.WeatherForecastDto;
+import org.mapstruct.Mapper;
+import org.springframework.stereotype.Component;
+
+@Component
+@Mapper(componentModel = "spring")
+public interface WeatherForecastMapper {
+
+    WeatherForecastDto toDto(WeatherForecast weatherForecast);
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/WeatherReasonMapper.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/WeatherReasonMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa28b4d0da57321c544fec9b0de6c4adf515fbc1
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/mapper/WeatherReasonMapper.java
@@ -0,0 +1,13 @@
+package cz.muni.fi.pa165.weather.server.mapper;
+
+import cz.muni.fi.pa165.weather.server.data.WeatherReason;
+import cz.muni.fi.pa165.weather.server.model.WeatherReasonDto;
+import org.mapstruct.Mapper;
+import org.springframework.stereotype.Component;
+
+@Component
+@Mapper(componentModel = "spring")
+public interface WeatherReasonMapper {
+
+    WeatherReasonDto toDto(WeatherReason weatherReason);
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/rest/WeatherController.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/rest/WeatherController.java
new file mode 100644
index 0000000000000000000000000000000000000000..636a34224ab0e684842df9b763db7a356a1cbd9e
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/rest/WeatherController.java
@@ -0,0 +1,47 @@
+package cz.muni.fi.pa165.weather.server.rest;
+
+import cz.muni.fi.pa165.weather.server.api.WeatherApiDelegate;
+import cz.muni.fi.pa165.weather.server.facade.WeatherFacade;
+import cz.muni.fi.pa165.weather.server.model.FlightCreationAdviceDto;
+import cz.muni.fi.pa165.weather.server.model.WeatherForecastDto;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.OffsetDateTime;
+
+@RestController
+public class WeatherController implements WeatherApiDelegate {
+
+    private final WeatherFacade weatherFacade;
+
+    @Autowired
+    public WeatherController(WeatherFacade weatherFacade) {
+        this.weatherFacade = weatherFacade;
+    }
+
+    @Override
+    public ResponseEntity<WeatherForecastDto> getWeatherForecast(
+            Double latitude,
+            Double longitude
+    ) {
+        return ResponseEntity.ok(weatherFacade.getWeatherForecast(latitude, longitude));
+    }
+
+    @Override
+    public ResponseEntity<FlightCreationAdviceDto> isSafeToCreateFlight(
+            Long departureAirportId,
+            Long arrivalAirportId,
+            OffsetDateTime departureTime,
+            OffsetDateTime arrivalTime
+    ) {
+        return ResponseEntity.ok(
+                weatherFacade.isSafeToCreateFlight(
+                        departureAirportId,
+                        arrivalAirportId,
+                        departureTime,
+                        arrivalTime
+                )
+        );
+    }
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/TimeUtils.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/TimeUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f6642a1d515fee29337f2c0df072cdd65af5456
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/TimeUtils.java
@@ -0,0 +1,29 @@
+package cz.muni.fi.pa165.weather.server.service;
+
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+
+/**
+ * Utility class for work with {@link LocalDateTime}.
+ */
+class TimeUtils {
+
+    private TimeUtils() {
+        // Intentionally made private to prevent instantiation.
+    }
+
+    static LocalDateTime toLocalDateTime(OffsetDateTime offsetDateTime) {
+        return offsetDateTime
+                .atZoneSameInstant(ZoneId.of("UTC+02:00"))
+                .toLocalDateTime();
+    }
+
+    static int getActualHour() {
+        return getHourOf(LocalDateTime.now());
+    }
+
+    static int getHourOf(LocalDateTime localDateTime) {
+        return localDateTime.getHour();
+    }
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/WeatherService.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/WeatherService.java
new file mode 100644
index 0000000000000000000000000000000000000000..b93f76db9f23d19ed832ef68715ef090d14044ae
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/WeatherService.java
@@ -0,0 +1,43 @@
+package cz.muni.fi.pa165.weather.server.service;
+
+import cz.muni.fi.pa165.weather.server.data.FlightCreationAdvice;
+import cz.muni.fi.pa165.weather.server.data.WeatherForecast;
+import org.springframework.stereotype.Service;
+
+import java.time.OffsetDateTime;
+
+@Service
+public interface WeatherService {
+
+    /**
+     * Get the weather info for the given {@code latitude}
+     * and {@code longitude} at this moment.
+     *
+     * @param latitude latitude
+     * @param longitude longitude
+     * @return weather information
+     */
+    WeatherForecast getWeatherForecast(
+            double latitude,
+            double longitude
+    );
+
+    /**
+     * Find out whether it's safe to create the flight based
+     * on {@code departureAirportId}, {@code arrivalAirportId},
+     * {@code departureTime} and {@code arrivalTime}. In case of
+     * that it isn't safe, returns also the reason why not.
+     *
+     * @param departureAirportId departure airport's id
+     * @param arrivalAirportId arrival airport's id
+     * @param departureTime flight departure time
+     * @param arrivalTime flight arrival time
+     * @return result as written above
+     */
+    FlightCreationAdvice isSafeToCreateFlight(
+            Long departureAirportId,
+            Long arrivalAirportId,
+            OffsetDateTime departureTime,
+            OffsetDateTime arrivalTime
+    );
+}
diff --git a/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/WeatherServiceImpl.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/WeatherServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..e540ed49dc2b321c0606fc3ac9e2e4c7607cb842
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/WeatherServiceImpl.java
@@ -0,0 +1,177 @@
+package cz.muni.fi.pa165.weather.server.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import cz.muni.fi.pa165.core.client.AirportApi;
+import cz.muni.fi.pa165.core.client.invoker.ApiException;
+import cz.muni.fi.pa165.core.client.model.AirportDto;
+import cz.muni.fi.pa165.weather.server.data.FlightCreationAdvice;
+import cz.muni.fi.pa165.weather.server.data.HourlyWeatherForecast;
+import cz.muni.fi.pa165.weather.server.data.WeatherForecast;
+import cz.muni.fi.pa165.weather.server.data.WeatherReason;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.OffsetDateTime;
+
+@Service
+public class WeatherServiceImpl implements WeatherService {
+
+    private final RestTemplate restTemplate = new RestTemplate();
+    private final ObjectMapper objectMapper = new ObjectMapper();
+    private final AirportApi airportClient = new AirportApi();
+
+    private static final String TEMPERATURE_ATTRIBUTE_NAME = "temperature_2m";
+    private static final String RAIN_ATTRIBUTE_NAME = "rain";
+    private static final String WIND_ATTRIBUTE_NAME = "windspeed_10m";
+    private static final String VISIBILITY_ATTRIBUTE_NAME = "visibility";
+
+    @Override
+    public WeatherForecast getWeatherForecast(double latitude, double longitude) {
+        String weatherForecastJsonAsString = restTemplate.getForObject(
+                getHourlyForecastUrl(latitude, longitude),
+                String.class
+        );
+        try {
+            HourlyWeatherForecast hourlyWeatherForecast = objectMapper
+                    .readValue(weatherForecastJsonAsString, HourlyWeatherForecast.class);
+
+            return getActualWeatherForecastFromHourlyForecast(hourlyWeatherForecast);
+        } catch (JsonProcessingException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public FlightCreationAdvice isSafeToCreateFlight(
+            Long departureAirportId,
+            Long arrivalAirportId,
+            OffsetDateTime departureTime,
+            OffsetDateTime arrivalTime
+    ) {
+        try {
+            AirportDto departureAirportDto = airportClient.getAirportById(departureAirportId);
+            AirportDto arrivalAirportDto = airportClient.getAirportById(arrivalAirportId);
+
+            WeatherForecast departureForecast = getWeatherForecast(
+                    departureAirportDto.getLocation().getLatitude(),
+                    departureAirportDto.getLocation().getLongitude()
+            );
+            WeatherForecast arrivalForecast = getWeatherForecast(
+                    arrivalAirportDto.getLocation().getLatitude(),
+                    arrivalAirportDto.getLocation().getLongitude()
+            );
+
+            var flightCreationAdvice = new FlightCreationAdvice();
+            flightCreationAdvice.setResult(false);
+            if (!departureForecast.temperatureInSafeBounds()
+                    || !arrivalForecast.temperatureInSafeBounds()) {
+                flightCreationAdvice.setWeatherReason(WeatherReason.NOK_TEMPERATURE);
+            } else if (!departureForecast.windSpeedInSafeBounds() ||
+                    !arrivalForecast.windSpeedInSafeBounds()) {
+                flightCreationAdvice.setWeatherReason(WeatherReason.NOK_WIND);
+            } else if (!departureForecast.rainInSafeBounds() ||
+                    !arrivalForecast.rainInSafeBounds()) {
+                flightCreationAdvice.setWeatherReason(WeatherReason.NOK_RAIN);
+            } else if (!departureForecast.visibilityInSafeBounds() ||
+                    !arrivalForecast.visibilityInSafeBounds()) {
+                flightCreationAdvice.setWeatherReason(WeatherReason.NOK_VISIBILITY);
+            } else {
+                flightCreationAdvice.setResult(true);
+                flightCreationAdvice.setWeatherReason(WeatherReason.OK_EVERYTHING);
+            }
+
+            return flightCreationAdvice;
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private WeatherForecast getActualWeatherForecastFromHourlyForecast(HourlyWeatherForecast hourlyWeatherForecast) {
+        return getWeatherForecastFromHourlyForHour(hourlyWeatherForecast, TimeUtils.getActualHour());
+    }
+
+    WeatherForecast getWeatherForecastFromHourlyForHour(
+            HourlyWeatherForecast hourlyWeatherForecast,
+            int actualHour
+    ) {
+        var weatherForecast = new WeatherForecast();
+        weatherForecast.setTemperature(parseTemperature(hourlyWeatherForecast, actualHour));
+        weatherForecast.setRain(parseRain(hourlyWeatherForecast, actualHour));
+        weatherForecast.setWindSpeed(parseWindSpeed(hourlyWeatherForecast, actualHour));
+        weatherForecast.setVisibility(parseVisibility(hourlyWeatherForecast, actualHour));
+
+        return weatherForecast;
+    }
+
+
+    private double parseValueByAttributeNameAndHour(
+            HourlyWeatherForecast hourlyWeatherForecast,
+            String attributeName,
+            int actualHour
+    ) {
+        return (double) hourlyWeatherForecast
+                .getHourly()
+                .get(attributeName)
+                .get(actualHour);
+    }
+
+    private double parseTemperature(
+            HourlyWeatherForecast hourlyWeatherForecast,
+            int actualHour
+    ) {
+        return parseValueByAttributeNameAndHour(
+                hourlyWeatherForecast,
+                TEMPERATURE_ATTRIBUTE_NAME,
+                actualHour
+        );
+    }
+
+    private double parseRain(
+            HourlyWeatherForecast hourlyWeatherForecast,
+            int actualHour
+    ) {
+        return parseValueByAttributeNameAndHour(
+                hourlyWeatherForecast,
+                RAIN_ATTRIBUTE_NAME,
+                actualHour
+        );
+    }
+
+    private double parseWindSpeed(
+            HourlyWeatherForecast hourlyWeatherForecast,
+            int actualHour
+    ) {
+        return parseValueByAttributeNameAndHour(
+                hourlyWeatherForecast,
+                WIND_ATTRIBUTE_NAME,
+                actualHour
+        );
+    }
+
+    private double parseVisibility(
+            HourlyWeatherForecast hourlyWeatherForecast,
+            int actualHour
+    ) {
+        return parseValueByAttributeNameAndHour(
+                hourlyWeatherForecast,
+                VISIBILITY_ATTRIBUTE_NAME,
+                actualHour
+        );
+    }
+
+    static String getHourlyForecastUrl(
+            double latitude,
+            double longitude
+    ) {
+        return "https://api.open-meteo.com/v1/forecast" +
+                "?latitude=" + latitude +
+                "&longitude=" + longitude +
+                "&hourly=" +
+                TEMPERATURE_ATTRIBUTE_NAME + "," +
+                RAIN_ATTRIBUTE_NAME + "," +
+                VISIBILITY_ATTRIBUTE_NAME + "," +
+                WIND_ATTRIBUTE_NAME +
+                "&forecast_days=1";
+    }
+}
diff --git a/weather/src/main/resources/application.yml b/weather/src/main/resources/application.yml
index 08729559efdd11fb686be558299337798e337ec7..8585c334357fcdc10ebddbaae9586a2cb0467195 100644
--- a/weather/src/main/resources/application.yml
+++ b/weather/src/main/resources/application.yml
@@ -9,4 +9,24 @@ spring:
 # Let's make weather microservice run on port 8088
 # for now to not collide with another microservices
 server:
-  port: 8088
\ No newline at end of file
+  port: 8088
+management:
+  endpoints:
+    web:
+      exposure:
+        include: 'info,health,metrics,prometheus'
+  info:
+    env:
+      enabled: true
+  endpoint:
+    health:
+      show-details: always
+      show-components: always
+      probes:
+        enabled: true
+info:
+  app:
+    encoding: 'UTF-8'
+    java:
+      source: '17'
+      target: '17'
diff --git a/weather/src/test/java/cz/muni/fi/pa165/weather/server/WeatherApplicationIT.java b/weather/src/test/java/cz/muni/fi/pa165/weather/server/WeatherApplicationIT.java
index 59dfb1620e3da0b2043a00ac6617d4f6c19c3fbd..78506e354b0c4d58691d4c92a1ab645490b470b2 100644
--- a/weather/src/test/java/cz/muni/fi/pa165/weather/server/WeatherApplicationIT.java
+++ b/weather/src/test/java/cz/muni/fi/pa165/weather/server/WeatherApplicationIT.java
@@ -1,18 +1,25 @@
 package cz.muni.fi.pa165.weather.server;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import cz.muni.fi.pa165.weather.server.model.FlightCreationAdvice;
-import cz.muni.fi.pa165.weather.server.model.WeatherForecast;
-import cz.muni.fi.pa165.weather.server.model.WeatherReason;
+import cz.muni.fi.pa165.weather.server.facade.WeatherFacade;
+import cz.muni.fi.pa165.weather.server.model.FlightCreationAdviceDto;
+import cz.muni.fi.pa165.weather.server.model.WeatherForecastDto;
+import cz.muni.fi.pa165.weather.server.model.WeatherReasonDto;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.test.web.servlet.MockMvc;
 
+import java.time.OffsetDateTime;
+
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -32,6 +39,9 @@ class WeatherApplicationIT {
     @Autowired
     ObjectMapper objectMapper;
 
+    @MockBean
+    private WeatherFacade weatherFacade;
+
     @Test
     void getWeatherForecastTest() throws Exception {
         log.debug("getWeatherForecastTest() running");
@@ -39,7 +49,15 @@ class WeatherApplicationIT {
         var expectedTemperature = 3.14;
         var expectedWindSpeed = 15.92;
         var expectedRain = 0.8;
-        var expectedVisibility = 6.7;
+        var expectedVisibility = 6000.7;
+
+        when(weatherFacade.getWeatherForecast(4.0, 4.0))
+                .thenReturn(new WeatherForecastDto()
+                        .temperature(expectedTemperature)
+                        .windSpeed(expectedWindSpeed)
+                        .rain(expectedRain)
+                        .visibility(expectedVisibility)
+                );
 
         var response = mockMvc.perform(get("/api/weatherForecast?latitude=4&longitude=4"))
                 .andExpect(status().isOk())
@@ -50,7 +68,10 @@ class WeatherApplicationIT {
                 .andReturn().getResponse().getContentAsString();
         log.debug("response: {}", response);
 
-        var weatherForecastResponse = objectMapper.readValue(response, WeatherForecast.class);
+        verify(weatherFacade).getWeatherForecast(4.0, 4.0);
+        verifyNoMoreInteractions(weatherFacade);
+
+        var weatherForecastResponse = objectMapper.readValue(response, WeatherForecastDto.class);
         assertThat(weatherForecastResponse.getTemperature()).isEqualTo(expectedTemperature);
         assertThat(weatherForecastResponse.getWindSpeed()).isEqualTo(expectedWindSpeed);
         assertThat(weatherForecastResponse.getRain()).isEqualTo(expectedRain);
@@ -62,7 +83,22 @@ class WeatherApplicationIT {
         log.debug("isSafeToCreateFlightTest() running");
 
         var expectedResult = true;
-        var expectedReason = WeatherReason.OK_EVERYTHING;
+        var expectedReason = WeatherReasonDto.OK_EVERYTHING;
+
+        String departureTimeAsString = "2012-12-31T22:00:00.000Z";
+        String arrivalTimeAsString = "2012-12-31T23:20:00.000Z";
+        OffsetDateTime departureTime = OffsetDateTime.parse(departureTimeAsString);
+        OffsetDateTime arrivalTime = OffsetDateTime.parse(arrivalTimeAsString);
+
+        when(weatherFacade.isSafeToCreateFlight(
+                1L,
+                2L,
+                departureTime,
+                arrivalTime)
+        ).thenReturn(new FlightCreationAdviceDto()
+                .result(expectedResult)
+                .reason(expectedReason)
+        );
 
         var response = mockMvc.perform(get("/api/isSafeToCreateFlight/1/2?departureTime=2012-12-31T22:00:00.000Z&arrivalTime=2012-12-31T23:20:00.000Z"))
                 .andExpect(status().isOk())
@@ -71,7 +107,10 @@ class WeatherApplicationIT {
                 .andReturn().getResponse().getContentAsString();
         log.debug("response: {}", response);
 
-        var weatherForecastResponse = objectMapper.readValue(response, FlightCreationAdvice.class);
+        verify(weatherFacade).isSafeToCreateFlight(1L, 2L, departureTime, arrivalTime);
+        verifyNoMoreInteractions(weatherFacade);
+
+        var weatherForecastResponse = objectMapper.readValue(response, FlightCreationAdviceDto.class);
         assertThat(weatherForecastResponse.getResult()).isEqualTo(expectedResult);
         assertThat(weatherForecastResponse.getReason()).isEqualTo(expectedReason);
     }
diff --git a/weather/src/test/java/cz/muni/fi/pa165/weather/server/data/WeatherForecastTest.java b/weather/src/test/java/cz/muni/fi/pa165/weather/server/data/WeatherForecastTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0448f5f7544fded911b000fc5e764ebde4a79d8
--- /dev/null
+++ b/weather/src/test/java/cz/muni/fi/pa165/weather/server/data/WeatherForecastTest.java
@@ -0,0 +1,77 @@
+package cz.muni.fi.pa165.weather.server.data;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+class WeatherForecastTest {
+
+    @Autowired
+    private WeatherForecast weatherForecast;
+
+    @Test
+    void temperatureInSafeBoundsTrue() {
+        weatherForecast.setTemperature(21.2);
+
+        assertTrue(weatherForecast.temperatureInSafeBounds());
+    }
+
+    @Test
+    void temperatureTooHigh() {
+        weatherForecast.setTemperature(42.42);
+
+        assertFalse(weatherForecast.temperatureInSafeBounds());
+    }
+
+    @Test
+    void temperatureTooLow() {
+        weatherForecast.setTemperature(-42.42);
+
+        assertFalse(weatherForecast.temperatureInSafeBounds());
+    }
+
+    @Test
+    void windSpeedInSafeBoundsTrue() {
+        weatherForecast.setWindSpeed(13);
+
+        assertTrue(weatherForecast.windSpeedInSafeBounds());
+    }
+
+    @Test
+    void windSpeedTooHigh() {
+        weatherForecast.setWindSpeed(81);
+
+        assertFalse(weatherForecast.windSpeedInSafeBounds());
+    }
+
+    @Test
+    void rainInSafeBoundsTrue() {
+        weatherForecast.setRain(1.2);
+
+        assertTrue(weatherForecast.rainInSafeBounds());
+    }
+
+    @Test
+    void rainsTooMuch() {
+        weatherForecast.setRain(3.2);
+
+        assertFalse(weatherForecast.rainInSafeBounds());
+    }
+
+    @Test
+    void visibilityInSafeBoundsTrue() {
+        weatherForecast.setVisibility(13500);
+
+        assertTrue(weatherForecast.visibilityInSafeBounds());
+    }
+
+    @Test
+    void visibilityTooLow() {
+        weatherForecast.setVisibility(911);
+
+        assertFalse(weatherForecast.visibilityInSafeBounds());
+    }
+}
\ No newline at end of file
diff --git a/weather/src/test/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacadeTest.java b/weather/src/test/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacadeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..80bd8d8180214e39fb99d860890e835128080e1a
--- /dev/null
+++ b/weather/src/test/java/cz/muni/fi/pa165/weather/server/facade/WeatherFacadeTest.java
@@ -0,0 +1,88 @@
+package cz.muni.fi.pa165.weather.server.facade;
+
+import cz.muni.fi.pa165.weather.server.data.FlightCreationAdvice;
+import cz.muni.fi.pa165.weather.server.data.WeatherForecast;
+import cz.muni.fi.pa165.weather.server.data.WeatherReason;
+import cz.muni.fi.pa165.weather.server.model.FlightCreationAdviceDto;
+import cz.muni.fi.pa165.weather.server.model.WeatherForecastDto;
+import cz.muni.fi.pa165.weather.server.model.WeatherReasonDto;
+import cz.muni.fi.pa165.weather.server.service.WeatherService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import java.time.OffsetDateTime;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@SpringBootTest
+class WeatherFacadeTest {
+
+    @Autowired
+    private WeatherFacade weatherFacade;
+
+    @MockBean
+    private WeatherService weatherService;
+
+    private static final WeatherForecast weatherForecast = new WeatherForecast();
+    private static final FlightCreationAdvice flightCreationAdvice = new FlightCreationAdvice();
+
+    @BeforeEach
+    void setUp() {
+        weatherForecast.setTemperature(21.2);
+        weatherForecast.setWindSpeed(13);
+        weatherForecast.setRain(1.2);
+        weatherForecast.setVisibility(13900);
+
+        flightCreationAdvice.setResult(false);
+        flightCreationAdvice.setWeatherReason(WeatherReason.NOK_WIND);
+    }
+
+    @Test
+    void getWeatherForecast() {
+        when(weatherService.getWeatherForecast(anyDouble(), anyDouble()))
+                .thenReturn(weatherForecast);
+
+        WeatherForecastDto weatherForecastDto = weatherFacade.getWeatherForecast(42.42, -42.42);
+
+        verify(weatherService)
+                .getWeatherForecast(42.42, -42.42);
+        verifyNoMoreInteractions(weatherService);
+        assertThat(weatherForecastDto.getTemperature())
+                .isEqualTo(weatherForecast.getTemperature());
+        assertThat(weatherForecastDto.getWindSpeed())
+                .isEqualTo(weatherForecast.getWindSpeed());
+        assertThat(weatherForecastDto.getRain())
+                .isEqualTo(weatherForecast.getRain());
+        assertThat(weatherForecastDto.getVisibility())
+                .isEqualTo(weatherForecast.getVisibility());
+    }
+
+    @Test
+    void isSafeToCreateFlight() {
+        var departureTime = OffsetDateTime.parse("2023-04-17T17:42:00Z");
+        var arrivalTime = OffsetDateTime.parse("2023-04-17T21:22:00+02:00");
+        when(weatherService.isSafeToCreateFlight(1L, 13L, departureTime, arrivalTime))
+                .thenReturn(flightCreationAdvice);
+
+        FlightCreationAdviceDto flightCreationAdviceDto = weatherFacade.isSafeToCreateFlight(
+                1L,
+                13L,
+                departureTime,
+                arrivalTime
+        );
+
+        verify(weatherService).isSafeToCreateFlight(1L, 13L, departureTime, arrivalTime);
+        verifyNoMoreInteractions(weatherService);
+        assertFalse(flightCreationAdviceDto.getResult());
+        assertThat(flightCreationAdviceDto.getReason())
+                .isEqualTo(WeatherReasonDto.NOK_WIND);
+    }
+}
\ No newline at end of file
diff --git a/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/FlightCreationAdviceMapperTest.java b/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/FlightCreationAdviceMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0d48669d0e1d2f27f5449667a008109e48aec3f2
--- /dev/null
+++ b/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/FlightCreationAdviceMapperTest.java
@@ -0,0 +1,32 @@
+package cz.muni.fi.pa165.weather.server.mapper;
+
+import cz.muni.fi.pa165.weather.server.data.FlightCreationAdvice;
+import cz.muni.fi.pa165.weather.server.data.WeatherReason;
+import cz.muni.fi.pa165.weather.server.model.FlightCreationAdviceDto;
+import cz.muni.fi.pa165.weather.server.model.WeatherReasonDto;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+@SpringBootTest
+class FlightCreationAdviceMapperTest {
+
+    @Autowired
+    private FlightCreationAdviceMapper flightCreationAdviceMapper;
+
+    @Test
+    void toDto() {
+        var flightCreationAdviceEntity = new FlightCreationAdvice();
+        flightCreationAdviceEntity.setResult(false);
+        flightCreationAdviceEntity.setWeatherReason(WeatherReason.NOK_RAIN);
+
+        FlightCreationAdviceDto flightCreationAdviceDto = flightCreationAdviceMapper.toDto(flightCreationAdviceEntity);
+
+        assertFalse(flightCreationAdviceDto.getResult());
+        assertThat(flightCreationAdviceDto.getReason())
+                .isEqualTo(WeatherReasonDto.NOK_RAIN);
+    }
+}
\ No newline at end of file
diff --git a/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/WeatherForecastMapperTest.java b/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/WeatherForecastMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..28ea90c93bd575a1f26651223889a582dcae1820
--- /dev/null
+++ b/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/WeatherForecastMapperTest.java
@@ -0,0 +1,36 @@
+package cz.muni.fi.pa165.weather.server.mapper;
+
+import cz.muni.fi.pa165.weather.server.data.WeatherForecast;
+import cz.muni.fi.pa165.weather.server.model.WeatherForecastDto;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class WeatherForecastMapperTest {
+
+    @Autowired
+    private WeatherForecastMapper weatherForecastMapper;
+
+    @Test
+    void toDto() {
+        var weatherForecastEntity = new WeatherForecast();
+        weatherForecastEntity.setTemperature(23.1);
+        weatherForecastEntity.setRain(0.3);
+        weatherForecastEntity.setWindSpeed(13.3);
+        weatherForecastEntity.setVisibility(18910);
+
+        WeatherForecastDto weatherForecastDto = weatherForecastMapper.toDto(weatherForecastEntity);
+
+        assertThat(weatherForecastDto.getTemperature())
+                .isEqualTo(weatherForecastEntity.getTemperature());
+        assertThat(weatherForecastDto.getRain())
+                .isEqualTo(weatherForecastEntity.getRain());
+        assertThat(weatherForecastDto.getWindSpeed())
+                .isEqualTo(weatherForecastEntity.getWindSpeed());
+        assertThat(weatherForecastDto.getVisibility())
+                .isEqualTo(weatherForecastEntity.getVisibility());
+    }
+}
\ No newline at end of file
diff --git a/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/WeatherReasonMapperTest.java b/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/WeatherReasonMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9320207b6a52d7f5b4aed1d06e85d1bd6a2d59cc
--- /dev/null
+++ b/weather/src/test/java/cz/muni/fi/pa165/weather/server/mapper/WeatherReasonMapperTest.java
@@ -0,0 +1,36 @@
+package cz.muni.fi.pa165.weather.server.mapper;
+
+import cz.muni.fi.pa165.weather.server.data.WeatherReason;
+import cz.muni.fi.pa165.weather.server.model.WeatherReasonDto;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class WeatherReasonMapperTest {
+
+    @Autowired
+    private WeatherReasonMapper weatherReasonMapper;
+
+    @Test
+    void toDtoWhenOkEverything() {
+        var weatherReasonEntity = WeatherReason.OK_EVERYTHING;
+
+        WeatherReasonDto weatherReasonDto = weatherReasonMapper.toDto(weatherReasonEntity);
+
+        assertThat(weatherReasonDto)
+                .isEqualTo(WeatherReasonDto.OK_EVERYTHING);
+    }
+
+    @Test
+    void toDtoWhenNokWind() {
+        var weatherReasonEntity = WeatherReason.NOK_WIND;
+
+        WeatherReasonDto weatherReasonDto = weatherReasonMapper.toDto(weatherReasonEntity);
+
+        assertThat(weatherReasonDto)
+                .isEqualTo(WeatherReasonDto.NOK_WIND);
+    }
+}
\ No newline at end of file
diff --git a/weather/src/test/java/cz/muni/fi/pa165/weather/server/service/TimeUtilsTest.java b/weather/src/test/java/cz/muni/fi/pa165/weather/server/service/TimeUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..711106820068c9ecc81622e89859b3380496a19d
--- /dev/null
+++ b/weather/src/test/java/cz/muni/fi/pa165/weather/server/service/TimeUtilsTest.java
@@ -0,0 +1,59 @@
+package cz.muni.fi.pa165.weather.server.service;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.OffsetDateTime;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class TimeUtilsTest {
+
+    @Test
+    void toLocalDateTimeFromUTC() {
+        OffsetDateTime offsetDateTime = OffsetDateTime
+                .parse("2023-04-17T17:42:18Z");
+
+        LocalDateTime localDateTime = TimeUtils.toLocalDateTime(offsetDateTime);
+
+        assertThat(localDateTime)
+                .isEqualTo(LocalDateTime.of(2023, Month.APRIL, 17, 19, 42, 18));
+    }
+
+    @Test
+    void toLocalDateTimeFromPositiveTimeZone() {
+        OffsetDateTime offsetDateTime = OffsetDateTime
+                .parse("2014-09-04T21:18:13+01:00");
+
+        LocalDateTime localDateTime = TimeUtils.toLocalDateTime(offsetDateTime);
+
+        assertThat(localDateTime)
+                .isEqualTo(LocalDateTime.of(2014, Month.SEPTEMBER, 4, 22, 18, 13));
+    }
+
+    @Test
+    void toLocalDateTimeFromNegativeZone() {
+        OffsetDateTime offsetDateTime = OffsetDateTime
+                .parse("2023-05-05T13:09:42-02:00");
+
+        LocalDateTime localDateTime = TimeUtils.toLocalDateTime(offsetDateTime);
+
+        assertThat(localDateTime)
+                .isEqualTo(LocalDateTime.of(2023, Month.MAY, 5, 17, 9, 42));
+    }
+
+    @Test
+    void getHourIndexOf() {
+        LocalDateTime localDateTime = LocalDateTime.of(
+                2023, Month.APRIL, 17, 14, 42
+        );
+
+        int hourIndex = TimeUtils.getHourOf(localDateTime);
+
+        assertThat(hourIndex)
+                .isEqualTo(14);
+    }
+}
\ No newline at end of file
diff --git a/weather/src/test/java/cz/muni/fi/pa165/weather/server/service/WeatherServiceImplTest.java b/weather/src/test/java/cz/muni/fi/pa165/weather/server/service/WeatherServiceImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cba9a4c300ac0f6359fcb632211f146695440f8c
--- /dev/null
+++ b/weather/src/test/java/cz/muni/fi/pa165/weather/server/service/WeatherServiceImplTest.java
@@ -0,0 +1,70 @@
+package cz.muni.fi.pa165.weather.server.service;
+
+import cz.muni.fi.pa165.weather.server.data.HourlyWeatherForecast;
+import cz.muni.fi.pa165.weather.server.data.WeatherForecast;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class WeatherServiceImplTest {
+
+    private static final HourlyWeatherForecast hourlyWeatherForecast = new HourlyWeatherForecast();
+
+    @Autowired
+    private WeatherServiceImpl weatherService;
+
+    @BeforeEach
+    void setUp() {
+        hourlyWeatherForecast.setHourly(Map.ofEntries(
+                Map.entry("temperature_2m", List.of(14.2, -21.3, 12.8)),
+                Map.entry("rain", List.of(0.9, 0.0, 0.1)),
+                Map.entry("windspeed_10m", List.of(31.2, 29.1, 42.42)),
+                Map.entry("visibility", List.of(12800.0, 13200.0, 5500.0))
+        ));
+    }
+
+    @Test
+    void getWeatherForecastFromHourlyForHour0() {
+        WeatherForecast weatherForecast = weatherService
+                .getWeatherForecastFromHourlyForHour(hourlyWeatherForecast, 0);
+
+        assertThat(weatherForecast.getTemperature())
+                .isEqualTo(14.2);
+        assertThat(weatherForecast.getRain())
+                .isEqualTo(0.9);
+        assertThat(weatherForecast.getWindSpeed())
+                .isEqualTo(31.2);
+        assertThat(weatherForecast.getVisibility())
+                .isEqualTo(12800.0);
+    }
+
+    @Test
+    void getWeatherForecastFromHourlyForHour1() {
+        WeatherForecast weatherForecast = weatherService
+                .getWeatherForecastFromHourlyForHour(hourlyWeatherForecast, 1);
+
+        assertThat(weatherForecast.getTemperature())
+                .isEqualTo(-21.3);
+        assertThat(weatherForecast.getRain())
+                .isEqualTo(0.0);
+        assertThat(weatherForecast.getWindSpeed())
+                .isEqualTo(29.1);
+        assertThat(weatherForecast.getVisibility())
+                .isEqualTo(13200.0);
+    }
+
+    @Test
+    void getHourlyForecastUrl() {
+        String hourlyForecastUrl = WeatherServiceImpl.getHourlyForecastUrl(42.42, -42.42);
+
+        assertThat(hourlyForecastUrl)
+                .isEqualTo("https://api.open-meteo.com/v1/forecast?latitude=42.42&longitude=-42.42&hourly=temperature_2m,rain,visibility,windspeed_10m&forecast_days=1");
+    }
+}
\ No newline at end of file