diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..378dfb67de3421235066750fd30bcd7980e3845f
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,53 @@
+image: 'maven:3.8.5-openjdk-17-slim'
+
+cache:
+  paths:
+    - .m2/repository
+
+variables:
+  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
+  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
+
+stages:
+  - build
+  - unit_test
+  - integration_test
+
+build:
+  tags:
+    - shared-fi
+  stage: build
+  script:
+    - echo "We are building your project, $GITLAB_USER_LOGIN"
+    - ls
+    - ./mvnw clean install -Dmaven.test.skip=true $MAVEN_CLI_OPTS
+
+unit_test:
+  tags:
+    - shared-fi
+  stage: unit_test
+  script:
+    - echo "We are testing your project build with unit tests, $GITLAB_USER_LOGIN"
+    - ./mvnw test $MAVEN_CLI_OPTS
+  artifacts:
+    expire_in: 10 min
+    paths:
+      - "*/target/surefire-reports/*"
+    reports:
+      junit:
+        - "*/target/surefire-reports/*.xml"
+
+integration_test:
+  tags:
+    - shared-fi
+  stage: integration_test
+  script:
+    - echo "We are testing your project build with integration tests, $GITLAB_USER_LOGIN"
+    - ./mvnw verify $MAVEN_CLI_OPTS
+  artifacts:
+    expire_in: 10 min
+    paths:
+      - "*/target/failsafe-reports/*"
+    reports:
+      junit:
+        - "*/target/failsafe-reports/*.xml"
\ No newline at end of file
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..cb28b0e37c7d206feb564310fdeec0927af4123a
Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..08ea486aa5a85d4894899b7e3aae7c51e68e161a
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/README.md b/README.md
index 66d3859762ee745ab74ebf1f6721b3cb2ba8a1a3..92b3b9b4ee61aa00abef0d43a4deb5a2e7c816e5 100644
--- a/README.md
+++ b/README.md
@@ -11,12 +11,50 @@ Create an information system managing flight records at an airport. The system s
 ![Alt text](/diagrams/DTO-Diagram.jpg "DTO class diagram")
 ![Alt text](/diagrams/UseCase-Diagram.jpg "use case diagram")
 
-## Installation
+## Installation/Running with maven
 ### To build with Maven
     `$mvn clean install`
 ### To run app on localhost
     `$mvn spring-boot:run - in each microservice separatly`
 
+## To run using Docker/Podman compose
+### To build with Maven
+    $ mvn clean install
+### If you want to force the images to be rebuild you can use
+    $ podman image list | grep "^localhost/pa165-airport-project_" | awk '{print $3}' | xargs podman image rm
+This will delete existing images.
+
+### To run the application (all microservices)
+    $ podman-compose up
+
+### 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.
+
+## Scenarios
+
+### Run scenarios
+Firstly run the app
+    $ cd locust-scenarios
+    $ locust Admin BasicUser
+Open internet browser
+Type http://localhost:8089
+Set: 
+    - Number of users 2
+    - Spawn rate 1
+    - Host http://localhost
+
+
 ## Usage
 ### Used ports
 + Authorization - port 8083
diff --git a/authorization/openapi.yaml b/authorization/openapi.yaml
deleted file mode 100644
index e9a842cb345cee79bf4a88c9d601de1a29ef40c4..0000000000000000000000000000000000000000
--- a/authorization/openapi.yaml
+++ /dev/null
@@ -1,301 +0,0 @@
-openapi: 3.0.3
-info:
-  title: Airport Manager authorization microservice
-  description: Airport Manager authorization microservice
-  version: 1.0.0
-servers:
-  - url: "{scheme}://{server}:{port}"
-    description: my server
-    variables:
-      scheme:
-        default: http
-        enum:
-          - http
-          - https
-      server:
-        default: localhost
-      port:
-        default: "${server.port}"
-paths:
-  /api/users/:
-    get:
-      tags:
-        - User
-      summary: List all system users
-      description: Get an array of all system users
-      operationId: getAllUsers
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/UserDto'
-    put:
-      tags:
-        - User
-      summary: Create user
-      description: Create a new user and return it.
-      operationId: createUser
-      requestBody:
-        description: Data to create a new user
-        required: true
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/CreationUserDto'
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/UserDto'
-        "400":
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-
-  /api/users/{id}:
-    get:
-      tags:
-        - User
-      summary: Get user by id.
-      description: Returns an object representing an user.
-      operationId: getUserById
-      parameters:
-        - name: id
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/UserDto'
-        "404":
-          description: Not Found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-    put:
-      tags:
-        - User
-      summary: Update user by id.
-      description: Update a user
-      operationId: updateUserById
-      requestBody:
-        description: Data to create a new user
-        required: true
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/CreationUserDto'
-      parameters:
-        - name: id
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/UserDto'
-        "404":
-          description: Not Found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-        "400":
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-    delete:
-      tags:
-        - User
-      summary: Delete user by id.
-      description: delete a user
-      operationId: deleteUserById
-      parameters:
-        - name: id
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "200":
-          description: OK
-        "404":
-          description: Not Found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-  /api/login:
-    put:
-      tags:
-        - User
-      summary: Login to application
-      description: Login to application and return an authentication token
-      operationId: login
-      requestBody:
-        description: Login name and password
-        required: true
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/LoginDto'
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/LoginResponse'
-        "400":
-          description: Invalid input
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-components:
-  schemas:
-    DomainEntity:
-      title: Domain Entity
-      description: Represents a Domain Entity
-      type: object
-      required:
-        - id
-      properties:
-        id:
-          type: integer
-          description: unique id
-          format: int64
-          example: 1
-      discriminator:
-        propertyName: objectType
-
-    ErrorMessage:
-      title: Error Message
-      description: Response body for HTML statuses.
-      type: object
-      properties:
-        message:
-          type: string
-          description: reason for error
-          example: entity not found
-
-    UserDto:
-      allOf:
-        - $ref: '#/components/schemas/DomainEntity'
-      type: object
-      title: User
-      description: Represents a system user.
-      required:
-        - login
-        - firstName
-        - lastName
-        - email
-        - role
-      properties:
-        login:
-          type: string
-          description: name used for logging in
-          example: john.doe
-        firstName:
-          type: string
-          description: first name of a the user
-          example: John
-        lastName:
-          type: string
-          description: last name of a user
-          example: Doe
-        email:
-          type: string
-          description: email address of the user
-          example: john@example.com
-        role:
-          description: the role of the user
-          type: string
-          #  FIXME: is this an openapi bug or just wrong syntax ? https://github.com/OpenAPITools/openapi-generator/issues/11323
-          #enum:
-          #  - manager
-          #  - admin
-    CreationUserDto:
-      type: object
-      title: User
-      description: Represents a user.
-      required:
-        - login
-        - firstName
-        - lastName
-        - password
-        - email
-        - role
-      properties:
-        login:
-          type: string
-          description: name used for logging in
-          example: john.doe
-        firstName:
-          type: string
-          description: first name of a the user
-          example: John
-        lastName:
-          type: string
-          description: last name of a user
-          example: Doe
-        password:
-          type: string
-          description: password of the user
-          example: secretPassword
-        email:
-          type: string
-          description: email address of the user
-          example: john@example.com
-        role:
-          type: string
-          # TODO: enum
-    LoginDto:
-      type: object
-      title: User
-      description: Represents login information for a user.
-      required:
-        - login
-        - password
-      properties:
-        login:
-          type: string
-          description: name used for logging in
-          example: john.doe
-        password:
-          type: string
-          description: password of the user
-          example: secretPassword
-    LoginResponse:
-      type: object
-      title: User
-      description: Represents a login response
-      required:
-        - token
-      properties:
-        token:
-          type: string
-          description: token used for authentication
\ No newline at end of file
diff --git a/authorization/src/main/java/cz/muni/fi/pa165/authorization/server/rest/UserController.java b/authorization/src/main/java/cz/muni/fi/pa165/authorization/server/rest/UserController.java
deleted file mode 100644
index e3921a3de83a660fea68daed83bb7aa15045a49a..0000000000000000000000000000000000000000
--- a/authorization/src/main/java/cz/muni/fi/pa165/authorization/server/rest/UserController.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package cz.muni.fi.pa165.authorization.server.rest;
-
-import cz.muni.fi.pa165.authorization.server.api.UserApiDelegate;
-import cz.muni.fi.pa165.authorization.server.model.CreationUserDto;
-import cz.muni.fi.pa165.authorization.server.model.LoginDto;
-import cz.muni.fi.pa165.authorization.server.model.LoginResponse;
-import cz.muni.fi.pa165.authorization.server.model.UserDto;
-import cz.muni.fi.pa165.authorization.server.service.UserService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.List;
-
-@RestController
-public class UserController implements UserApiDelegate {
-
-    private final UserService userService;
-
-    @Autowired
-    public UserController(UserService userService) {
-        this.userService = userService;
-    }
-
-    @Override
-    public ResponseEntity<UserDto> getUserById(Long id) {
-        return ResponseEntity.ok(userService.getUserById(id));
-    }
-
-    @Override
-    public ResponseEntity<UserDto> createUser(CreationUserDto creationUserDto) {
-        return ResponseEntity.ok(userService.createUser(creationUserDto));
-    }
-
-    @Override
-    public ResponseEntity<UserDto> updateUserById(Long id, CreationUserDto creationUserDto) {
-        return ResponseEntity.ok(userService.updateUserById(id, creationUserDto));
-    }
-
-    @Override
-    public ResponseEntity<Void> deleteUserById(Long id) {
-        userService.deleteUserById(id);
-        return ResponseEntity.ok(null);
-    }
-
-    @Override
-    public ResponseEntity<LoginResponse> login(LoginDto loginDto) {
-        return ResponseEntity.ok(userService.login(loginDto));
-    }
-
-    @Override
-    public ResponseEntity<List<UserDto>> getAllUsers() {
-        return ResponseEntity.ok(userService.getAllUsers());
-    }
-}
diff --git a/authorization/src/main/java/cz/muni/fi/pa165/authorization/server/service/UserService.java b/authorization/src/main/java/cz/muni/fi/pa165/authorization/server/service/UserService.java
deleted file mode 100644
index dbe944782304fdf4d98a512dd32ca79a6d0f2174..0000000000000000000000000000000000000000
--- a/authorization/src/main/java/cz/muni/fi/pa165/authorization/server/service/UserService.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package cz.muni.fi.pa165.authorization.server.service;
-
-import cz.muni.fi.pa165.authorization.server.model.CreationUserDto;
-import cz.muni.fi.pa165.authorization.server.model.LoginDto;
-import cz.muni.fi.pa165.authorization.server.model.LoginResponse;
-import cz.muni.fi.pa165.authorization.server.model.UserDto;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-@Service
-public class UserService {
-
-    public UserDto getUserById(Long id) {
-        return new UserDto()
-                .id(id)
-                .login("john.doe")
-                .firstName("John")
-                .lastName("Doe")
-                .email("john@example.com")
-                .role("manager");
-    }
-
-    public UserDto createUser(CreationUserDto creationUserDto) {
-        return new UserDto()
-                .id(1L)
-                .login(creationUserDto.getLogin())
-                .firstName(creationUserDto.getFirstName())
-                .lastName(creationUserDto.getLastName())
-                .email(creationUserDto.getEmail())
-                .role(creationUserDto.getRole());
-    }
-
-    public UserDto updateUserById(Long id, CreationUserDto creationUserDto) {
-        return new UserDto()
-                .id(id)
-                .login(creationUserDto.getLogin())
-                .firstName(creationUserDto.getFirstName())
-                .lastName(creationUserDto.getLastName())
-                .email(creationUserDto.getEmail())
-                .role(creationUserDto.getRole());
-    }
-
-    public void deleteUserById(Long id) {
-
-    }
-
-    public LoginResponse login(LoginDto loginDto) {
-        return new LoginResponse().token("token-for-" + loginDto.getLogin());
-    }
-
-    public List<UserDto> getAllUsers() {
-        return List.of(getUserById(1L));
-    }
-}
diff --git a/authorization/src/main/resources/application.yml b/authorization/src/main/resources/application.yml
deleted file mode 100644
index 86820bd5b40c31e85ed3ec1d44cc4cba01a5cfd4..0000000000000000000000000000000000000000
--- a/authorization/src/main/resources/application.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-spring:
-  datasource:
-    url: jdbc:h2:mem:exampleDb
-    username: sa
-    password: password
-    driverClassName: org.h2.Driver
-  jpa:
-    database-platform: org.hibernate.dialect.H2Dialect
-server:
-  port: 8083
\ No newline at end of file
diff --git a/authorization/src/test/java/cz/muni/fi/pa165/authorization/server/UsersIT.java b/authorization/src/test/java/cz/muni/fi/pa165/authorization/server/UsersIT.java
deleted file mode 100644
index 7a1ce4adb605f74052b224e93f7267ab2dae3c4f..0000000000000000000000000000000000000000
--- a/authorization/src/test/java/cz/muni/fi/pa165/authorization/server/UsersIT.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package cz.muni.fi.pa165.authorization.server;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import cz.muni.fi.pa165.authorization.server.model.LoginResponse;
-import cz.muni.fi.pa165.authorization.server.model.UserDto;
-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.http.MediaType;
-import org.springframework.test.web.servlet.MockMvc;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-@SpringBootTest
-@AutoConfigureMockMvc
-class UsersIT {
-
-	private static final Logger log = LoggerFactory.getLogger(UsersIT.class);
-
-	@Autowired
-	private MockMvc mockMvc;
-
-	@Autowired
-	private ObjectMapper objectMapper;
-
-	@Test
-	void getUserByIdTest() throws Exception {
-		log.debug("getUserByIdTest() running");
-		String response = mockMvc.perform(get("/api/users/1"))
-				.andExpect(status().isOk())
-				.andExpect(jsonPath("$.firstName").value("John"))
-				.andExpect(jsonPath("$.lastName").value("Doe"))
-				.andExpect(jsonPath("$.login").value("john.doe"))
-				.andExpect(jsonPath("$.email").value("john@example.com"))
-				.andExpect(jsonPath("$.role").value("manager"))
-				.andReturn().getResponse().getContentAsString();
-		log.debug("response: {}", response);
-		UserDto responseDto = objectMapper.readValue(response, UserDto.class);
-		assertThat(responseDto.getFirstName()).isEqualTo("John");
-		assertThat(responseDto.getLastName()).isEqualTo("Doe");
-		assertThat(responseDto.getLogin()).isEqualTo("john.doe");
-		assertThat(responseDto.getEmail()).isEqualTo("john@example.com");
-	}
-
-	@Test
-	void getAllUsers() throws Exception {
-		log.debug("getAllUsers() running");
-		String response = mockMvc.perform(get("/api/users/"))
-				.andExpect(status().isOk())
-				.andExpect(jsonPath("$[0].firstName").value("John"))
-				.andExpect(jsonPath("$[0].lastName").value("Doe"))
-				.andExpect(jsonPath("$[0].login").value("john.doe"))
-				.andExpect(jsonPath("$[0].email").value("john@example.com"))
-				.andExpect(jsonPath("$[0].role").value("manager"))
-				.andReturn().getResponse().getContentAsString();
-		log.debug("response: {}", response);
-		UserDto[] responseDtos = objectMapper.readValue(response, UserDto[].class);
-		assertThat(responseDtos[0].getFirstName()).isEqualTo("John");
-		assertThat(responseDtos[0].getLastName()).isEqualTo("Doe");
-		assertThat(responseDtos[0].getLogin()).isEqualTo("john.doe");
-		assertThat(responseDtos[0].getEmail()).isEqualTo("john@example.com");
-	}
-
-	@Test
-	void createUser() throws Exception {
-		log.debug("createUser() running");
-		String requestBody = """
-                {
-                  "login": "john.doe",
-                  "firstName": "John",
-                  "lastName": "Doe",
-                  "password": "secretPassword",
-                  "email": "john@example.com",
-                  "role": "manager"
-                }""";
-
-		String response = mockMvc.perform(
-						put("/api/users/")
-								.contentType(MediaType.APPLICATION_JSON)
-								.content(requestBody))
-				.andExpect(status().isOk())
-				.andExpect(jsonPath("$.id").value(1L))
-				.andExpect(jsonPath("$.firstName").value("John"))
-				.andExpect(jsonPath("$.lastName").value("Doe"))
-				.andExpect(jsonPath("$.login").value("john.doe"))
-				.andExpect(jsonPath("$.email").value("john@example.com"))
-				.andExpect(jsonPath("$.role").value("manager"))
-				.andReturn().getResponse().getContentAsString();
-		log.debug("response: {}", response);
-		UserDto responseDto = objectMapper.readValue(response, UserDto.class);
-		assertThat(responseDto.getId()).isEqualTo(1L);
-		assertThat(responseDto.getFirstName()).isEqualTo("John");
-		assertThat(responseDto.getLastName()).isEqualTo("Doe");
-		assertThat(responseDto.getLogin()).isEqualTo("john.doe");
-		assertThat(responseDto.getEmail()).isEqualTo("john@example.com");
-	}
-
-	@Test
-	void updateUserById() throws Exception {
-		log.debug("updateUserByIdTest() running");
-		String requestBody = """
-                {
-                  "login": "john.doe",
-                  "firstName": "John",
-                  "lastName": "Doe",
-                  "password": "secretPassword",
-                  "email": "john@example.com",
-                  "role": "manager"
-                }""";
-
-		String response = mockMvc.perform(
-						put("/api/users/1")
-								.contentType(MediaType.APPLICATION_JSON)
-								.content(requestBody))
-				.andExpect(status().isOk())
-				.andExpect(jsonPath("$.firstName").value("John"))
-				.andExpect(jsonPath("$.lastName").value("Doe"))
-				.andExpect(jsonPath("$.login").value("john.doe"))
-				.andExpect(jsonPath("$.email").value("john@example.com"))
-				.andExpect(jsonPath("$.role").value("manager"))
-				.andReturn().getResponse().getContentAsString();
-		log.debug("response: {}", response);
-		UserDto responseDto = objectMapper.readValue(response, UserDto.class);
-		assertThat(responseDto.getFirstName()).isEqualTo("John");
-		assertThat(responseDto.getLastName()).isEqualTo("Doe");
-		assertThat(responseDto.getLogin()).isEqualTo("john.doe");
-		assertThat(responseDto.getEmail()).isEqualTo("john@example.com");
-	}
-
-	@Test
-	void deleteUser() throws Exception {
-		log.debug("deleteUserBy() running");
-
-		String response = mockMvc.perform(
-						delete("/api/users/1"))
-				.andExpect(status().isOk())
-				.andReturn().getResponse().getContentAsString();
-		log.debug("response: {}", response);
-	}
-
-	@Test
-	void login() throws Exception {
-		log.debug("login() running");
-		String requestBody = """
-                {
-                  "login": "john.doe",
-                  "password": "password"
-                }""";
-
-		String response = mockMvc.perform(
-						put("/api/login")
-								.contentType(MediaType.APPLICATION_JSON)
-								.content(requestBody))
-				.andExpect(status().isOk())
-				.andExpect(jsonPath("$.token").value("token-for-john.doe"))
-				.andReturn().getResponse().getContentAsString();
-		log.debug("response: {}", response);
-		LoginResponse responseDto = objectMapper.readValue(response, LoginResponse.class);
-		assertThat(responseDto.getToken()).isEqualTo("token-for-john.doe");
-	}
-
-}
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..47a0610f271a7d7f2e24ec38bf44e361c1f922af
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,46 @@
+version: "3"
+
+networks:
+  grafana-prometheus:
+    driver: bridge
+
+services:
+  core:
+    build: "./core"
+    ports:
+        - "8080:8080"
+
+  authorization:
+    build: "./user"
+    ports:
+      - "8083:8083"
+
+  report:
+    build: "./report"
+    ports:
+      - "8085:8085"
+
+  weather:
+    build: "./weather"
+    ports:
+      - "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/Dockerfile b/core/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d131222e38617e1c16b7e0e8e265b68f253b1849
--- /dev/null
+++ b/core/Dockerfile
@@ -0,0 +1,3 @@
+FROM eclipse-temurin:17-jdk-alpine
+COPY target/*.jar /app.jar
+ENTRYPOINT ["java", "-jar", "/app.jar"]
\ No newline at end of file
diff --git a/core/openapi.yaml b/core/openapi.yaml
index 9caaa678d0b3b1818303ac4f95039ce47afd7504..f719cd6c8d88f8e414caaebfac9790c90051c2c9 100644
--- a/core/openapi.yaml
+++ b/core/openapi.yaml
@@ -27,6 +27,8 @@ servers:
 tags:
   - name: Core
     description: Microservice for core.
+security:
+  - BearerAuth: []
 paths:
   /api/stewards:
     get:
@@ -172,33 +174,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-    put:
-      tags:
-        - Steward
-      summary: Update assignment of steward to a flight.
-      operationId: updateStewardFlights
-      parameters:
-        - name: stewardId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: flightId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "200":
-          $ref: '#/components/responses/SingleStewardDtoResponse'
-        "400":
-          description: Input data not correct
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
     delete:
       tags:
         - Steward
@@ -488,7 +463,7 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/AirplaneTypeDto'
+              $ref: '#/components/schemas/NewAirplaneTypeDtoRequest'
         required: true
       responses:
         "200":
@@ -544,6 +519,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+        "404":
+          description: Airplane type not Found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
   /api/airplanes/{id}:
     get:
       tags:
@@ -565,6 +546,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/AirplaneDto'
+        "404":
+          description: Not Found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
     delete:
       tags:
         - Airplane
@@ -603,7 +590,7 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/AirplaneDto'
+              $ref: '#/components/schemas/NewAirplaneDtoRequest'
         required: true
       responses:
         "200":
@@ -618,92 +605,8 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-  /api/airplanes/{airplaneId}/airplaneTypes/{airplaneTypeId}:
-    post:
-      tags:
-        - Airplane
-      summary: Assign airplane type to a airplane.
-      operationId: assignAirplaneType
-      parameters:
-        - name: airplaneId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: airplaneTypeId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "201":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/AirplaneDto'
-        "400":
-          description: Input data not correct
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-    put:
-      tags:
-        - Airplane
-      summary: Update assignment of airplane type to an airplane.
-      operationId: updateAirplaneTypeAssignment
-      parameters:
-        - name: airplaneId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: airplaneTypeId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/AirplaneDto'
-        "400":
-          description: Input data not correct
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-    delete:
-      tags:
-        - Airplane
-      summary: Delete assignment of airplane type to an airplane.
-      operationId: deleteAirplaneTypeAssignment
-      parameters:
-        - name: airplaneId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: airplaneTypeId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "204":
-          description: Deleted
         "404":
-          description: Not Found
+          description: Airplane type not Found
           content:
             application/json:
               schema:
@@ -808,7 +711,7 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/CountryDto'
+              $ref: '#/components/schemas/NewCountryDtoRequest'
         required: true
       responses:
         "200":
@@ -826,7 +729,7 @@ paths:
   /api/cities:
     get:
       tags:
-        - Country
+        - City
       summary: Get all cities.
       description: Returns an array of objects representing cities.
       operationId: getAllCities
@@ -923,7 +826,7 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/CityDto'
+              $ref: '#/components/schemas/NewCityDtoRequest'
         required: true
       responses:
         "200":
@@ -970,37 +873,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-    put:
-      tags:
-        - City
-      summary: Update assignment of country to a city.
-      operationId: updateCountryAssignment
-      parameters:
-        - name: cityId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: countryId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/CityDto'
-        "400":
-          description: Input data not correct
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
     delete:
       tags:
         - City
@@ -1028,96 +900,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-  /api/cities/{cityId}/airports/{airportId}:
-    post:
-      tags:
-        - City
-      summary: Assign airport to a city.
-      operationId: assignAirport
-      parameters:
-        - name: cityId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: airportId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "201":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/CityDto'
-        "400":
-          description: Input data not correct
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-    put:
-      tags:
-        - City
-      summary: Update assignment of airport to a city.
-      operationId: updateAirportAssignment
-      parameters:
-        - name: cityId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: airportId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/CityDto'
-        "400":
-          description: Input data not correct
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-    delete:
-      tags:
-        - City
-      summary: Delete assignment of airport to a city.
-      operationId: deleteAirportAssignment
-      parameters:
-        - name: cityId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: airportId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "204":
-          description: Deleted
-        "404":
-          description: Not Found
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
   /api/airports:
     get:
       tags:
@@ -1204,7 +986,7 @@ paths:
     put:
       tags:
         - Airport
-      summary: Update city by id.
+      summary: Update airport by id.
       operationId: updateAirport
       parameters:
         - name: id
@@ -1218,7 +1000,7 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/AirportDto'
+              $ref: '#/components/schemas/NewAirportDtoRequest'
         required: true
       responses:
         "200":
@@ -1265,37 +1047,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-    put:
-      tags:
-        - Airport
-      summary: Update assignment of departing flight to an airport.
-      operationId: updateDepartingFlightAssignment
-      parameters:
-        - name: airportId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-        - name: departingFlightId
-          in: path
-          required: true
-          schema:
-            type: integer
-            format: int64
-      responses:
-        "200":
-          description: OK
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/AirportDto'
-        "400":
-          description: Input data not correct
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
     delete:
       tags:
         - Airport
@@ -1355,11 +1106,11 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-    put:
+    delete:
       tags:
         - Airport
-      summary: Update assignment of arriving flight to an airport.
-      operationId: updateArrivingFlightAssignment
+      summary: Delete assignment of arriving flight to an airport.
+      operationId: deleteArrivingFlightAssignment
       parameters:
         - name: airportId
           in: path
@@ -1374,7 +1125,35 @@ paths:
             type: integer
             format: int64
       responses:
-        "200":
+        "204":
+          description: Deleted
+        "404":
+          description: Not Found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+  /api/airports/{airportId}/city/{cityId}:
+    post:
+      tags:
+        - Airport
+      summary: Assign the city to an airport.
+      operationId: assignCity
+      parameters:
+        - name: airportId
+          in: path
+          required: true
+          schema:
+            type: integer
+            format: int64
+        - name: cityId
+          in: path
+          required: true
+          schema:
+            type: integer
+            format: int64
+      responses:
+        "201":
           description: OK
           content:
             application/json:
@@ -1389,8 +1168,8 @@ paths:
     delete:
       tags:
         - Airport
-      summary: Delete assignment of arriving flight to an airport.
-      operationId: deleteArrivingFlightAssignment
+      summary: Delete assignment of the city to an airport.
+      operationId: deleteCityAssignment
       parameters:
         - name: airportId
           in: path
@@ -1398,7 +1177,7 @@ paths:
           schema:
             type: integer
             format: int64
-        - name: arrivingFlightId
+        - name: cityId
           in: path
           required: true
           schema:
@@ -1413,9 +1192,32 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+  /api/db/seed:
+    post:
+      tags:
+        - Database
+      summary: Seeds the predefined data database
+      operationId: seed
+      responses:
+        "201":
+          description: Created
+  /api/db/clear:
+    delete:
+      tags:
+        - Database
+      summary: Clears the predefined data database
+      operationId: clear
+      responses:
+        "204":
+          description: Deleted
 components:
+  securitySchemes:
+    BearerAuth:
+      type: http
+      description: "OAuth2 Resource Server, provide a valid access token"
+      scheme: bearer
   schemas:
-    DomainEntity:
+    DomainEntityDto:
       title: Domain Entity
       description: Represents a Domain Entity
       type: object
@@ -1430,8 +1232,6 @@ components:
       discriminator:
         propertyName: objectType
     ErrorMessage:
-      allOf:
-        - $ref: '#/components/schemas/DomainEntity'
       title: Error Message
       description: Response body for HTML statuses.
       type: object
@@ -1460,7 +1260,7 @@ components:
           example: /api/stewards/1
     StewardDto:
       allOf:
-        - $ref: '#/components/schemas/DomainEntity'
+        - $ref: '#/components/schemas/DomainEntityDto'
       type: object
       title: Steward
       description: Represents a steward on a flight.
@@ -1477,11 +1277,12 @@ components:
           type: string
           description: last name of a steward
           example: Doe
-        flights:
+        assignedFlightIds:
           type: array
           description: flights assigned to a steward
           items:
-            $ref: '#/components/schemas/FlightDto'
+            type: integer
+            format: int64
     NewStewardDtoRequest:
       type: object
       title: New Steward Request
@@ -1500,7 +1301,7 @@ components:
           example: Doe
     FlightDto:
       allOf:
-        - $ref: '#/components/schemas/DomainEntity'
+        - $ref: '#/components/schemas/DomainEntityDto'
       type: object
       title: Flight
       description: Represents a flight.
@@ -1508,7 +1309,7 @@ components:
         - id
         - departureTime
         - arrivalTime
-        - stewards
+        - stewardIds
         - airplane
         - departureAirport
         - arrivalAirport
@@ -1517,23 +1318,27 @@ components:
           type: string
           description: time of flight departure
           format: date-time
-          example: 2022-12-22T12:04:04.493908908+01:00
+          example: 2023-12-22T12:04:04.493908908+01:00
         arrivalTime:
           type: string
           description: time of flight arrival
           format: date-time
-          example: 2022-12-22T12:04:04.493908908+01:00
-        stewards:
+          example: 2023-12-22T12:04:04.493908908+01:00
+        assignedStewardIds:
           type: array
           description: stewards assigned to a flight
           items:
-            $ref: '#/components/schemas/StewardDto'
-        airplane:
-          $ref: '#/components/schemas/AirplaneDto'
-        departureAirport:
-          $ref: '#/components/schemas/AirportDto'
-        arrivalAirport:
-          $ref: '#/components/schemas/AirportDto'
+            type: integer
+            format: int64
+        airplaneId:
+          type: integer
+          format: int64
+        departureAirportId:
+          type: integer
+          format: int64
+        arrivalAirportId:
+          type: integer
+          format: int64
     NewFlightDtoRequest:
       type: object
       title: New Flight Request
@@ -1541,20 +1346,26 @@ components:
       required:
         - departureTime
         - arrivalTime
+        - airplaneId
       properties:
         departureTime:
           type: string
           description: time of flight departure
           format: date-time
-          example: 2022-12-22T12:04:04.493908908+01:00
+          example: 2023-12-22T12:04:04.493908908+01:00
         arrivalTime:
           type: string
           description: time of flight arrival
           format: date-time
-          example: 2022-12-22T12:04:04.493908908+01:00
+          example: 2023-12-22T12:04:04.493908908+01:00
+        airplaneId:
+          type: integer
+          format: int64
+          description: assigned airplane id
+          example: 1
     AirplaneTypeDto:
       allOf:
-        - $ref: '#/components/schemas/DomainEntity'
+        - $ref: '#/components/schemas/DomainEntityDto'
       type: object
       title: Airplane Type
       description: Represents an Airplane Type.
@@ -1566,6 +1377,12 @@ components:
           type: string
           description: airplane type name
           example: Boeing 747
+        airplanesIds:
+          type: array
+          items:
+            type: integer
+            format: int64
+            description: assigned airplanes ids
     NewAirplaneTypeDtoRequest:
       type: object
       title: New Airplane Type Request
@@ -1579,7 +1396,7 @@ components:
           example: Boeing 747
     AirplaneDto:
       allOf:
-        - $ref: '#/components/schemas/DomainEntity'
+        - $ref: '#/components/schemas/DomainEntityDto'
       type: object
       title: Airplane
       description: Represents an Airplane.
@@ -1587,7 +1404,7 @@ components:
         - id
         - name
         - capacity
-        - type
+        - typeId
       properties:
         name:
           type: string
@@ -1598,8 +1415,11 @@ components:
           description: airplane seat capacity
           format: int32
           example: 150
-        type:
-          $ref: '#/components/schemas/AirplaneTypeDto'
+        typeId:
+          type: integer
+          format: int64
+          description: assigned airplane type's id
+          example: 1
     NewAirplaneDtoRequest:
       type: object
       title: New Airplane Request
@@ -1607,6 +1427,7 @@ components:
       required:
         - name
         - capacity
+        - typeId
       properties:
         name:
           type: string
@@ -1617,6 +1438,11 @@ components:
           description: airplane seat capacity
           format: int32
           example: 150
+        typeId:
+          type: integer
+          format: int64
+          description: assigned airplane type's id
+          example: 1
     PageableObject:
       type: object
       title: Pageable Object
@@ -1681,7 +1507,7 @@ components:
           type: boolean
     CountryDto:
       allOf:
-        - $ref: '#/components/schemas/DomainEntity'
+        - $ref: '#/components/schemas/DomainEntityDto'
       type: object
       title: Country
       description: Represents a country.
@@ -1693,6 +1519,12 @@ components:
           type: string
           description: country name
           example: Canada
+        citiesIds:
+          type: array
+          items:
+            type: integer
+            format: int64
+          description: cities assigned to the country
     NewCountryDtoRequest:
       type: object
       title: New Country Request
@@ -1706,7 +1538,7 @@ components:
           example: Canada
     CityDto:
       allOf:
-        - $ref: '#/components/schemas/DomainEntity'
+        - $ref: '#/components/schemas/DomainEntityDto'
       type: object
       title: City
       description: Represents a City in a Country containing Airports.
@@ -1718,12 +1550,16 @@ components:
           type: string
           description: city name
           example: London
-        country:
-          $ref: '#/components/schemas/CountryDto'
-        airports:
+        countryId:
+          type: integer
+          format: int64
+          description: id of assigned country, 0 if none assigned yet
+        airportsIds:
           type: array
           items:
-            $ref: '#/components/schemas/AirportDto'
+            type: integer
+            format: int64
+          description: ids of assigned airports
     NewCityDtoRequest:
       type: object
       title: New City Request
@@ -1753,7 +1589,7 @@ components:
           example: 2.17403
     AirportDto:
       allOf:
-        - $ref: '#/components/schemas/DomainEntity'
+        - $ref: '#/components/schemas/DomainEntityDto'
       type: object
       title: Airport
       description: Represents an Airport.
@@ -1761,9 +1597,6 @@ components:
         - id
         - name
         - code
-        - departingFlights
-        - arrivingFlights
-        - city
         - location
       properties:
         name:
@@ -1774,18 +1607,24 @@ components:
           type: string
           description: airport code in IATA format
           example: JFK
-        departingFlights:
+        location:
+          $ref: '#/components/schemas/GPSLocationDto'
+        departingFlightsIds:
           type: array
           items:
-            $ref: '#/components/schemas/FlightDto'
-        arrivingFlights:
+            type: integer
+            format: int64
+          description: assigned departing flights ids
+        arrivingFlightsIds:
           type: array
           items:
-            $ref: '#/components/schemas/FlightDto'
-        city:
-          $ref: '#/components/schemas/CityDto'
-        location:
-          $ref: '#/components/schemas/GPSLocationDto'
+            type: integer
+            format: int64
+          description: assigned arriving flights ids
+        cityId:
+          type: integer
+          format: int64
+          description: assigned city's id, 0 if none assigned yet
     NewAirportDtoRequest:
       type: object
       title: New Airport Type Request
@@ -1793,6 +1632,7 @@ components:
       required:
         - name
         - code
+        - location
       properties:
         name:
           type: string
@@ -1802,6 +1642,8 @@ components:
           type: string
           description: airport code in IATA format
           example: JFK
+        location:
+          $ref: '#/components/schemas/GPSLocationDto'
   responses:
     SingleStewardDtoResponse:
       description: Response containing a single Steward.
diff --git a/core/pom.xml b/core/pom.xml
index 62fe1e1df351da1a959d2fc2b2c93f02c784aa1e..05d16bac11e7e65e359aa8fcb3ad06f483c31db6 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>
@@ -31,6 +43,11 @@
             <artifactId>mapstruct</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.modelmapper</groupId>
+            <artifactId>modelmapper</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
@@ -42,8 +59,8 @@
         </dependency>
 
         <dependency>
-            <groupId>jakarta.validation</groupId>
-            <artifactId>jakarta.validation-api</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
 
         <dependency>
@@ -62,13 +79,19 @@
         </dependency>
 
         <dependency>
-            <groupId>javax.validation</groupId>
-            <artifactId>validation-api</artifactId>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>org.springdoc</groupId>
-            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cz.muni.fi.pa165</groupId>
+            <artifactId>user-client</artifactId>
+            <version>1.0-SNAPSHOT</version>
         </dependency>
 
         <!-- for pagination from JPA without actually using JPA -->
@@ -77,11 +100,9 @@
             <artifactId>spring-data-commons</artifactId>
         </dependency>
 
-        <!-- for testing -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
         </dependency>
     </dependencies>
 
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/config/AppConfig.java b/core/src/main/java/cz/muni/fi/pa165/core/config/AppConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..53c20c692cb6f905a6fbed337c6b4899cb4af90c
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/config/AppConfig.java
@@ -0,0 +1,89 @@
+package cz.muni.fi.pa165.core.config;
+
+import cz.muni.fi.pa165.user.client.Authorities;
+import cz.muni.fi.pa165.user.client.UserServiceInterceptionConfigurer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@Import(UserServiceInterceptionConfigurer.class)
+public class AppConfig {
+
+    @Bean
+    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http.authorizeHttpRequests(x -> x
+                        // swagger:
+                        .requestMatchers("/swagger-ui/**").permitAll()
+                        .requestMatchers("/v3/api-docs/**").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/swagger-ui.html").permitAll()
+
+                        // Manager can read everything
+                        .requestMatchers(HttpMethod.GET, "/api/**").hasAuthority(Authorities.MANAGER)
+
+                        // Manager can create/update/delete flights
+                        .requestMatchers(HttpMethod.POST, "/api/flights").hasAuthority(Authorities.MANAGER)
+                        .requestMatchers(HttpMethod.PUT, "/api/flights/*").hasAuthority(Authorities.MANAGER)
+                        .requestMatchers(HttpMethod.DELETE, "/api/flights/*").hasAuthority(Authorities.MANAGER)
+
+                        // Manager can create/update/delete stewards
+                        .requestMatchers(HttpMethod.POST, "/api/stewards").hasAuthority(Authorities.MANAGER)
+                        .requestMatchers(HttpMethod.PUT, "/api/stewards/*").hasAuthority(Authorities.MANAGER)
+                        .requestMatchers(HttpMethod.DELETE, "/api/stewards/*").hasAuthority(Authorities.MANAGER)
+
+                        // Manager can create/update/delete airplanes
+                        .requestMatchers(HttpMethod.POST, "/api/airplanes").hasAuthority(Authorities.MANAGER)
+                        .requestMatchers(HttpMethod.PUT, "/api/airplanes/*").hasAuthority(Authorities.MANAGER)
+                        .requestMatchers(HttpMethod.DELETE, "/api/airplanes/*").hasAuthority(Authorities.MANAGER)
+
+                        // Manager can assign flights
+                        .requestMatchers("/api/airports/*/departingFlights/*").hasAuthority(Authorities.MANAGER)
+                        .requestMatchers("/api/airports/*/arrivingFlights/*").hasAuthority(Authorities.MANAGER)
+
+                        // Manager can assign stewards
+                        .requestMatchers("/api/stewards/*/flights/*").hasAuthority(Authorities.MANAGER)
+
+                        // Administrator can create/update/delete airplane types
+                        .requestMatchers(HttpMethod.GET, "/api/airplaneTypes").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.GET, "/api/airplaneTypes/*").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.POST, "/api/airplaneTypes").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.PUT, "/api/airplaneTypes/*").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.DELETE, "/api/airplaneTypes/*").hasAuthority(Authorities.ADMINISTRATOR)
+
+                        // Administrator can create/update/delete countries
+                        .requestMatchers(HttpMethod.GET, "/api/countries").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.GET, "/api/countries/*").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.POST, "/api/countries").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.PUT, "/api/countries/*").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.DELETE, "/api/countries/*").hasAuthority(Authorities.ADMINISTRATOR)
+
+                        // Administrator can create/update/delete/assign (do everything) with cities
+                        .requestMatchers( "/api/cities").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers( "/api/cities/**").hasAuthority(Authorities.ADMINISTRATOR)
+
+                        // Administrator read/create/update/delete countries
+                        .requestMatchers(HttpMethod.GET, "/api/countries").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.GET, "/api/countries/*").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.POST, "/api/countries").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.PUT, "/api/countries/*").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.DELETE, "/api/countries/*").hasAuthority(Authorities.ADMINISTRATOR)
+
+                        // Administrator read/create/update/delete airports
+                        .requestMatchers(HttpMethod.GET, "/api/airports").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.GET, "/api/airports/*").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.POST, "/api/airports").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.PUT, "/api/airports/*").hasAuthority(Authorities.ADMINISTRATOR)
+                        .requestMatchers(HttpMethod.DELETE, "/api/airports/*").hasAuthority(Authorities.ADMINISTRATOR)
+
+                        // deny everything else
+                        .anyRequest().denyAll()
+                )
+                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
+        return http.build();
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Airplane.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Airplane.java
new file mode 100644
index 0000000000000000000000000000000000000000..c419d22916c8b2013609145c3c790fad996bbf69
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Airplane.java
@@ -0,0 +1,46 @@
+package cz.muni.fi.pa165.core.data.domain;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Objects;
+
+@Entity(name = "airplanes")
+@Data
+@NoArgsConstructor
+public class Airplane extends DomainEntity {
+
+    @NotNull
+    @NotBlank
+    @Column(unique = true)
+    private String name;
+
+    @NotNull
+    @PositiveOrZero
+    private Integer capacity;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    private AirplaneType type;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof Airplane airplane)) {
+            return false;
+        }
+        return Objects.equals(getId(), airplane.getId()) && Objects.equals(getName(), airplane.getName()) &&
+                Objects.equals(getCapacity(), airplane.getCapacity()) && Objects.equals(getType(), airplane.getType());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getId(), getName(), getCapacity(), getType());
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/AirplaneType.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/AirplaneType.java
new file mode 100644
index 0000000000000000000000000000000000000000..c79756575d1f655f7d4eb22c2074bd82f7e277e9
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/AirplaneType.java
@@ -0,0 +1,45 @@
+package cz.muni.fi.pa165.core.data.domain;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.OneToMany;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Entity(name = "airplane_types")
+@Data
+@NoArgsConstructor
+public class AirplaneType extends DomainEntity {
+
+    @NotNull
+    @NotBlank
+    @Column(unique = true)
+    private String name;
+
+    @OneToMany(mappedBy = "type", cascade = CascadeType.ALL)
+    private List<Airplane> airplanes = new ArrayList<>();
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof AirplaneType airplaneType)) {
+            return false;
+        }
+        return Objects.equals(getName(), airplaneType.getName());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getName());
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Airport.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Airport.java
new file mode 100644
index 0000000000000000000000000000000000000000..68a2fb8e015c0595ed0b1935b5f6d865b9011482
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Airport.java
@@ -0,0 +1,91 @@
+package cz.muni.fi.pa165.core.data.domain;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Entity
+@Table(name = "airports")
+@Data
+@NoArgsConstructor
+public class Airport extends DomainEntity {
+
+    @NotNull
+    @NotBlank
+    @Column(unique = true)
+    private String name;
+
+    @NotNull
+    @Column(unique = true)
+    @Size(min = 3, max = 3)
+    private String code;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "city_id")
+    private City city;
+
+    private double latitude;
+    private double longitude;
+
+    @OneToMany(fetch = FetchType.LAZY, mappedBy = "arrivingAirport")
+    private List<Flight> arrivingFlights = new ArrayList<>();
+
+    @OneToMany(fetch = FetchType.LAZY, mappedBy = "departingAirport")
+    private List<Flight> departingFlights = new ArrayList<>();
+
+    public void addArrivingFlight(Flight arrivingFlight) {
+        arrivingFlights.add(arrivingFlight);
+    }
+
+    public void removeArrivingFlight(Flight arrivingFlight) {
+        arrivingFlights.remove(arrivingFlight);
+    }
+
+    public void addDepartingFlight(Flight departingFlight) {
+        departingFlights.add(departingFlight);
+    }
+
+    public void removeDepartingFlight(Flight departingFlight) {
+        departingFlights.remove(departingFlight);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof Airport airport)) {
+            return false;
+        }
+        return getName().equals(airport.getName())
+                && getCode().equals(airport.getCode());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getName(), getCode());
+    }
+
+    @Override
+    public String toString() {
+        return "Airport{" +
+                "name='" + name + '\'' +
+                ", code='" + code + '\'' +
+                ", city=" + city.getName() +
+                '}';
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/City.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/City.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3cc44dd180df600fcd4c911e9765fbbe0527c90
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/City.java
@@ -0,0 +1,61 @@
+package cz.muni.fi.pa165.core.data.domain;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Entity
+@Table(name = "cities")
+@Data
+@NoArgsConstructor
+public class City extends DomainEntity {
+
+    @NotNull
+    @NotBlank
+    @Column(unique = true)
+    private String name;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "country_id")
+    private Country country;
+
+    @OneToMany(fetch = FetchType.LAZY, mappedBy = "city")
+    private List<Airport> airports = new ArrayList<>();
+
+    public void addAirport(Airport airport) {
+        airports.add(airport);
+    }
+
+    public void removeAirport(Airport airport) {
+        airports.remove(airport);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof City city)) {
+            return false;
+        }
+        return getName().equals(city.getName());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getName());
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Country.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Country.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1d18da489a38a55452c34839ba2408197a6cf98
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Country.java
@@ -0,0 +1,55 @@
+package cz.muni.fi.pa165.core.data.domain;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Entity
+@Table(name = "countries")
+@Data
+@NoArgsConstructor
+public class Country extends DomainEntity {
+
+    @NotNull
+    @NotBlank
+    @Column(unique = true)
+    private String name;
+
+    @OneToMany(fetch = FetchType.LAZY, mappedBy = "country")
+    private List<City> cities = new ArrayList<>();
+
+    public void addCity(City city) {
+        cities.add(city);
+    }
+
+    public void removeCity(City city) {
+        cities.remove(city);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof Country country)) {
+            return false;
+        }
+        return getName().equals(country.getName());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getName());
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Flight.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Flight.java
new file mode 100644
index 0000000000000000000000000000000000000000..541481770baac2d614c83fba99224e8ba44749f8
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Flight.java
@@ -0,0 +1,61 @@
+package cz.muni.fi.pa165.core.data.domain;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.Future;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Objects;
+
+@Entity
+@Table(name = "flights")
+@Data
+@NoArgsConstructor
+public class Flight extends DomainEntity {
+
+    @NotNull
+    @Future
+    private OffsetDateTime departureTime;
+
+    @NotNull
+    @Future
+    private OffsetDateTime arrivalTime;
+
+    @OneToMany(mappedBy = "flight")
+    private List<FlightSteward> flightStewards;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "departing_airport_id")
+    private Airport departingAirport;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "arriving_airport_id")
+    private Airport arrivingAirport;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "airplane_id")
+    private Airplane airplane;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof Flight flight)) {
+            return false;
+        }
+        return Objects.equals(getId(), flight.getId()) &&
+                Objects.equals(getDepartureTime(), flight.getDepartureTime()) &&
+                Objects.equals(getArrivalTime(), flight.getArrivalTime()) &&
+                Objects.equals(getFlightStewards(), flight.getFlightStewards());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getId(), getDepartureTime(), getArrivalTime(), getFlightStewards());
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/FlightSteward.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/FlightSteward.java
new file mode 100644
index 0000000000000000000000000000000000000000..a873d52b1df4e2d50474de5912a1d10bca477a88
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/FlightSteward.java
@@ -0,0 +1,46 @@
+package cz.muni.fi.pa165.core.data.domain;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.Entity;
+import jakarta.persistence.ManyToOne;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+
+import java.util.Objects;
+
+/**
+ * Connection table for flight-steward many-to-many relationship
+ */
+@Entity(name = "flight_steward")
+@Data
+@NoArgsConstructor
+public class FlightSteward extends DomainEntity {
+
+    @ManyToOne
+    @OnDelete(action = OnDeleteAction.CASCADE)
+    private Flight flight;
+
+    @ManyToOne
+    @OnDelete(action = OnDeleteAction.CASCADE)
+    private Steward steward;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof FlightSteward flightSteward)) {
+            return false;
+        }
+        return Objects.equals(getId(), flightSteward.getId()) &&
+                Objects.equals(getFlight(), flightSteward.getFlight()) &&
+                Objects.equals(getSteward(), flightSteward.getSteward());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getId(),  getFlight(), getSteward());
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Steward.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Steward.java
new file mode 100644
index 0000000000000000000000000000000000000000..f742515e9b2badd0991a4973ffaaeab8a7b261f1
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/Steward.java
@@ -0,0 +1,48 @@
+package cz.muni.fi.pa165.core.data.domain;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Objects;
+
+@Entity
+@Table(name = "stewards")
+@Data
+@NoArgsConstructor
+public class Steward extends DomainEntity {
+
+    @NotNull
+    @NotBlank
+    private String firstName;
+
+    @NotNull
+    @NotBlank
+    private String lastName;
+
+    @OneToMany(mappedBy = "steward")
+    private List<FlightSteward> flightStewards;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof Steward steward)) {
+            return false;
+        }
+        return Objects.equals(getId(), steward.getId()) &&
+                Objects.equals(getFirstName(), steward.getFirstName()) &&
+                Objects.equals(getLastName(), steward.getLastName()) &&
+                Objects.equals(getFlightStewards(), steward.getFlightStewards());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getId(), getFirstName(), getLastName(), getFlightStewards());
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/domain/common/DomainEntity.java b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/common/DomainEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..62a5e04a5460b3992c71434aa9c29fbd2016e3bf
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/domain/common/DomainEntity.java
@@ -0,0 +1,16 @@
+package cz.muni.fi.pa165.core.data.domain.common;
+
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.MappedSuperclass;
+import lombok.Data;
+
+@MappedSuperclass
+@Data
+public class DomainEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+}
\ No newline at end of file
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airplane/AirplaneRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airplane/AirplaneRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..83ad96aec601a4d5301f932b6d45c1a508bce9f6
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airplane/AirplaneRepository.java
@@ -0,0 +1,10 @@
+package cz.muni.fi.pa165.core.data.repository.airplane;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface AirplaneRepository extends BaseRepository<Airplane, Long> {
+    
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airplanetype/AirplaneTypeRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airplanetype/AirplaneTypeRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d08c5c653d117ff898701aca9a9102f5c8a35d5
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airplanetype/AirplaneTypeRepository.java
@@ -0,0 +1,13 @@
+package cz.muni.fi.pa165.core.data.repository.airplanetype;
+
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface AirplaneTypeRepository extends BaseRepository<AirplaneType, Long> {
+
+    Optional<AirplaneType> findByName(String name);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airport/AirportRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airport/AirportRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..cfdcef4c8ce1a040c6fa21669cc03d20f62ebaff
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/airport/AirportRepository.java
@@ -0,0 +1,24 @@
+package cz.muni.fi.pa165.core.data.repository.airport;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface AirportRepository extends BaseRepository<Airport, Long> {
+
+    @Query("SELECT a FROM Airport a WHERE a.name = :name")
+    Optional<Airport> findByName(@Param("name") String name);
+
+    @Query("SELECT a FROM Airport a WHERE a.code = :code")
+    Optional<Airport> findByCode(@Param("code") String code);
+
+    @Query("SELECT a FROM Airport a WHERE a.city = :city")
+    List<Airport> findByCity(@Param("city") City city);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/city/CityRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/city/CityRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..5dca32556bc6a8af19e2c4ce8a32107d9c63be6f
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/city/CityRepository.java
@@ -0,0 +1,16 @@
+package cz.muni.fi.pa165.core.data.repository.city;
+
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface CityRepository extends BaseRepository<City, Long> {
+
+    @Query("SELECT c FROM City c WHERE c.name = :name")
+    Optional<City> findByName(@Param("name") String name);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/common/BaseRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/common/BaseRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..59c3038b4e61d54c6808f9470f98060ac597f60c
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/common/BaseRepository.java
@@ -0,0 +1,44 @@
+package cz.muni.fi.pa165.core.data.repository.common;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Optional;
+
+/**
+ * BaseRepository for common CRUD operations
+ *
+ * @param <E> Entity
+ * @param <K> Key
+ * @author martinslovik
+ */
+@Repository
+@Validated
+public interface BaseRepository<E extends DomainEntity, K> extends CrudRepository<E, K> {
+
+    <S extends E> S save(S entity);
+
+    <S extends E> Iterable<S> saveAll(Iterable<S> entities);
+
+    Optional<E> findById(K id);
+
+    boolean existsById(K id);
+
+    Iterable<E> findAll();
+
+    Iterable<E> findAllById(Iterable<K> ids);
+
+    long count();
+
+    void deleteById(K id);
+
+    void delete(E entity);
+
+    void deleteAllById(Iterable<? extends K> ids);
+
+    void deleteAll(Iterable<? extends E> entities);
+
+    void deleteAll();
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/country/CountryRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/country/CountryRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..011541c54631c02a8c9f2bd5af7bc3b41b49eb5f
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/country/CountryRepository.java
@@ -0,0 +1,16 @@
+package cz.muni.fi.pa165.core.data.repository.country;
+
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface CountryRepository extends BaseRepository<Country, Long> {
+
+    @Query("SELECT c FROM Country c WHERE c.name = :name")
+    Optional<Country> findByName(@Param("name") String name);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/flight/FlightRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/flight/FlightRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..66d0f4d27b18077a8a5528a892dc4bce18c1a0e4
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/flight/FlightRepository.java
@@ -0,0 +1,21 @@
+package cz.muni.fi.pa165.core.data.repository.flight;
+
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface FlightRepository extends BaseRepository<Flight, Long> {
+
+    /**
+     * Returns Flight entity with eagerly fetched Stewards
+     * @param id flightId
+     * @return Optional<Flight>
+     */
+    @Query("SELECT f FROM Flight f JOIN FETCH f.flightStewards fs WHERE f.id = :id")
+    Optional<Flight> findByIdWithStewards(@Param("id") Long id);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/flightsteward/FlightStewardRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/flightsteward/FlightStewardRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d9990713401e213b22aaebfb94847bfd72806cb
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/flightsteward/FlightStewardRepository.java
@@ -0,0 +1,15 @@
+package cz.muni.fi.pa165.core.data.repository.flightsteward;
+
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.data.domain.FlightSteward;
+import cz.muni.fi.pa165.core.data.domain.Steward;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface FlightStewardRepository extends BaseRepository<FlightSteward, Long> {
+
+    Optional<FlightSteward> findByStewardAndFlight(Steward steward, Flight flight);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/repository/steward/StewardRepository.java b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/steward/StewardRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..78064e0b0cafba567a1dacff1f3eedf66161f7ce
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/repository/steward/StewardRepository.java
@@ -0,0 +1,16 @@
+package cz.muni.fi.pa165.core.data.repository.steward;
+
+import cz.muni.fi.pa165.core.data.domain.Steward;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface StewardRepository extends BaseRepository<Steward, Long> {
+
+    @Query("SELECT s FROM Steward s JOIN FETCH s.flightStewards fs WHERE s.id = :id")
+    Optional<Steward> findByIdWithFlights(@Param("id") Long id);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/data/seed/DatabaseInitializer.java b/core/src/main/java/cz/muni/fi/pa165/core/data/seed/DatabaseInitializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..be5e643c5d39895f7c9c2bbf341411f966e9f006
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/data/seed/DatabaseInitializer.java
@@ -0,0 +1,174 @@
+package cz.muni.fi.pa165.core.data.seed;
+
+import cz.muni.fi.pa165.core.data.domain.*;
+import cz.muni.fi.pa165.core.data.repository.airplane.AirplaneRepository;
+import cz.muni.fi.pa165.core.data.repository.airplanetype.AirplaneTypeRepository;
+import cz.muni.fi.pa165.core.data.repository.airport.AirportRepository;
+import cz.muni.fi.pa165.core.data.repository.city.CityRepository;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import cz.muni.fi.pa165.core.data.repository.country.CountryRepository;
+import cz.muni.fi.pa165.core.data.repository.flight.FlightRepository;
+import cz.muni.fi.pa165.core.data.repository.flightsteward.FlightStewardRepository;
+import cz.muni.fi.pa165.core.data.repository.steward.StewardRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class DatabaseInitializer {
+
+    private final List<BaseRepository<?, ?>> repositories = new ArrayList<>();
+
+    private final AirplaneRepository airplaneRepository;
+    private final AirplaneTypeRepository airplaneTypeRepository;
+    private final AirportRepository airportRepository;
+    private final CityRepository cityRepository;
+    private final CountryRepository countryRepository;
+    private final FlightRepository flightRepository;
+    private final FlightStewardRepository flightStewardRepository;
+    private final StewardRepository stewardRepository;
+
+    @Autowired
+    public DatabaseInitializer(AirplaneRepository airplaneRepository,
+                               AirplaneTypeRepository airplaneTypeRepository,
+                               AirportRepository airportRepository,
+                               CityRepository cityRepository,
+                               CountryRepository countryRepository,
+                               FlightRepository flightRepository,
+                               FlightStewardRepository flightStewardRepository,
+                               StewardRepository stewardRepository) {
+        this.airplaneRepository = airplaneRepository;
+        this.airplaneTypeRepository = airplaneTypeRepository;
+        this.airportRepository = airportRepository;
+        this.cityRepository = cityRepository;
+        this.countryRepository = countryRepository;
+        this.flightRepository = flightRepository;
+        this.flightStewardRepository = flightStewardRepository;
+        this.stewardRepository = stewardRepository;
+
+        repositories.add(flightRepository);
+        repositories.add(flightStewardRepository);
+        repositories.add(stewardRepository);
+        repositories.add(airportRepository);
+        repositories.add(cityRepository);
+        repositories.add(countryRepository);
+        repositories.add(airplaneTypeRepository);
+        repositories.add(airplaneRepository);
+    }
+
+    public void clear() {
+        repositories.forEach(BaseRepository::deleteAll);
+    }
+
+    public void seed() {
+        clear();
+
+        var boeing777 = createAirplaneType("Boeing 777");
+        airplaneTypeRepository.save(boeing777);
+
+        var freddyTheAirplane = createAirplane("Freddy The Airplane", 100, boeing777);
+        airplaneRepository.save(freddyTheAirplane);
+
+        var flight1 = createFlight(OffsetDateTime.parse("2023-12-22T12:04:04.493908908+01:00"),
+                OffsetDateTime.parse("2023-12-22T12:05:05.493908908+01:00"), freddyTheAirplane);
+        flightRepository.save(flight1);
+
+        var steward1 = createSteward("John", "Doe");
+        stewardRepository.save(steward1);
+
+        var steward2 = createSteward("Jane", "Doe");
+        stewardRepository.save(steward2);
+
+        var flight1Steward1 = assignFlightToSteward(flight1, steward1);
+        flightStewardRepository.save(flight1Steward1);
+
+        var flight1Steward2 = assignFlightToSteward(flight1, steward2);
+        flightStewardRepository.save(flight1Steward2);
+
+        var germany = createCountry("Germany");
+        countryRepository.save(germany);
+
+        var france = createCountry("France");
+        countryRepository.save(france);
+
+        var berlin = createCity("Berlin");
+        berlin.setCountry(germany);
+        cityRepository.save(berlin);
+
+        var paris = createCity("Paris");
+        paris.setCountry(france);
+        cityRepository.save(paris);
+
+        var ber = createAirport("Berlin Brandenburg Airport", "BER", 52.3666666667, 13.5033333333);
+        ber.setCity(berlin);
+        airportRepository.save(ber);
+        flight1.setDepartingAirport(ber);
+        flightRepository.save(flight1);
+
+        var cdg = createAirport("Paris Charles de Gaulle airport", "CDG", 49.0083899664, 2.53844117956);
+        cdg.setCity(paris);
+        airportRepository.save(cdg);
+        flight1.setArrivingAirport(cdg);
+        flightRepository.save(flight1);
+    }
+
+    private Country createCountry(String name) {
+        var country = new Country();
+        country.setName(name);
+        return country;
+    }
+
+    private City createCity(String name) {
+        var city = new City();
+        city.setName(name);
+        return city;
+    }
+
+    private AirplaneType createAirplaneType(String name) {
+        var airplaneType = new AirplaneType();
+        airplaneType.setName(name);
+        return airplaneType;
+    }
+
+    private Airplane createAirplane(String name, Integer capacity, AirplaneType airplaneType) {
+        var airplane = new Airplane();
+        airplane.setName(name);
+        airplane.setCapacity(capacity);
+        airplane.setType(airplaneType);
+        return airplane;
+    }
+
+    private Flight createFlight(OffsetDateTime departureTime, OffsetDateTime arrivalTime, Airplane airplane) {
+        var flight = new Flight();
+        flight.setDepartureTime(departureTime);
+        flight.setArrivalTime(arrivalTime);
+        flight.setAirplane(airplane);
+        return flight;
+    }
+
+    private Steward createSteward(String firstName, String lastName) {
+        var steward = new Steward();
+        steward.setFirstName(firstName);
+        steward.setLastName(lastName);
+        return steward;
+    }
+
+    private FlightSteward assignFlightToSteward(Flight flight, Steward steward) {
+        var flightSteward = new FlightSteward();
+        flightSteward.setFlight(flight);
+        flightSteward.setSteward(steward);
+        return flightSteward;
+    }
+
+    private Airport createAirport(String name, String code, double latitude, double longitude) {
+        var airport = new Airport();
+        airport.setName(name);
+        airport.setCode(code);
+        airport.setLatitude(latitude);
+        airport.setLongitude(longitude);
+        return airport;
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ConstraintValidationAdvice.java b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ConstraintValidationAdvice.java
new file mode 100644
index 0000000000000000000000000000000000000000..87a4a57a437866a4909d202a552db0f039ff9a12
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ConstraintValidationAdvice.java
@@ -0,0 +1,28 @@
+package cz.muni.fi.pa165.core.exceptions;
+
+import cz.muni.fi.pa165.core.model.ErrorMessage;
+import jakarta.validation.ConstraintViolationException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import java.time.OffsetDateTime;
+
+@ControllerAdvice
+public class ConstraintValidationAdvice {
+
+    @ExceptionHandler(ConstraintViolationException.class)
+    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
+    ResponseEntity<ErrorMessage> handleConstraintValidationException(ConstraintViolationException ex) {
+        var errorMessage = new ErrorMessage();
+        errorMessage.setTimestamp(OffsetDateTime.now());
+        errorMessage.setStatus(HttpStatus.UNPROCESSABLE_ENTITY.value());
+        errorMessage.setError(HttpStatus.UNPROCESSABLE_ENTITY.getReasonPhrase());
+        errorMessage.setMessage(ex.getMessage());
+        errorMessage.setPath(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
+        return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/exceptions/DataIntegrityValidationAdvice.java b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/DataIntegrityValidationAdvice.java
new file mode 100644
index 0000000000000000000000000000000000000000..08385ecbbdd6d23550f0f841494ee49d6d77d989
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/DataIntegrityValidationAdvice.java
@@ -0,0 +1,32 @@
+package cz.muni.fi.pa165.core.exceptions;
+
+import cz.muni.fi.pa165.core.model.ErrorMessage;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import java.time.OffsetDateTime;
+
+@ControllerAdvice
+public class DataIntegrityValidationAdvice {
+
+    @ExceptionHandler(DataIntegrityViolationException.class)
+    @ResponseStatus(HttpStatus.CONFLICT)
+    ResponseEntity<ErrorMessage> handleDataIntegrityViolationException(DataIntegrityViolationException ex) {
+        var errorMessage = new ErrorMessage();
+        errorMessage.setTimestamp(OffsetDateTime.now());
+        errorMessage.setStatus(HttpStatus.CONFLICT.value());
+        errorMessage.setError(HttpStatus.CONFLICT.getReasonPhrase());
+        /*
+            Custom exception message for security reasons to prevent showing failed insert SQL statement
+            to the Client and therefore revealing our DB tables.
+        */
+        errorMessage.setMessage("fields must be unique.");
+        errorMessage.setPath(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
+        return new ResponseEntity<>(errorMessage, HttpStatus.CONFLICT);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/exceptions/InvalidGpsLocationAdvice.java b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/InvalidGpsLocationAdvice.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c80af99f70582c7f7a0b82e567685d64cbc6c6b
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/InvalidGpsLocationAdvice.java
@@ -0,0 +1,27 @@
+package cz.muni.fi.pa165.core.exceptions;
+
+import cz.muni.fi.pa165.core.model.ErrorMessage;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import java.time.OffsetDateTime;
+
+@ControllerAdvice
+public class InvalidGpsLocationAdvice {
+
+    @ExceptionHandler(InvalidGpsLocationException.class)
+    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
+    ResponseEntity<ErrorMessage> handleInvalidGpsLocationException(InvalidGpsLocationException ex) {
+        var errorMessage = new ErrorMessage();
+        errorMessage.setTimestamp(OffsetDateTime.now());
+        errorMessage.setStatus(HttpStatus.UNPROCESSABLE_ENTITY.value());
+        errorMessage.setError(HttpStatus.UNPROCESSABLE_ENTITY.getReasonPhrase());
+        errorMessage.setMessage(ex.getMessage());
+        errorMessage.setPath(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
+        return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/exceptions/InvalidGpsLocationException.java b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/InvalidGpsLocationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..302b42e483c6af1c7cb57e60ee74bc1d460f1b43
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/InvalidGpsLocationException.java
@@ -0,0 +1,11 @@
+package cz.muni.fi.pa165.core.exceptions;
+
+/**
+ * Thrown in case of invalid GPS location, e.g. (-120.3, 42.42).
+ */
+public class InvalidGpsLocationException extends RuntimeException {
+
+    public InvalidGpsLocationException(String message) {
+        super(message);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ResourceNotFoundAdvice.java b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ResourceNotFoundAdvice.java
new file mode 100644
index 0000000000000000000000000000000000000000..52199bdecfb9ebae72dd69ecc13a7b6b572dabef
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ResourceNotFoundAdvice.java
@@ -0,0 +1,27 @@
+package cz.muni.fi.pa165.core.exceptions;
+
+import cz.muni.fi.pa165.core.model.ErrorMessage;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import java.time.OffsetDateTime;
+
+@ControllerAdvice
+public class ResourceNotFoundAdvice {
+
+    @ExceptionHandler(ResourceNotFoundException.class)
+    @ResponseStatus(HttpStatus.NOT_FOUND)
+    ResponseEntity<ErrorMessage> handleResourceNotFoundException(ResourceNotFoundException ex) {
+        ErrorMessage errorMessage = new ErrorMessage();
+        errorMessage.setTimestamp(OffsetDateTime.now());
+        errorMessage.setStatus(HttpStatus.NOT_FOUND.value());
+        errorMessage.setError(HttpStatus.NOT_FOUND.getReasonPhrase());
+        errorMessage.setMessage(ex.getMessage());
+        errorMessage.setPath(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
+        return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ResourceNotFoundException.java b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ResourceNotFoundException.java
new file mode 100644
index 0000000000000000000000000000000000000000..a226a1b647cf845048c93afea4b37785aca05dc5
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/exceptions/ResourceNotFoundException.java
@@ -0,0 +1,25 @@
+package cz.muni.fi.pa165.core.exceptions;
+
+public class ResourceNotFoundException extends RuntimeException {
+
+    public ResourceNotFoundException() {
+        super("Resource Not Found");
+    }
+
+    public ResourceNotFoundException(String message) {
+        super(message);
+    }
+
+    public ResourceNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ResourceNotFoundException(Throwable cause) {
+        super(cause);
+    }
+
+    public ResourceNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}
+
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/airplane/AirplaneFacade.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/airplane/AirplaneFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..db3abbef5f43662f21c8f4bcedda69d339555660
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/airplane/AirplaneFacade.java
@@ -0,0 +1,26 @@
+package cz.muni.fi.pa165.core.facade.airplane;
+
+import cz.muni.fi.pa165.core.model.AirplaneDto;
+import cz.muni.fi.pa165.core.model.NewAirplaneDtoRequest;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @param <K> Key
+ * @author martinslovik
+ */
+public interface AirplaneFacade<K> {
+
+    AirplaneDto save(NewAirplaneDtoRequest newAirplaneDtoRequest);
+
+    Optional<AirplaneDto> findById(K id);
+
+    List<AirplaneDto> findAll();
+
+    void deleteById(K id);
+
+    void deleteAll();
+
+    AirplaneDto update(Long id, NewAirplaneDtoRequest newAirplaneDtoRequest);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/airplane/AirplaneFacadeImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/airplane/AirplaneFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..3cef2e7d3bc2821caeb8080a483d9d44f1c61f2e
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/airplane/AirplaneFacadeImpl.java
@@ -0,0 +1,73 @@
+package cz.muni.fi.pa165.core.facade.airplane;
+
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.mapper.AirplaneMapper;
+import cz.muni.fi.pa165.core.model.AirplaneDto;
+import cz.muni.fi.pa165.core.model.NewAirplaneDtoRequest;
+import cz.muni.fi.pa165.core.service.airplane.AirplaneService;
+import cz.muni.fi.pa165.core.service.airplanetype.AirplaneTypeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class AirplaneFacadeImpl implements AirplaneFacade<Long> {
+
+    private final AirplaneService airplaneService;
+    private final AirplaneTypeService airplaneTypeService;
+    private final AirplaneMapper airplaneMapper;
+
+    @Autowired
+    public AirplaneFacadeImpl(AirplaneService airplaneService, AirplaneTypeService airplaneTypeService, AirplaneMapper airplaneMapper) {
+        this.airplaneService = airplaneService;
+        this.airplaneTypeService = airplaneTypeService;
+        this.airplaneMapper = airplaneMapper;
+    }
+
+    @Override
+    public AirplaneDto save(NewAirplaneDtoRequest newAirplaneDtoRequest) {
+        var airplaneType = airplaneTypeService.findById(newAirplaneDtoRequest.getTypeId())
+                .orElseThrow(ResourceNotFoundException::new);
+        var entityToSave = airplaneMapper.toEntityFromNewRequest(newAirplaneDtoRequest);
+        entityToSave.setType(airplaneType);
+        var savedEntity = airplaneService.save(entityToSave);
+        return airplaneMapper.toDto(savedEntity);
+    }
+
+    @Override
+    public Optional<AirplaneDto> findById(Long id) {
+        var foundEntity = airplaneService.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        return Optional.ofNullable(airplaneMapper.toDto(foundEntity));
+    }
+
+    @Override
+    public List<AirplaneDto> findAll() {
+        var entities = airplaneService.findAll();
+        return entities.stream()
+                .map(airplaneMapper::toDto)
+                .toList();
+    }
+
+    @Override
+    public void deleteById(Long id) {
+        airplaneService.deleteById(id);
+    }
+
+    @Override
+    public void deleteAll() {
+        airplaneService.deleteAll();
+    }
+
+    @Override
+    public AirplaneDto update(Long id, NewAirplaneDtoRequest newAirplaneDtoRequest) {
+        var newAirplaneEntity = airplaneMapper.toEntityFromNewRequest(newAirplaneDtoRequest);
+
+        var savedEntity = airplaneService.update(id, newAirplaneEntity);
+
+        return airplaneMapper.toDto(savedEntity);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/airplanetype/AirplaneTypeFacade.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/airplanetype/AirplaneTypeFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f01c4dcd2a3caa0ecf5dff20b9558faadaab375
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/airplanetype/AirplaneTypeFacade.java
@@ -0,0 +1,26 @@
+package cz.muni.fi.pa165.core.facade.airplanetype;
+
+import cz.muni.fi.pa165.core.model.AirplaneTypeDto;
+import cz.muni.fi.pa165.core.model.NewAirplaneTypeDtoRequest;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @param <K> Key
+ * @author martinslovik
+ */
+public interface AirplaneTypeFacade<K> {
+
+    AirplaneTypeDto save(NewAirplaneTypeDtoRequest newAirplaneTypeDtoRequest);
+
+    Optional<AirplaneTypeDto> findById(K id);
+
+    List<AirplaneTypeDto> findAll();
+
+    void deleteById(K id);
+
+    void deleteAll();
+
+    AirplaneTypeDto update(K id, NewAirplaneTypeDtoRequest newAirplaneTypeDtoRequest);
+}
\ No newline at end of file
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/airplanetype/AirplaneTypeFacadeImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/airplanetype/AirplaneTypeFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e835e562d283fa6a02281d7ce96c4ead7e38ca1
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/airplanetype/AirplaneTypeFacadeImpl.java
@@ -0,0 +1,66 @@
+package cz.muni.fi.pa165.core.facade.airplanetype;
+
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.mapper.AirplaneTypeMapper;
+import cz.muni.fi.pa165.core.model.AirplaneTypeDto;
+import cz.muni.fi.pa165.core.model.NewAirplaneTypeDtoRequest;
+import cz.muni.fi.pa165.core.service.airplanetype.AirplaneTypeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class AirplaneTypeFacadeImpl implements AirplaneTypeFacade<Long> {
+
+    private final AirplaneTypeService airplaneTypeService;
+    private final AirplaneTypeMapper airplaneTypeMapper;
+
+    @Autowired
+    public AirplaneTypeFacadeImpl(AirplaneTypeService airplaneTypeService, AirplaneTypeMapper airplaneTypeMapper) {
+        this.airplaneTypeService = airplaneTypeService;
+        this.airplaneTypeMapper = airplaneTypeMapper;
+    }
+
+    @Override
+    public AirplaneTypeDto save(NewAirplaneTypeDtoRequest newAirplaneTypeDtoRequest) {
+        var entityToSave = airplaneTypeMapper.toEntityFromNewRequest(newAirplaneTypeDtoRequest);
+        var savedEntity = airplaneTypeService.save(entityToSave);
+        return airplaneTypeMapper.toDto(savedEntity);
+    }
+
+    @Override
+    public Optional<AirplaneTypeDto> findById(Long id) {
+        var foundEntity = airplaneTypeService.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        return Optional.ofNullable(airplaneTypeMapper.toDto(foundEntity));
+    }
+
+    @Override
+    public List<AirplaneTypeDto> findAll() {
+        var entities = airplaneTypeService.findAll();
+        return entities.stream()
+                .map(airplaneTypeMapper::toDto)
+                .toList();
+    }
+
+    @Override
+    public void deleteById(Long id) {
+        airplaneTypeService.deleteById(id);
+    }
+
+    @Override
+    public void deleteAll() {
+        airplaneTypeService.deleteAll();
+    }
+
+    @Override
+    public AirplaneTypeDto update(Long id, NewAirplaneTypeDtoRequest newAirplaneTypeDtoRequest) {
+        var newAirplaneTypeEntity = airplaneTypeMapper.toEntityFromNewRequest(newAirplaneTypeDtoRequest);
+
+        return airplaneTypeMapper.toDto(airplaneTypeService.update(id, newAirplaneTypeEntity));
+    }
+
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/airport/AirportFacade.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/airport/AirportFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..71a6ddd8d63d29dae04020e63fc517525b0e9eab
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/airport/AirportFacade.java
@@ -0,0 +1,43 @@
+package cz.muni.fi.pa165.core.facade.airport;
+
+import cz.muni.fi.pa165.core.model.AirportDto;
+import cz.muni.fi.pa165.core.model.NewAirportDtoRequest;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @param <K> Key
+ */
+public interface AirportFacade<K> {
+
+    AirportDto save(NewAirportDtoRequest newAirportDtoRequest);
+
+    Optional<AirportDto> findById(K id);
+
+    List<AirportDto> findAll();
+
+    void deleteById(K id);
+
+    void deleteAll();
+
+    AirportDto update(K id, NewAirportDtoRequest newAirportDtoRequest);
+
+    Optional<AirportDto> findByName(String name);
+
+    Optional<AirportDto> findByCode(String code);
+
+    List<AirportDto> findByCity(K cityId);
+
+    AirportDto addArrivingFlightAssignment(K airportId, K flightId);
+
+    AirportDto deleteArrivingFlightAssignment(K airportId, K flightId);
+
+    AirportDto addDepartingFlightAssignment(K airportId, K flightId);
+
+    AirportDto deleteDepartingFlightAssignment(K airportId, K flightId);
+
+    AirportDto addCityAssignment(K airportId, K cityId);
+
+    AirportDto deleteCityAssignment(K airportId, K cityId);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/airport/AirportFacadeImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/airport/AirportFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..323ce754ef0d38905b02a3045e4c14bc3b6cd5e5
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/airport/AirportFacadeImpl.java
@@ -0,0 +1,204 @@
+package cz.muni.fi.pa165.core.facade.airport;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.mapper.AirportMapper;
+import cz.muni.fi.pa165.core.model.AirportDto;
+import cz.muni.fi.pa165.core.model.NewAirportDtoRequest;
+import cz.muni.fi.pa165.core.service.airport.AirportService;
+import cz.muni.fi.pa165.core.service.city.CityService;
+import cz.muni.fi.pa165.core.service.flight.FlightService;
+import cz.muni.fi.pa165.core.validation.GpsLocationValidator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class AirportFacadeImpl implements AirportFacade<Long> {
+
+    private final AirportService airportService;
+    private final FlightService flightService;
+    private final CityService cityService;
+    private final AirportMapper airportMapper;
+    private final GpsLocationValidator gpsLocationValidator;
+
+    @Autowired
+    public AirportFacadeImpl(AirportService airportService, FlightService flightService, CityService cityService,
+                             AirportMapper airportMapper, GpsLocationValidator gpsLocationValidator) {
+        this.airportService = airportService;
+        this.flightService = flightService;
+        this.cityService = cityService;
+        this.airportMapper = airportMapper;
+        this.gpsLocationValidator = gpsLocationValidator;
+    }
+
+    @Override
+    public AirportDto save(NewAirportDtoRequest newAirportDtoRequest) {
+        gpsLocationValidator.validate(newAirportDtoRequest.getLocation().getLatitude(),
+                newAirportDtoRequest.getLocation().getLongitude());
+
+        Airport entityToSave = airportMapper.toEntityFromNewRequest(newAirportDtoRequest);
+        Airport savedEntity = airportService.save(entityToSave);
+        return airportMapper.toDto(savedEntity);
+    }
+
+    @Override
+    public Optional<AirportDto> findById(Long id) {
+        Airport foundEntity = airportService.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+        return Optional.of(airportMapper.toDto(foundEntity));
+    }
+
+    @Override
+    public List<AirportDto> findAll() {
+        List<Airport> foundEntities = airportService.findAll();
+        return foundEntities.stream()
+                .map(airportMapper::toDto)
+                .toList();
+    }
+
+    @Override
+    public void deleteById(Long id) {
+        airportService.deleteById(id);
+    }
+
+    @Override
+    public void deleteAll() {
+        airportService.deleteAll();
+    }
+
+    @Override
+    public AirportDto update(Long id, NewAirportDtoRequest newAirportDtoRequest) {
+        gpsLocationValidator.validate(newAirportDtoRequest.getLocation().getLatitude(),
+                newAirportDtoRequest.getLocation().getLongitude());
+
+        if (!airportService.existsById(id)) {
+            throw new ResourceNotFoundException();
+        }
+        Airport newEntity = airportMapper.toEntityFromNewRequest(newAirportDtoRequest);
+        Airport updatedEntity = airportService.update(id, newEntity);
+        return airportMapper.toDto(updatedEntity);
+    }
+
+    @Override
+    public Optional<AirportDto> findByName(String name) {
+        Optional<Airport> foundEntity = airportService.findByName(name);
+        return foundEntity.map(airportMapper::toDto);
+    }
+
+    @Override
+    public Optional<AirportDto> findByCode(String code) {
+        Optional<Airport> foundEntity = airportService.findByCode(code);
+        return foundEntity.map(airportMapper::toDto);
+    }
+
+    @Override
+    public List<AirportDto> findByCity(Long cityId) {
+        City city = cityService.findById(cityId)
+                .orElseThrow(ResourceNotFoundException::new);
+        List<Airport> foundAirports = airportService.findByCity(city);
+        return foundAirports.stream()
+                .map(airportMapper::toDto)
+                .toList();
+    }
+
+    @Override
+    public AirportDto addArrivingFlightAssignment(Long airportId, Long flightId) {
+        Airport airport = airportService.findById(airportId)
+                .orElseThrow(ResourceNotFoundException::new);
+        Flight flight = flightService.findById(flightId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        airport.addArrivingFlight(flight);
+        flight.setArrivingAirport(airport);
+
+        Airport updatedAirport = airportService.update(airportId, airport);
+        flightService.update(flightId, flight);
+
+        return airportMapper.toDto(updatedAirport);
+    }
+
+    @Override
+    public AirportDto deleteArrivingFlightAssignment(Long airportId, Long flightId) {
+        Airport airport = airportService.findById(airportId)
+                .orElseThrow(ResourceNotFoundException::new);
+        Flight flight = flightService.findById(flightId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        airport.removeArrivingFlight(flight);
+        flight.setArrivingAirport(null);
+
+        Airport updatedAirport = airportService.update(airportId, airport);
+        flightService.update(flightId, flight);
+
+        return airportMapper.toDto(updatedAirport);
+    }
+
+    @Override
+    public AirportDto addDepartingFlightAssignment(Long airportId, Long flightId) {
+        Airport airport = airportService.findById(airportId)
+                .orElseThrow(ResourceNotFoundException::new);
+        Flight flight = flightService.findById(flightId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        airport.addDepartingFlight(flight);
+        flight.setDepartingAirport(airport);
+
+        Airport updatedAirport = airportService.update(airportId, airport);
+        flightService.update(flightId, flight);
+
+        return airportMapper.toDto(updatedAirport);
+    }
+
+    @Override
+    public AirportDto deleteDepartingFlightAssignment(Long airportId, Long flightId) {
+        Airport airport = airportService.findById(airportId)
+                .orElseThrow(ResourceNotFoundException::new);
+        Flight flight = flightService.findById(flightId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        airport.removeDepartingFlight(flight);
+        flight.setDepartingAirport(null);
+
+        Airport updatedAirport = airportService.update(airportId, airport);
+        flightService.update(flightId, flight);
+
+        return airportMapper.toDto(updatedAirport);
+    }
+
+    @Override
+    public AirportDto addCityAssignment(Long airportId, Long cityId) {
+        Airport airport = airportService.findById(airportId)
+                .orElseThrow(ResourceNotFoundException::new);
+        City city = cityService.findById(cityId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        airport.setCity(city);
+        city.addAirport(airport);
+
+        Airport updatedAirport = airportService.update(airportId, airport);
+        cityService.update(cityId, city);
+
+        return airportMapper.toDto(updatedAirport);
+    }
+
+    @Override
+    public AirportDto deleteCityAssignment(Long airportId, Long cityId) {
+        Airport airport = airportService.findById(airportId)
+                .orElseThrow(ResourceNotFoundException::new);
+        City city = cityService.findById(cityId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        airport.setCity(null);
+        city.removeAirport(airport);
+
+        Airport updatedAirport = airportService.update(airportId, airport);
+        cityService.update(cityId, city);
+
+        return airportMapper.toDto(updatedAirport);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/city/CityFacade.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/city/CityFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..865417e40e024b3c8b863bb8b7f4d1e57c7bbde8
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/city/CityFacade.java
@@ -0,0 +1,31 @@
+package cz.muni.fi.pa165.core.facade.city;
+
+import cz.muni.fi.pa165.core.model.CityDto;
+import cz.muni.fi.pa165.core.model.NewCityDtoRequest;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @param <K> Key
+ */
+public interface CityFacade<K> {
+
+    CityDto save(NewCityDtoRequest newCityDtoRequest);
+
+    Optional<CityDto> findById(K id);
+
+    List<CityDto> findAll();
+
+    void deleteById(K id);
+
+    void deleteAll();
+
+    CityDto update(K id, NewCityDtoRequest newCityDtoRequest);
+
+    Optional<CityDto> findByName(String name);
+
+    CityDto addCountryAssignment(K cityId, K countryId);
+
+    void deleteCountryAssignment(K cityId, K countryId);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/city/CityFacadeImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/city/CityFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..353de7d11c8d4e5d7d384c3dc888d8a622bc9790
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/city/CityFacadeImpl.java
@@ -0,0 +1,112 @@
+package cz.muni.fi.pa165.core.facade.city;
+
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.mapper.CityMapper;
+import cz.muni.fi.pa165.core.model.CityDto;
+import cz.muni.fi.pa165.core.model.NewCityDtoRequest;
+import cz.muni.fi.pa165.core.service.city.CityService;
+import cz.muni.fi.pa165.core.service.country.CountryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class CityFacadeImpl implements CityFacade<Long> {
+
+    private final CityService cityService;
+    private final CountryService countryService;
+    private final CityMapper cityMapper;
+
+    @Autowired
+    public CityFacadeImpl(CityService cityService, CountryService countryService, CityMapper cityMapper) {
+        this.cityService = cityService;
+        this.countryService = countryService;
+        this.cityMapper = cityMapper;
+    }
+
+    @Override
+    public CityDto save(NewCityDtoRequest newCityDtoRequest) {
+        City entityToSave = cityMapper.toEntityFromNewRequest(newCityDtoRequest);
+        City savedEntity = cityService.save(entityToSave);
+        return cityMapper.toDto(savedEntity);
+    }
+
+    @Override
+    public Optional<CityDto> findById(Long id) {
+        City foundEntity = cityService.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+        return Optional.of(cityMapper.toDto(foundEntity));
+    }
+
+    @Override
+    public List<CityDto> findAll() {
+        List<City> foundEntities = cityService.findAll();
+        return foundEntities.stream()
+                .map(cityMapper::toDto)
+                .toList();
+    }
+
+    @Override
+    public void deleteById(Long id) {
+        if (!cityService.existsById(id)) {
+            throw new ResourceNotFoundException();
+        }
+        cityService.deleteById(id);
+    }
+
+    @Override
+    public void deleteAll() {
+        cityService.deleteAll();
+    }
+
+    @Override
+    public CityDto update(Long id, NewCityDtoRequest newCityDtoRequest) {
+        if (!cityService.existsById(id)) {
+            throw new ResourceNotFoundException();
+        }
+
+        City newEntity = cityMapper.toEntityFromNewRequest(newCityDtoRequest);
+        City updatedEntity = cityService.update(id, newEntity);
+        return cityMapper.toDto(updatedEntity);
+    }
+
+    @Override
+    public Optional<CityDto> findByName(String name) {
+        Optional<City> foundEntity = cityService.findByName(name);
+        return foundEntity.map(cityMapper::toDto);
+    }
+
+    @Override
+    public CityDto addCountryAssignment(Long cityId, Long countryId) {
+        City city = cityService.findById(cityId)
+                .orElseThrow(ResourceNotFoundException::new);
+        Country country = countryService.findById(countryId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        country.addCity(city);
+        city.setCountry(country);
+
+        countryService.update(countryId, country);
+        City updatedCity = cityService.update(cityId, city);
+
+        return cityMapper.toDto(updatedCity);
+    }
+
+    @Override
+    public void deleteCountryAssignment(Long cityId, Long countryId) {
+        City city = cityService.findById(cityId)
+                .orElseThrow(ResourceNotFoundException::new);
+        Country country = countryService.findById(countryId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        country.removeCity(city);
+        city.setCountry(null);
+
+        countryService.update(countryId, country);
+        cityService.update(cityId, city);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/country/CountryFacade.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/country/CountryFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..801cdbac840dee4bf5acbe266f56d4b32ee2cc25
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/country/CountryFacade.java
@@ -0,0 +1,27 @@
+package cz.muni.fi.pa165.core.facade.country;
+
+import cz.muni.fi.pa165.core.model.CountryDto;
+import cz.muni.fi.pa165.core.model.NewCountryDtoRequest;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @param <K> Key
+ */
+public interface CountryFacade<K> {
+
+    CountryDto save(NewCountryDtoRequest newCountryDtoRequest);
+
+    Optional<CountryDto> findById(K id);
+
+    List<CountryDto> findAll();
+
+    void deleteById(K id);
+
+    void deleteAll();
+
+    CountryDto update(K id, NewCountryDtoRequest newCountryDtoRequest);
+
+    Optional<CountryDto> findByName(String name);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/country/CountryFacadeImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/country/CountryFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5b957635b149e39537a0b042c69711105744c0e
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/country/CountryFacadeImpl.java
@@ -0,0 +1,78 @@
+package cz.muni.fi.pa165.core.facade.country;
+
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.mapper.CountryMapper;
+import cz.muni.fi.pa165.core.model.CountryDto;
+import cz.muni.fi.pa165.core.model.NewCountryDtoRequest;
+import cz.muni.fi.pa165.core.service.country.CountryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class CountryFacadeImpl implements CountryFacade<Long> {
+
+    private final CountryService countryService;
+    private final CountryMapper countryMapper;
+
+    @Autowired
+    public CountryFacadeImpl(CountryService countryService, CountryMapper countryMapper) {
+        this.countryService = countryService;
+        this.countryMapper = countryMapper;
+    }
+
+    @Override
+    public CountryDto save(NewCountryDtoRequest newCountryDtoRequest) {
+        Country entityToSave = countryMapper.toEntityFromNewRequest(newCountryDtoRequest);
+        Country savedEntity = countryService.save(entityToSave);
+        return countryMapper.toDto(savedEntity);
+    }
+
+    @Override
+    public Optional<CountryDto> findById(Long id) {
+        Country foundEntity = countryService.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+        return Optional.of(countryMapper.toDto(foundEntity));
+    }
+
+    @Override
+    public List<CountryDto> findAll() {
+        List<Country> foundEntities = countryService.findAll();
+        return foundEntities.stream()
+                .map(countryMapper::toDto)
+                .toList();
+    }
+
+    @Override
+    public void deleteById(Long id) {
+        if (!countryService.existsById(id)) {
+            throw new ResourceNotFoundException();
+        }
+        countryService.deleteById(id);
+    }
+
+    @Override
+    public void deleteAll() {
+        countryService.deleteAll();
+    }
+
+    @Override
+    public CountryDto update(Long id, NewCountryDtoRequest newCountryDtoRequest) {
+        if (!countryService.existsById(id)) {
+            throw new ResourceNotFoundException();
+        }
+
+        Country newEntity = countryMapper.toEntityFromNewRequest(newCountryDtoRequest);
+        Country updatedEntity = countryService.update(id, newEntity);
+        return countryMapper.toDto(updatedEntity);
+    }
+
+    @Override
+    public Optional<CountryDto> findByName(String name) {
+        Optional<Country> foundEntity = countryService.findByName(name);
+        return foundEntity.map(countryMapper::toDto);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/flight/FlightFacade.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/flight/FlightFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..acee09cfad187e4bf66f2f2641fdc5d8ed031cce
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/flight/FlightFacade.java
@@ -0,0 +1,24 @@
+package cz.muni.fi.pa165.core.facade.flight;
+
+import cz.muni.fi.pa165.core.model.FlightDto;
+import cz.muni.fi.pa165.core.model.NewFlightDtoRequest;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @param <K> Key
+ * @author martinslovik
+ */
+public interface FlightFacade<K> {
+
+    FlightDto save(NewFlightDtoRequest newFlightDtoRequest);
+
+    Optional<FlightDto> findById(K id);
+
+    List<FlightDto> findAll();
+
+    void deleteById(K id);
+
+    FlightDto update(Long id, NewFlightDtoRequest newFlightDtoRequest);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/flight/FlightFacadeImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/flight/FlightFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..75fc0190d553cc19af4517290aabaa5333f83947
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/flight/FlightFacadeImpl.java
@@ -0,0 +1,79 @@
+package cz.muni.fi.pa165.core.facade.flight;
+
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.mapper.FlightMapper;
+import cz.muni.fi.pa165.core.model.FlightDto;
+import cz.muni.fi.pa165.core.model.NewFlightDtoRequest;
+import cz.muni.fi.pa165.core.service.airplane.AirplaneService;
+import cz.muni.fi.pa165.core.service.flight.FlightService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class FlightFacadeImpl implements FlightFacade<Long> {
+
+    private final FlightService flightService;
+    private final AirplaneService airplaneService;
+    private final FlightMapper flightMapper;
+
+    @Autowired
+    public FlightFacadeImpl(FlightService flightService, AirplaneService airplaneService, FlightMapper flightMapper) {
+        this.flightService = flightService;
+        this.airplaneService = airplaneService;
+        this.flightMapper = flightMapper;
+    }
+
+    @Override
+    public FlightDto save(NewFlightDtoRequest newFlightDtoRequest) {
+        var airplane = airplaneService.findById(newFlightDtoRequest.getAirplaneId())
+                .orElseThrow(ResourceNotFoundException::new);
+        var entityToSave = flightMapper.toEntityFromNewRequest(newFlightDtoRequest);
+        entityToSave.setAirplane(airplane);
+        var savedEntity = flightService.save(entityToSave);
+        return flightMapper.toDto(savedEntity);
+    }
+
+    @Override
+    public Optional<FlightDto> findById(Long id) {
+        var foundFlightEntity = flightService.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        var flightDto = flightMapper.toDto(foundFlightEntity);
+
+        return Optional.of(flightDto);
+    }
+
+    @Override
+    public List<FlightDto> findAll() {
+        var foundFlights = flightService.findAll();
+
+        return foundFlights
+                .stream()
+                .map(flightMapper::toDto)
+                .toList();
+    }
+
+    @Override
+    public void deleteById(Long id) {
+        if (!flightService.existsById(id)) {
+            throw new ResourceNotFoundException();
+
+        }
+        flightService.deleteById(id);
+    }
+
+    @Override
+    public FlightDto update(Long id, NewFlightDtoRequest newFlightDtoRequest) {
+        if (!flightService.existsById(id)) {
+            throw new ResourceNotFoundException();
+
+        }
+        var newFlightEntity = flightMapper.toEntityFromNewRequest(newFlightDtoRequest);
+        var updatedFlightEntity = flightService.update(id, newFlightEntity);
+
+        return flightMapper.toDto(updatedFlightEntity);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/steward/StewardFacade.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/steward/StewardFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e2ab914ef529b3c0138b964d8e662c813adae8b
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/steward/StewardFacade.java
@@ -0,0 +1,28 @@
+package cz.muni.fi.pa165.core.facade.steward;
+
+import cz.muni.fi.pa165.core.model.NewStewardDtoRequest;
+import cz.muni.fi.pa165.core.model.StewardDto;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @param <K> Key
+ * @author martinslovik
+ */
+public interface StewardFacade<K> {
+
+    StewardDto save(NewStewardDtoRequest newStewardDtoRequest);
+
+    Optional<StewardDto> findById(K id);
+
+    List<StewardDto> findAll();
+
+    void deleteById(K id);
+
+    StewardDto update(Long id, NewStewardDtoRequest newStewardDtoRequest);
+
+    StewardDto assignStewardFlight(Long stewardId, Long flightId);
+
+    void deleteStewardFlightsAssignment(Long stewardId, Long flightId);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/facade/steward/StewardFacadeImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/facade/steward/StewardFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..5cceefd2efa770ef4d17a325d00e35b4d33c9d1e
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/facade/steward/StewardFacadeImpl.java
@@ -0,0 +1,104 @@
+package cz.muni.fi.pa165.core.facade.steward;
+
+import cz.muni.fi.pa165.core.data.domain.FlightSteward;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.mapper.StewardMapper;
+import cz.muni.fi.pa165.core.model.NewStewardDtoRequest;
+import cz.muni.fi.pa165.core.model.StewardDto;
+import cz.muni.fi.pa165.core.service.flight.FlightService;
+import cz.muni.fi.pa165.core.service.steward.StewardService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class StewardFacadeImpl implements StewardFacade<Long> {
+
+    private final StewardService stewardService;
+    private final FlightService flightService;
+    private final StewardMapper stewardMapper;
+
+
+    @Autowired
+    public StewardFacadeImpl(StewardService stewardService, FlightService flightService, StewardMapper stewardMapper) {
+        this.stewardService = stewardService;
+        this.flightService = flightService;
+        this.stewardMapper = stewardMapper;
+    }
+
+    @Override
+    public StewardDto save(NewStewardDtoRequest newStewardDtoRequest) {
+        var entityToSave = stewardMapper.toEntityFromNewRequest(newStewardDtoRequest);
+        var savedEntity = stewardService.save(entityToSave);
+        return stewardMapper.toDto(savedEntity);
+    }
+
+    @Override
+    public Optional<StewardDto> findById(Long id) {
+        var foundStewardEntity = stewardService.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        var stewardDto = stewardMapper.toDto(foundStewardEntity);
+
+        return Optional.of(stewardDto);
+    }
+
+    @Override
+    public List<StewardDto> findAll() {
+        var foundStewards = stewardService.findAll();
+
+        return foundStewards
+                .stream()
+                .map(stewardMapper::toDto)
+                .toList();
+    }
+
+    @Override
+    public void deleteById(Long id) {
+        if (!stewardService.existsById(id)) {
+            throw new ResourceNotFoundException();
+        }
+        stewardService.deleteById(id);
+    }
+
+    @Override
+    public StewardDto update(Long id, NewStewardDtoRequest newStewardDtoRequest) {
+        if (!stewardService.existsById(id)) {
+            throw new ResourceNotFoundException();
+        }
+        var newStewardEntity = stewardMapper.toEntityFromNewRequest(newStewardDtoRequest);
+        var updatedStewardEntity = stewardService.update(id, newStewardEntity);
+
+        return stewardMapper.toDto(updatedStewardEntity);
+    }
+
+    @Override
+    public StewardDto assignStewardFlight(Long stewardId, Long flightId) {
+        var stewardEntity = stewardService.findByIdWithFlights(stewardId)
+                .orElse(stewardService.findById(stewardId)
+                        .orElseThrow(ResourceNotFoundException::new));
+
+        var flightEntity = flightService.findById(flightId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        var flightSteward = new FlightSteward();
+        flightSteward.setSteward(stewardEntity);
+        flightSteward.setFlight(flightEntity);
+        stewardService.saveFlightStewards(flightSteward);
+
+        return stewardMapper.toDto(stewardEntity);
+    }
+
+    @Override
+    public void deleteStewardFlightsAssignment(Long stewardId, Long flightId) {
+        var stewardEntity = stewardService.findById(stewardId)
+                        .orElseThrow(ResourceNotFoundException::new);
+
+        var flightEntity = flightService.findById(flightId)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        stewardService.deleteFlightStewards(stewardEntity, flightEntity);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirplaneMapper.java b/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirplaneMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd9336c7a122fc555aea3c7156e6f13b16561889
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirplaneMapper.java
@@ -0,0 +1,27 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.model.AirplaneDto;
+import cz.muni.fi.pa165.core.model.NewAirplaneDtoRequest;
+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 AirplaneMapper {
+
+    @Autowired
+    private AirplaneTypeMapper airplaneTypeMapper;
+
+    public abstract Airplane toEntityFromNewRequest(NewAirplaneDtoRequest newAirplaneDtoRequest);
+
+    @Mapping(target = "typeId", source = "type")
+    public abstract AirplaneDto toDto(Airplane airplane);
+
+    protected Long getAirplanesTypeId(AirplaneType airplaneType) {
+        return airplaneTypeMapper.getAirplanesTypeId(airplaneType);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirplaneTypeMapper.java b/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirplaneTypeMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..47f858281c3ba1d5a7e4d4fbbac4ebd79a9c265b
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirplaneTypeMapper.java
@@ -0,0 +1,30 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.model.AirplaneTypeDto;
+import cz.muni.fi.pa165.core.model.NewAirplaneTypeDtoRequest;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring")
+public interface AirplaneTypeMapper {
+
+    AirplaneType toEntityFromNewRequest(NewAirplaneTypeDtoRequest newAirplaneTypeDtoRequest);
+
+    @Mapping(target = "airplanesIds", source = "airplanes")
+    AirplaneTypeDto toDto(AirplaneType airplaneType);
+
+    default Long getAirplanesTypeId(AirplaneType airplaneType) {
+        return airplaneType == null ? 0L : airplaneType.getId();
+    }
+
+    default List<Long> mapAirplaneTypesToIds(List<Airplane> airplanes) {
+        return airplanes
+                .stream()
+                .map(Airplane::getId)
+                .toList();
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirportMapper.java b/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirportMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8764aa894456a704113fa002eee63944ef8be7e
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/mapper/AirportMapper.java
@@ -0,0 +1,39 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.model.AirportDto;
+import cz.muni.fi.pa165.core.model.NewAirportDtoRequest;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+@Mapper(componentModel = "spring")
+public abstract class AirportMapper {
+
+    @Mapping(target = "latitude", source = "location.latitude")
+    @Mapping(target = "longitude", source = "location.longitude")
+    public abstract Airport toEntityFromNewRequest(NewAirportDtoRequest newAirportDtoRequest);
+
+    @Mapping(target = "cityId", source = "city")
+    @Mapping(target = "arrivingFlightsIds", source = "arrivingFlights")
+    @Mapping(target = "departingFlightsIds", source = "departingFlights")
+    @Mapping(target = "location.latitude", source = "latitude")
+    @Mapping(target = "location.longitude", source = "longitude")
+    public abstract AirportDto toDto(Airport airport);
+
+    protected Long getCityId(City city) {
+        return city == null ? 0L : city.getId();
+    }
+
+    protected List<Long> mapFlightsToIds(List<Flight> flights) {
+        return flights
+                .stream()
+                .map(Flight::getId)
+                .toList();
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/mapper/CityMapper.java b/core/src/main/java/cz/muni/fi/pa165/core/mapper/CityMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffbcf492c017212163e474a55505d172385be102
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/mapper/CityMapper.java
@@ -0,0 +1,32 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.model.CityDto;
+import cz.muni.fi.pa165.core.model.NewCityDtoRequest;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring")
+public interface CityMapper {
+
+    City toEntityFromNewRequest(NewCityDtoRequest newCityDtoRequest);
+
+    @Mapping(target = "countryId", source = "country")
+    @Mapping(target = "airportsIds", source = "airports")
+    CityDto toDto(City city);
+
+    default Long getCountryId(Country country) {
+        return country == null ? 0L : country.getId();
+    }
+
+    default List<Long> mapAirportsToIds(List<Airport> airports) {
+        return airports
+                .stream()
+                .map(Airport::getId)
+                .toList();
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/mapper/CountryMapper.java b/core/src/main/java/cz/muni/fi/pa165/core/mapper/CountryMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..e270fd36e79538609c9bf90fab9470b8c2824ae0
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/mapper/CountryMapper.java
@@ -0,0 +1,26 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.model.CountryDto;
+import cz.muni.fi.pa165.core.model.NewCountryDtoRequest;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring")
+public interface CountryMapper {
+
+    Country toEntityFromNewRequest(NewCountryDtoRequest newCountryDtoRequest);
+
+    @Mapping(target = "citiesIds", source = "cities")
+    CountryDto toDto(Country country);
+
+    default List<Long> mapCitiesToIds(List<City> cities) {
+        return cities
+                .stream()
+                .map(City::getId)
+                .toList();
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/mapper/FlightMapper.java b/core/src/main/java/cz/muni/fi/pa165/core/mapper/FlightMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..f5e82c7e5b6b36cd157d9aa4f525c183a7265f72
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/mapper/FlightMapper.java
@@ -0,0 +1,44 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.data.domain.FlightSteward;
+import cz.muni.fi.pa165.core.model.FlightDto;
+import cz.muni.fi.pa165.core.model.NewFlightDtoRequest;
+import org.mapstruct.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Mapper(componentModel = "spring")
+public interface FlightMapper {
+
+    Flight toEntityFromNewRequest(NewFlightDtoRequest newFlightDtoRequest);
+
+    default List<Long> toStewardIdList(List<FlightSteward> flightStewards) {
+        if (flightStewards != null) {
+            return flightStewards.stream()
+                    .map(fs -> fs.getSteward().getId())
+                    .toList();
+        }
+        return new ArrayList<>();
+    }
+
+    default Long toAirplaneId(Airplane airplane) {
+        return airplane == null ? 0L : airplane.getId();
+    }
+
+    default Long toAirportId(Airport airport) {
+        return airport == null ? 0L : airport.getId();
+    }
+
+    @Mapping(target = "assignedStewardIds", expression = "java(toStewardIdList(flight.getFlightStewards()))")
+    @Mapping(target = "airplaneId", expression = "java(toAirplaneId(flight.getAirplane()))")
+    @Mapping(target = "departureAirportId", expression = "java(toAirportId(flight.getDepartingAirport()))")
+    @Mapping(target = "arrivalAirportId", expression = "java(toAirportId(flight.getArrivingAirport()))")
+    FlightDto toDto(Flight flight);
+
+    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+    Flight partialUpdate(FlightDto flightDto, @MappingTarget Flight flight);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/mapper/StewardMapper.java b/core/src/main/java/cz/muni/fi/pa165/core/mapper/StewardMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..42e1ca3c98fba5a1c3f7455af0a031b275fe9a0d
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/mapper/StewardMapper.java
@@ -0,0 +1,29 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.FlightSteward;
+import cz.muni.fi.pa165.core.data.domain.Steward;
+import cz.muni.fi.pa165.core.model.NewStewardDtoRequest;
+import cz.muni.fi.pa165.core.model.StewardDto;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Mapper(componentModel = "spring")
+public interface StewardMapper {
+
+    Steward toEntityFromNewRequest(NewStewardDtoRequest newStewardDtoRequest);
+
+    default List<Long> toFlightIdList(List<FlightSteward> flightStewards) {
+        if (flightStewards != null) {
+            return flightStewards.stream()
+                    .map(fs -> fs.getFlight().getId())
+                    .toList();
+        }
+        return new ArrayList<>();
+    }
+
+    @Mapping(target = "assignedFlightIds", expression = "java(toFlightIdList(steward.getFlightStewards()))")
+    StewardDto toDto(Steward steward);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/rest/AirplaneController.java b/core/src/main/java/cz/muni/fi/pa165/core/rest/AirplaneController.java
index 143e24e85c08429f18855594d6c14d3004f69c7c..f7ef9a5ec76d5fb2095033d6c4b357f7024ef1b2 100644
--- a/core/src/main/java/cz/muni/fi/pa165/core/rest/AirplaneController.java
+++ b/core/src/main/java/cz/muni/fi/pa165/core/rest/AirplaneController.java
@@ -2,8 +2,10 @@ package cz.muni.fi.pa165.core.rest;
 
 import cz.muni.fi.pa165.core.api.AirplaneApi;
 import cz.muni.fi.pa165.core.api.AirplaneApiDelegate;
+import cz.muni.fi.pa165.core.facade.airplane.AirplaneFacade;
 import cz.muni.fi.pa165.core.model.AirplaneDto;
 import cz.muni.fi.pa165.core.model.NewAirplaneDtoRequest;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -12,20 +14,14 @@ import java.util.List;
 @RestController
 public class AirplaneController implements AirplaneApiDelegate {
 
-    /**
-     * POST /api/airplanes/{airplaneId}/airplaneTypes/{airplaneTypeId} : Assign airplane type to a airplane.
-     *
-     * @param airplaneId     (required)
-     * @param airplaneTypeId (required)
-     * @return OK (status code 201)
-     * or Input data not correct (status code 400)
-     * @see AirplaneApi#assignAirplaneType
-     */
-    @Override
-    public ResponseEntity<AirplaneDto> assignAirplaneType(Long airplaneId, Long airplaneTypeId) {
-        return AirplaneApiDelegate.super.assignAirplaneType(airplaneId, airplaneTypeId);
+    private final AirplaneFacade<Long> airplaneFacade;
+
+    @Autowired
+    public AirplaneController(AirplaneFacade<Long> airplaneFacade) {
+        this.airplaneFacade = airplaneFacade;
     }
 
+
     /**
      * POST /api/airplanes : Create a new airplane.
      * Creates a new airplane and returns it as a response.
@@ -37,7 +33,7 @@ public class AirplaneController implements AirplaneApiDelegate {
      */
     @Override
     public ResponseEntity<AirplaneDto> createAirplane(NewAirplaneDtoRequest newAirplaneDtoRequest) {
-        return AirplaneApiDelegate.super.createAirplane(newAirplaneDtoRequest);
+        return ResponseEntity.ok(airplaneFacade.save(newAirplaneDtoRequest));
     }
 
     /**
@@ -50,22 +46,10 @@ public class AirplaneController implements AirplaneApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteAirplane(Long id) {
-        return AirplaneApiDelegate.super.deleteAirplane(id);
+        airplaneFacade.deleteById(id);
+        return null;
     }
 
-    /**
-     * DELETE /api/airplanes/{airplaneId}/airplaneTypes/{airplaneTypeId} : Delete assignment of airplane type to an airplane.
-     *
-     * @param airplaneId     (required)
-     * @param airplaneTypeId (required)
-     * @return Deleted (status code 204)
-     * or Not Found (status code 404)
-     * @see AirplaneApi#deleteAirplaneTypeAssignment
-     */
-    @Override
-    public ResponseEntity<Void> deleteAirplaneTypeAssignment(Long airplaneId, Long airplaneTypeId) {
-        return AirplaneApiDelegate.super.deleteAirplaneTypeAssignment(airplaneId, airplaneTypeId);
-    }
 
     /**
      * GET /api/airplanes/{id} : Get airplane by id.
@@ -77,7 +61,7 @@ public class AirplaneController implements AirplaneApiDelegate {
      */
     @Override
     public ResponseEntity<AirplaneDto> getAirplaneById(Long id) {
-        return AirplaneApiDelegate.super.getAirplaneById(id);
+        return ResponseEntity.of(airplaneFacade.findById(id));
     }
 
     /**
@@ -89,7 +73,7 @@ public class AirplaneController implements AirplaneApiDelegate {
      */
     @Override
     public ResponseEntity<List<AirplaneDto>> getAllAirplanes() {
-        return AirplaneApiDelegate.super.getAllAirplanes();
+        return ResponseEntity.ok(airplaneFacade.findAll());
     }
 
     /**
@@ -97,27 +81,14 @@ public class AirplaneController implements AirplaneApiDelegate {
      * Updates a airplane by id and returns it as a response.
      *
      * @param id          (required)
-     * @param airplaneDto (required)
+     * @param newAirplaneDtoRequest (required)
      * @return OK (status code 200)
      * or Input data not correct (status code 400)
-     * @see AirplaneApi#updateAirplane
-     */
-    @Override
-    public ResponseEntity<AirplaneDto> updateAirplane(Long id, AirplaneDto airplaneDto) {
-        return AirplaneApiDelegate.super.updateAirplane(id, airplaneDto);
-    }
-
-    /**
-     * PUT /api/airplanes/{airplaneId}/airplaneTypes/{airplaneTypeId} : Update assignment of airplane type to an airplane.
-     *
-     * @param airplaneId     (required)
-     * @param airplaneTypeId (required)
-     * @return OK (status code 200)
      * or Input data not correct (status code 400)
-     * @see AirplaneApi#updateAirplaneTypeAssignment
+     * @see AirplaneApi#updateAirplane
      */
     @Override
-    public ResponseEntity<AirplaneDto> updateAirplaneTypeAssignment(Long airplaneId, Long airplaneTypeId) {
-        return AirplaneApiDelegate.super.updateAirplaneTypeAssignment(airplaneId, airplaneTypeId);
+    public ResponseEntity<AirplaneDto> updateAirplane(Long id, NewAirplaneDtoRequest newAirplaneDtoRequest) {
+        return ResponseEntity.ok(airplaneFacade.update(id, newAirplaneDtoRequest));
     }
 }
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/rest/AirplaneTypeController.java b/core/src/main/java/cz/muni/fi/pa165/core/rest/AirplaneTypeController.java
index 86419eaf19d9b606fc2a198fd9cac46bf29def19..43302987e78238ab402da20f4162ad42b205fb71 100644
--- a/core/src/main/java/cz/muni/fi/pa165/core/rest/AirplaneTypeController.java
+++ b/core/src/main/java/cz/muni/fi/pa165/core/rest/AirplaneTypeController.java
@@ -2,8 +2,10 @@ package cz.muni.fi.pa165.core.rest;
 
 import cz.muni.fi.pa165.core.api.AirplaneTypeApi;
 import cz.muni.fi.pa165.core.api.AirplaneTypeApiDelegate;
+import cz.muni.fi.pa165.core.facade.airplanetype.AirplaneTypeFacade;
 import cz.muni.fi.pa165.core.model.AirplaneTypeDto;
 import cz.muni.fi.pa165.core.model.NewAirplaneTypeDtoRequest;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -12,6 +14,13 @@ import java.util.List;
 @RestController
 public class AirplaneTypeController implements AirplaneTypeApiDelegate {
 
+    private final AirplaneTypeFacade<Long> airplaneTypeFacade;
+
+    @Autowired
+    public AirplaneTypeController(AirplaneTypeFacade<Long> airplaneTypeFacade) {
+        this.airplaneTypeFacade = airplaneTypeFacade;
+    }
+
     /**
      * POST /api/airplaneTypes : Create a new airplane type.
      * Creates a new airplane type and returns it as a response.
@@ -23,7 +32,7 @@ public class AirplaneTypeController implements AirplaneTypeApiDelegate {
      */
     @Override
     public ResponseEntity<AirplaneTypeDto> createAirplaneType(NewAirplaneTypeDtoRequest newAirplaneTypeDtoRequest) {
-        return AirplaneTypeApiDelegate.super.createAirplaneType(newAirplaneTypeDtoRequest);
+        return ResponseEntity.ok(airplaneTypeFacade.save(newAirplaneTypeDtoRequest));
     }
 
     /**
@@ -36,7 +45,8 @@ public class AirplaneTypeController implements AirplaneTypeApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteAirplaneType(Long id) {
-        return AirplaneTypeApiDelegate.super.deleteAirplaneType(id);
+        airplaneTypeFacade.deleteById(id);
+        return null;
     }
 
     /**
@@ -49,7 +59,7 @@ public class AirplaneTypeController implements AirplaneTypeApiDelegate {
      */
     @Override
     public ResponseEntity<AirplaneTypeDto> getAirplaneTypeById(Long id) {
-        return AirplaneTypeApiDelegate.super.getAirplaneTypeById(id);
+        return ResponseEntity.of(airplaneTypeFacade.findById(id));
     }
 
     /**
@@ -61,7 +71,7 @@ public class AirplaneTypeController implements AirplaneTypeApiDelegate {
      */
     @Override
     public ResponseEntity<List<AirplaneTypeDto>> getAllAirplaneTypes() {
-        return AirplaneTypeApiDelegate.super.getAllAirplaneTypes();
+        return ResponseEntity.ok(airplaneTypeFacade.findAll());
     }
 
     /**
@@ -69,13 +79,13 @@ public class AirplaneTypeController implements AirplaneTypeApiDelegate {
      * Updates a airplane type by id and returns it as a response.
      *
      * @param id              (required)
-     * @param airplaneTypeDto (required)
+     * @param newAirplaneTypeDtoRequest (required)
      * @return OK (status code 200)
      * or Input data not correct (status code 400)
      * @see AirplaneTypeApi#updateAirplaneType
      */
     @Override
-    public ResponseEntity<AirplaneTypeDto> updateAirplaneType(Long id, AirplaneTypeDto airplaneTypeDto) {
-        return AirplaneTypeApiDelegate.super.updateAirplaneType(id, airplaneTypeDto);
+    public ResponseEntity<AirplaneTypeDto> updateAirplaneType(Long id, NewAirplaneTypeDtoRequest newAirplaneTypeDtoRequest) {
+        return ResponseEntity.ok(airplaneTypeFacade.update(id, newAirplaneTypeDtoRequest));
     }
 }
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/rest/AirportController.java b/core/src/main/java/cz/muni/fi/pa165/core/rest/AirportController.java
index 46747877632af17c6da12c45eac404c1427c216b..93242ce6df90282fd3ccbc2d10537e2691d27736 100644
--- a/core/src/main/java/cz/muni/fi/pa165/core/rest/AirportController.java
+++ b/core/src/main/java/cz/muni/fi/pa165/core/rest/AirportController.java
@@ -2,8 +2,10 @@ package cz.muni.fi.pa165.core.rest;
 
 import cz.muni.fi.pa165.core.api.AirportApi;
 import cz.muni.fi.pa165.core.api.AirportApiDelegate;
+import cz.muni.fi.pa165.core.facade.airport.AirportFacade;
 import cz.muni.fi.pa165.core.model.AirportDto;
 import cz.muni.fi.pa165.core.model.NewAirportDtoRequest;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -12,6 +14,13 @@ import java.util.List;
 @RestController
 public class AirportController implements AirportApiDelegate {
 
+    private final AirportFacade<Long> airportFacade;
+
+    @Autowired
+    public AirportController(AirportFacade<Long> airportFacade) {
+        this.airportFacade = airportFacade;
+    }
+
     /**
      * POST /api/airports/{airportId}/arrivingFlights/{arrivingFlightId} : Assign arriving flight to an airport.
      *
@@ -23,7 +32,7 @@ public class AirportController implements AirportApiDelegate {
      */
     @Override
     public ResponseEntity<AirportDto> assignArrivingFlight(Long airportId, Long arrivingFlightId) {
-        return AirportApiDelegate.super.assignArrivingFlight(airportId, arrivingFlightId);
+        return ResponseEntity.ok(airportFacade.addArrivingFlightAssignment(airportId, arrivingFlightId));
     }
 
     /**
@@ -37,7 +46,21 @@ public class AirportController implements AirportApiDelegate {
      */
     @Override
     public ResponseEntity<AirportDto> assignDepartingFlight(Long airportId, Long departingFlightId) {
-        return AirportApiDelegate.super.assignDepartingFlight(airportId, departingFlightId);
+        return ResponseEntity.ok(airportFacade.addDepartingFlightAssignment(airportId, departingFlightId));
+    }
+
+    /**
+     * POST /api/airports/{airportId}/city/{cityId} : Assign the city to an airport.
+     *
+     * @param airportId  (required)
+     * @param cityId  (required)
+     * @return OK (status code 201)
+     *         or Input data not correct (status code 400)
+     * @see AirportApi#assignCity
+     */
+    @Override
+    public ResponseEntity<AirportDto> assignCity(Long airportId, Long cityId) {
+        return ResponseEntity.ok(airportFacade.addCityAssignment(airportId, cityId));
     }
 
     /**
@@ -51,7 +74,7 @@ public class AirportController implements AirportApiDelegate {
      */
     @Override
     public ResponseEntity<AirportDto> createAirport(NewAirportDtoRequest newAirportDtoRequest) {
-        return AirportApiDelegate.super.createAirport(newAirportDtoRequest);
+        return ResponseEntity.ok(airportFacade.save(newAirportDtoRequest));
     }
 
     /**
@@ -64,7 +87,8 @@ public class AirportController implements AirportApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteAirport(Long id) {
-        return AirportApiDelegate.super.deleteAirport(id);
+        airportFacade.deleteById(id);
+        return null;
     }
 
     /**
@@ -78,7 +102,8 @@ public class AirportController implements AirportApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteArrivingFlightAssignment(Long airportId, Long arrivingFlightId) {
-        return AirportApiDelegate.super.deleteArrivingFlightAssignment(airportId, arrivingFlightId);
+        airportFacade.deleteArrivingFlightAssignment(airportId, arrivingFlightId);
+        return null;
     }
 
     /**
@@ -92,7 +117,23 @@ public class AirportController implements AirportApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteDepartingFlightAssignment(Long airportId, Long departingFlightId) {
-        return AirportApiDelegate.super.deleteDepartingFlightAssignment(airportId, departingFlightId);
+        airportFacade.deleteDepartingFlightAssignment(airportId, departingFlightId);
+        return null;
+    }
+
+    /**
+     * DELETE /api/airports/{airportId}/city/{cityId} : Delete assignment of the city to an airport.
+     *
+     * @param airportId  (required)
+     * @param cityId  (required)
+     * @return Deleted (status code 204)
+     *         or Not Found (status code 404)
+     * @see AirportApi#deleteCityAssignment
+     */
+    @Override
+    public ResponseEntity<Void> deleteCityAssignment(Long airportId, Long cityId) {
+        airportFacade.deleteCityAssignment(airportId, cityId);
+        return null;
     }
 
     /**
@@ -105,7 +146,7 @@ public class AirportController implements AirportApiDelegate {
      */
     @Override
     public ResponseEntity<AirportDto> getAirportById(Long id) {
-        return AirportApiDelegate.super.getAirportById(id);
+        return ResponseEntity.of(airportFacade.findById(id));
     }
 
     /**
@@ -117,49 +158,21 @@ public class AirportController implements AirportApiDelegate {
      */
     @Override
     public ResponseEntity<List<AirportDto>> getAllAirports() {
-        return AirportApiDelegate.super.getAllAirports();
+        return ResponseEntity.ok(airportFacade.findAll());
     }
 
     /**
-     * PUT /api/airports/{id} : Update city by id.
+     * PUT /api/airports/{id} : Update airport by id.
      * Updates a airport by id and returns it as a response.
      *
-     * @param id         (required)
-     * @param airportDto (required)
+     * @param id  (required)
+     * @param newAirportDtoRequest  (required)
      * @return OK (status code 200)
-     * or Input data not correct (status code 400)
+     *         or Input data not correct (status code 400)
      * @see AirportApi#updateAirport
      */
     @Override
-    public ResponseEntity<AirportDto> updateAirport(Long id, AirportDto airportDto) {
-        return AirportApiDelegate.super.updateAirport(id, airportDto);
-    }
-
-    /**
-     * PUT /api/airports/{airportId}/arrivingFlights/{arrivingFlightId} : Update assignment of arriving flight to an airport.
-     *
-     * @param airportId        (required)
-     * @param arrivingFlightId (required)
-     * @return OK (status code 200)
-     * or Input data not correct (status code 400)
-     * @see AirportApi#updateArrivingFlightAssignment
-     */
-    @Override
-    public ResponseEntity<AirportDto> updateArrivingFlightAssignment(Long airportId, Long arrivingFlightId) {
-        return AirportApiDelegate.super.updateArrivingFlightAssignment(airportId, arrivingFlightId);
-    }
-
-    /**
-     * PUT /api/airports/{airportId}/departingFlights/{departingFlightId} : Update assignment of departing flight to an airport.
-     *
-     * @param airportId         (required)
-     * @param departingFlightId (required)
-     * @return OK (status code 200)
-     * or Input data not correct (status code 400)
-     * @see AirportApi#updateDepartingFlightAssignment
-     */
-    @Override
-    public ResponseEntity<AirportDto> updateDepartingFlightAssignment(Long airportId, Long departingFlightId) {
-        return AirportApiDelegate.super.updateDepartingFlightAssignment(airportId, departingFlightId);
+    public ResponseEntity<AirportDto> updateAirport(Long id, NewAirportDtoRequest newAirportDtoRequest) {
+        return ResponseEntity.ok(airportFacade.update(id, newAirportDtoRequest));
     }
 }
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/rest/CityController.java b/core/src/main/java/cz/muni/fi/pa165/core/rest/CityController.java
index 2fd210bdb20c5de3d418eb2ca8618883173f6e09..806a0ad7fb6ba603a4b8830ea635f451771ed95a 100644
--- a/core/src/main/java/cz/muni/fi/pa165/core/rest/CityController.java
+++ b/core/src/main/java/cz/muni/fi/pa165/core/rest/CityController.java
@@ -2,14 +2,28 @@ package cz.muni.fi.pa165.core.rest;
 
 import cz.muni.fi.pa165.core.api.CityApi;
 import cz.muni.fi.pa165.core.api.CityApiDelegate;
+import cz.muni.fi.pa165.core.facade.city.CityFacade;
+import cz.muni.fi.pa165.core.facade.country.CountryFacade;
 import cz.muni.fi.pa165.core.model.CityDto;
 import cz.muni.fi.pa165.core.model.NewCityDtoRequest;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 @RestController
 public class CityController implements CityApiDelegate {
 
+    private final CityFacade<Long> cityFacade;
+    private final CountryFacade<Long> countryFacade;
+
+    @Autowired
+    public CityController(CityFacade<Long> cityFacade, CountryFacade<Long> countryFacade) {
+        this.cityFacade = cityFacade;
+        this.countryFacade = countryFacade;
+    }
+
     /**
      * POST /api/cities/{cityId}/countries/{countryId} : Assign country to a city.
      *
@@ -21,7 +35,7 @@ public class CityController implements CityApiDelegate {
      */
     @Override
     public ResponseEntity<CityDto> assignCountry(Long cityId, Long countryId) {
-        return CityApiDelegate.super.assignCountry(cityId, countryId);
+        return ResponseEntity.ok(cityFacade.addCountryAssignment(cityId, countryId));
     }
 
     /**
@@ -35,7 +49,7 @@ public class CityController implements CityApiDelegate {
      */
     @Override
     public ResponseEntity<CityDto> createCity(NewCityDtoRequest newCityDtoRequest) {
-        return CityApiDelegate.super.createCity(newCityDtoRequest);
+        return ResponseEntity.ok(cityFacade.save(newCityDtoRequest));
     }
 
     /**
@@ -48,7 +62,8 @@ public class CityController implements CityApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteCity(Long id) {
-        return CityApiDelegate.super.deleteCity(id);
+        cityFacade.deleteById(id);
+        return null;
     }
 
     /**
@@ -62,7 +77,8 @@ public class CityController implements CityApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteCountryAssignment(Long cityId, Long countryId) {
-        return CityApiDelegate.super.deleteCountryAssignment(cityId, countryId);
+        cityFacade.deleteCountryAssignment(cityId, countryId);
+        return null;
     }
 
     /**
@@ -75,35 +91,33 @@ public class CityController implements CityApiDelegate {
      */
     @Override
     public ResponseEntity<CityDto> getCityById(Long id) {
-        return CityApiDelegate.super.getCityById(id);
+        return ResponseEntity.of(cityFacade.findById(id));
     }
 
     /**
-     * PUT /api/cities/{id} : Update city by id.
-     * Updates a city by id and returns it as a response.
+     * GET /api/cities : Get all cities.
+     * Returns an array of objects representing cities.
      *
-     * @param id      (required)
-     * @param cityDto (required)
      * @return OK (status code 200)
-     * or Input data not correct (status code 400)
-     * @see CityApi#updateCity
+     * @see CityApi#getAllCities
      */
     @Override
-    public ResponseEntity<CityDto> updateCity(Long id, CityDto cityDto) {
-        return CityApiDelegate.super.updateCity(id, cityDto);
+    public ResponseEntity<List<CityDto>> getAllCities() {
+        return ResponseEntity.ok(cityFacade.findAll());
     }
 
     /**
-     * PUT /api/cities/{cityId}/countries/{countryId} : Update assignment of country to a city.
+     * PUT /api/cities/{id} : Update city by id.
+     * Updates a city by id and returns it as a response.
      *
-     * @param cityId    (required)
-     * @param countryId (required)
+     * @param id  (required)
+     * @param newCityDtoRequest  (required)
      * @return OK (status code 200)
-     * or Input data not correct (status code 400)
-     * @see CityApi#updateCountryAssignment
+     *         or Input data not correct (status code 400)
+     * @see CityApi#updateCity
      */
     @Override
-    public ResponseEntity<CityDto> updateCountryAssignment(Long cityId, Long countryId) {
-        return CityApiDelegate.super.updateCountryAssignment(cityId, countryId);
+    public ResponseEntity<CityDto> updateCity(Long id, NewCityDtoRequest newCityDtoRequest) {
+        return ResponseEntity.ok(cityFacade.update(id, newCityDtoRequest));
     }
 }
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/rest/CountryController.java b/core/src/main/java/cz/muni/fi/pa165/core/rest/CountryController.java
index c2c0a698c97fc5984083f20258272b82809fcf6e..f9d76971ff4d5b3501b67d97dbdc16d3986caf19 100644
--- a/core/src/main/java/cz/muni/fi/pa165/core/rest/CountryController.java
+++ b/core/src/main/java/cz/muni/fi/pa165/core/rest/CountryController.java
@@ -2,9 +2,10 @@ package cz.muni.fi.pa165.core.rest;
 
 import cz.muni.fi.pa165.core.api.CountryApi;
 import cz.muni.fi.pa165.core.api.CountryApiDelegate;
+import cz.muni.fi.pa165.core.facade.country.CountryFacade;
 import cz.muni.fi.pa165.core.model.CountryDto;
 import cz.muni.fi.pa165.core.model.NewCountryDtoRequest;
-import org.springframework.http.HttpStatus;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -13,6 +14,13 @@ import java.util.List;
 @RestController
 public class CountryController implements CountryApiDelegate {
 
+    private final CountryFacade<Long> countryFacade;
+
+    @Autowired
+    public CountryController(CountryFacade<Long> countryFacade) {
+        this.countryFacade = countryFacade;
+    }
+
     /**
      * POST /api/countries : Create a new country.
      * Creates a new country and returns it as a response.
@@ -24,11 +32,7 @@ public class CountryController implements CountryApiDelegate {
      */
     @Override
     public ResponseEntity<CountryDto> createCountry(NewCountryDtoRequest newCountryDtoRequest) {
-        // Sample implementation just for the purpose of testing Rest endpoint
-        var countryDto = new CountryDto()
-                .id(42L)
-                .name("Uganda");
-        return new ResponseEntity<>(countryDto, HttpStatus.CREATED);
+        return ResponseEntity.ok(countryFacade.save(newCountryDtoRequest));
     }
 
     /**
@@ -41,7 +45,8 @@ public class CountryController implements CountryApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteCountry(Long id) {
-        return CountryApiDelegate.super.deleteCountry(id);
+        countryFacade.deleteById(id);
+        return null;
     }
 
     /**
@@ -53,8 +58,7 @@ public class CountryController implements CountryApiDelegate {
      */
     @Override
     public ResponseEntity<List<CountryDto>> getAllCountries() {
-        // Sample implementation just for the purpose of testing Rest endpoint
-        return CountryApiDelegate.super.getAllCountries();
+        return ResponseEntity.ok(countryFacade.findAll());
     }
 
     /**
@@ -67,24 +71,21 @@ public class CountryController implements CountryApiDelegate {
      */
     @Override
     public ResponseEntity<CountryDto> getCountryById(Long id) {
-        var countryDto = new CountryDto()
-                .id(4242L)
-                .name("Fraaaance");
-        return ResponseEntity.ok(countryDto);
+        return ResponseEntity.of(countryFacade.findById(id));
     }
 
     /**
      * PUT /api/countries/{id} : Update country by id.
      * Updates a country by id and returns it as a response.
      *
-     * @param id         (required)
-     * @param countryDto (required)
+     * @param id  (required)
+     * @param newCountryDtoRequest  (required)
      * @return OK (status code 200)
-     * or Input data not correct (status code 400)
+     *         or Input data not correct (status code 400)
      * @see CountryApi#updateCountry
      */
     @Override
-    public ResponseEntity<CountryDto> updateCountry(Long id, CountryDto countryDto) {
-        return CountryApiDelegate.super.updateCountry(id, countryDto);
+    public ResponseEntity<CountryDto> updateCountry(Long id, NewCountryDtoRequest newCountryDtoRequest) {
+        return ResponseEntity.ok(countryFacade.update(id, newCountryDtoRequest));
     }
 }
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/rest/DatabaseController.java b/core/src/main/java/cz/muni/fi/pa165/core/rest/DatabaseController.java
new file mode 100644
index 0000000000000000000000000000000000000000..a73af3cfbe12620d70a2f1a00f164ea768be39c7
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/rest/DatabaseController.java
@@ -0,0 +1,43 @@
+package cz.muni.fi.pa165.core.rest;
+
+import cz.muni.fi.pa165.core.api.DatabaseApi;
+import cz.muni.fi.pa165.core.api.DatabaseApiDelegate;
+import cz.muni.fi.pa165.core.data.seed.DatabaseInitializer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class DatabaseController implements DatabaseApiDelegate {
+
+    private final DatabaseInitializer databaseInitializer;
+
+    @Autowired
+    public DatabaseController(DatabaseInitializer databaseInitializer) {
+        this.databaseInitializer = databaseInitializer;
+    }
+
+    /**
+     * POST /api/db/seed : Seeds the predefined data database
+     *
+     * @return Created (status code 201)
+     * @see DatabaseApi#seed
+     */
+    @Override
+    public ResponseEntity<Void> seed() {
+        databaseInitializer.seed();
+        return ResponseEntity.ok().build();
+    }
+
+    /**
+     * DELETE /api/db/clear : Clears the predefined data database
+     *
+     * @return Deleted (status code 204)
+     * @see DatabaseApi#clear
+     */
+    @Override
+    public ResponseEntity<Void> clear() {
+        databaseInitializer.clear();
+        return ResponseEntity.ok().build();
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/rest/FlightController.java b/core/src/main/java/cz/muni/fi/pa165/core/rest/FlightController.java
index f36ad554b1f3791375a6337663f1338c6a589b06..b77606628f2c8c04bf7bd964d1f67bdfb8290361 100644
--- a/core/src/main/java/cz/muni/fi/pa165/core/rest/FlightController.java
+++ b/core/src/main/java/cz/muni/fi/pa165/core/rest/FlightController.java
@@ -2,8 +2,10 @@ package cz.muni.fi.pa165.core.rest;
 
 import cz.muni.fi.pa165.core.api.FlightApi;
 import cz.muni.fi.pa165.core.api.FlightApiDelegate;
+import cz.muni.fi.pa165.core.facade.flight.FlightFacade;
 import cz.muni.fi.pa165.core.model.FlightDto;
 import cz.muni.fi.pa165.core.model.NewFlightDtoRequest;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -12,6 +14,13 @@ import java.util.List;
 @RestController
 public class FlightController implements FlightApiDelegate {
 
+    private final FlightFacade<Long> flightFacade;
+
+    @Autowired
+    public FlightController(FlightFacade<Long> flightFacade) {
+        this.flightFacade = flightFacade;
+    }
+
     /**
      * POST /api/flights : Create a new flight.
      * Creates a new flight and returns it as a response.
@@ -23,7 +32,7 @@ public class FlightController implements FlightApiDelegate {
      */
     @Override
     public ResponseEntity<FlightDto> createFlight(NewFlightDtoRequest newFlightDtoRequest) {
-        return FlightApiDelegate.super.createFlight(newFlightDtoRequest);
+        return ResponseEntity.ok(flightFacade.save(newFlightDtoRequest));
     }
 
     /**
@@ -36,7 +45,8 @@ public class FlightController implements FlightApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteFlight(Long id) {
-        return FlightApiDelegate.super.deleteFlight(id);
+        flightFacade.deleteById(id);
+        return null;
     }
 
     /**
@@ -48,7 +58,7 @@ public class FlightController implements FlightApiDelegate {
      */
     @Override
     public ResponseEntity<List<FlightDto>> getAllFlights() {
-        return FlightApiDelegate.super.getAllFlights();
+        return ResponseEntity.ok(flightFacade.findAll());
     }
 
     /**
@@ -61,7 +71,7 @@ public class FlightController implements FlightApiDelegate {
      */
     @Override
     public ResponseEntity<FlightDto> getFlightById(Long id) {
-        return FlightApiDelegate.super.getFlightById(id);
+        return ResponseEntity.of(flightFacade.findById(id));
     }
 
     /**
@@ -76,6 +86,6 @@ public class FlightController implements FlightApiDelegate {
      */
     @Override
     public ResponseEntity<FlightDto> updateFlight(Long id, NewFlightDtoRequest newFlightDtoRequest) {
-        return FlightApiDelegate.super.updateFlight(id, newFlightDtoRequest);
+        return ResponseEntity.ok(flightFacade.update(id, newFlightDtoRequest));
     }
 }
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/rest/StewardController.java b/core/src/main/java/cz/muni/fi/pa165/core/rest/StewardController.java
index c24bdc0a7bdc783ec10b4ad1d29f686983f8f842..e041698c013dccd342f45211b2bec02453a9b916 100644
--- a/core/src/main/java/cz/muni/fi/pa165/core/rest/StewardController.java
+++ b/core/src/main/java/cz/muni/fi/pa165/core/rest/StewardController.java
@@ -2,7 +2,9 @@ package cz.muni.fi.pa165.core.rest;
 
 import cz.muni.fi.pa165.core.api.StewardApi;
 import cz.muni.fi.pa165.core.api.StewardApiDelegate;
+import cz.muni.fi.pa165.core.facade.steward.StewardFacade;
 import cz.muni.fi.pa165.core.model.*;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -11,6 +13,13 @@ import java.util.List;
 @RestController
 public class StewardController implements StewardApiDelegate {
 
+    private final StewardFacade<Long> stewardFacade;
+
+    @Autowired
+    public StewardController(StewardFacade<Long> stewardFacade) {
+        this.stewardFacade = stewardFacade;
+    }
+
     /**
      * POST /api/stewards : Create a new steward.
      * Creates a new steward and returns it as a response.
@@ -22,7 +31,7 @@ public class StewardController implements StewardApiDelegate {
      */
     @Override
     public ResponseEntity<StewardDto> createSteward(NewStewardDtoRequest newStewardDtoRequest) {
-        return StewardApiDelegate.super.createSteward(newStewardDtoRequest);
+        return ResponseEntity.ok(stewardFacade.save(newStewardDtoRequest));
     }
 
     /**
@@ -36,7 +45,7 @@ public class StewardController implements StewardApiDelegate {
      */
     @Override
     public ResponseEntity<StewardDto> createStewardFlights(Long stewardId, Long flightId) {
-        return StewardApiDelegate.super.createStewardFlights(stewardId, flightId);
+        return ResponseEntity.ok(stewardFacade.assignStewardFlight(stewardId, flightId));
     }
 
     /**
@@ -49,7 +58,8 @@ public class StewardController implements StewardApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteSteward(Long id) {
-        return StewardApiDelegate.super.deleteSteward(id);
+        stewardFacade.deleteById(id);
+        return null;
     }
 
     /**
@@ -63,7 +73,8 @@ public class StewardController implements StewardApiDelegate {
      */
     @Override
     public ResponseEntity<Void> deleteStewardFlights(Long stewardId, Long flightId) {
-        return StewardApiDelegate.super.deleteStewardFlights(stewardId, flightId);
+        stewardFacade.deleteStewardFlightsAssignment(stewardId, flightId);
+        return null;
     }
 
     /**
@@ -75,7 +86,7 @@ public class StewardController implements StewardApiDelegate {
      */
     @Override
     public ResponseEntity<List<StewardDto>> getAllStewards() {
-        return StewardApiDelegate.super.getAllStewards();
+        return ResponseEntity.ok(stewardFacade.findAll());
     }
 
     /**
@@ -89,7 +100,7 @@ public class StewardController implements StewardApiDelegate {
      */
     @Override
     public ResponseEntity<StewardDto> getSteward(Long id) {
-        return StewardApiDelegate.super.getSteward(id);
+        return ResponseEntity.of(stewardFacade.findById(id));
     }
 
     /**
@@ -119,20 +130,6 @@ public class StewardController implements StewardApiDelegate {
      */
     @Override
     public ResponseEntity<StewardDto> updateSteward(Long id, NewStewardDtoRequest newStewardDtoRequest) {
-        return StewardApiDelegate.super.updateSteward(id, newStewardDtoRequest);
-    }
-
-    /**
-     * PUT /api/stewards/{stewardId}/flights/{flightId} : Update assignment of steward to a flight.
-     *
-     * @param stewardId (required)
-     * @param flightId  (required)
-     * @return Response containing a single Steward. (status code 200)
-     * or Input data not correct (status code 400)
-     * @see StewardApi#updateStewardFlights
-     */
-    @Override
-    public ResponseEntity<StewardDto> updateStewardFlights(Long stewardId, Long flightId) {
-        return StewardApiDelegate.super.updateStewardFlights(stewardId, flightId);
+        return ResponseEntity.ok(stewardFacade.update(id, newStewardDtoRequest));
     }
 }
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/airplane/AirplaneService.java b/core/src/main/java/cz/muni/fi/pa165/core/service/airplane/AirplaneService.java
new file mode 100644
index 0000000000000000000000000000000000000000..c724f1e16fd54ae0d791a1c76b067f3c77824679
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/airplane/AirplaneService.java
@@ -0,0 +1,9 @@
+package cz.muni.fi.pa165.core.service.airplane;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.service.common.BaseService;
+
+public interface AirplaneService extends BaseService<Airplane, Long> {
+
+    Airplane update(Long id, Airplane newAirplane);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/airplane/AirplaneServiceImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/service/airplane/AirplaneServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..17d6445a797d8b8d16a4c35b31c654785eb7b333
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/airplane/AirplaneServiceImpl.java
@@ -0,0 +1,31 @@
+package cz.muni.fi.pa165.core.service.airplane;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.data.repository.airplane.AirplaneRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.service.common.BaseServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AirplaneServiceImpl extends BaseServiceImpl<Airplane, Long> implements AirplaneService {
+
+    private final AirplaneRepository airplaneRepository;
+
+    @Autowired
+    public AirplaneServiceImpl(AirplaneRepository airplaneRepository) {
+        super(airplaneRepository);
+        this.airplaneRepository = airplaneRepository;
+    }
+
+    @Override
+    public Airplane update(Long id, Airplane newAirplane) {
+        var entityToUpdate = airplaneRepository.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        entityToUpdate.setName(newAirplane.getName());
+        entityToUpdate.setCapacity(newAirplane.getCapacity());
+
+        return airplaneRepository.save(entityToUpdate);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeService.java b/core/src/main/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeService.java
new file mode 100644
index 0000000000000000000000000000000000000000..72a345900acb1b62bcf661204c3b5f0a90b440fe
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeService.java
@@ -0,0 +1,13 @@
+package cz.muni.fi.pa165.core.service.airplanetype;
+
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.service.common.BaseService;
+
+import java.util.Optional;
+
+public interface AirplaneTypeService extends BaseService<AirplaneType, Long> {
+
+    Optional<AirplaneType> findByName(String name);
+
+    AirplaneType update(Long id, AirplaneType newAirplaneType);
+}
\ No newline at end of file
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeServiceImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..a776ef1a4ca59990eb17532e9e2c4005530e4c35
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeServiceImpl.java
@@ -0,0 +1,37 @@
+package cz.muni.fi.pa165.core.service.airplanetype;
+
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.data.repository.airplanetype.AirplaneTypeRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.service.common.BaseServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+public class AirplaneTypeServiceImpl extends BaseServiceImpl<AirplaneType, Long> implements AirplaneTypeService {
+
+    private final AirplaneTypeRepository airplaneTypeRepository;
+
+    @Autowired
+    public AirplaneTypeServiceImpl(AirplaneTypeRepository airplaneTypeRepository) {
+        super(airplaneTypeRepository);
+        this.airplaneTypeRepository = airplaneTypeRepository;
+    }
+
+    @Override
+    public Optional<AirplaneType> findByName(String name) {
+        return airplaneTypeRepository.findByName(name);
+    }
+
+    @Override
+    public AirplaneType update(Long id, AirplaneType newAirplaneType) {
+        var entityToUpdate = airplaneTypeRepository.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        entityToUpdate.setName(newAirplaneType.getName());
+
+        return airplaneTypeRepository.save(entityToUpdate);
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/airport/AirportService.java b/core/src/main/java/cz/muni/fi/pa165/core/service/airport/AirportService.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ae859c70b9aa7b08b54217779f2ea05cab030a2
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/airport/AirportService.java
@@ -0,0 +1,21 @@
+package cz.muni.fi.pa165.core.service.airport;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.service.common.BaseService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public interface AirportService extends BaseService<Airport, Long> {
+
+    Optional<Airport> findByName(String name);
+
+    Optional<Airport> findByCode(String code);
+
+    List<Airport> findByCity(City city);
+
+    Airport update(Long id, Airport updatedAirport);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/airport/AirportServiceImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/service/airport/AirportServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..881f95850096a1bfc4b84929f092859a8a988a2e
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/airport/AirportServiceImpl.java
@@ -0,0 +1,49 @@
+package cz.muni.fi.pa165.core.service.airport;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.repository.airport.AirportRepository;
+import cz.muni.fi.pa165.core.service.common.BaseServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class AirportServiceImpl extends BaseServiceImpl<Airport, Long> implements AirportService {
+
+    private final AirportRepository airportRepository;
+
+    @Autowired
+    public AirportServiceImpl(AirportRepository airportRepository) {
+        super(airportRepository);
+        this.airportRepository = airportRepository;
+    }
+
+    @Override
+    public Optional<Airport> findByName(String name) {
+        return airportRepository.findByName(name);
+    }
+
+    @Override
+    public Optional<Airport> findByCode(String code) {
+        return airportRepository.findByCode(code);
+    }
+
+    @Override
+    public List<Airport> findByCity(City city) {
+        return airportRepository.findByCity(city);
+    }
+
+    @Override
+    public Airport update(Long id, Airport updatedAirport) {
+        Airport airportToUpdate = airportRepository.findById(id)
+                .orElseThrow(RuntimeException::new);
+
+        airportToUpdate.setName(updatedAirport.getName());
+        airportToUpdate.setCode(updatedAirport.getCode());
+
+        return airportRepository.save(airportToUpdate);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/city/CityService.java b/core/src/main/java/cz/muni/fi/pa165/core/service/city/CityService.java
new file mode 100644
index 0000000000000000000000000000000000000000..663bd787ba0d12d72dbd339333c0117e675db2de
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/city/CityService.java
@@ -0,0 +1,13 @@
+package cz.muni.fi.pa165.core.service.city;
+
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.service.common.BaseService;
+
+import java.util.Optional;
+
+public interface CityService extends BaseService<City, Long> {
+
+    Optional<City> findByName(String name);
+
+    City update(Long id, City updatedCity);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/city/CityServiceImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/service/city/CityServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..6dc713cfef09d452f79e373494c0b91ea2845b50
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/city/CityServiceImpl.java
@@ -0,0 +1,37 @@
+package cz.muni.fi.pa165.core.service.city;
+
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.repository.city.CityRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.service.common.BaseServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+public class CityServiceImpl extends BaseServiceImpl<City, Long> implements CityService {
+
+    private final CityRepository cityRepository;
+
+    @Autowired
+    public CityServiceImpl(CityRepository cityRepository) {
+        super(cityRepository);
+        this.cityRepository = cityRepository;
+    }
+
+    @Override
+    public Optional<City> findByName(String name) {
+        return cityRepository.findByName(name);
+    }
+
+    @Override
+    public City update(Long id, City updatedCity) {
+        City cityToUpdate = cityRepository.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        cityToUpdate.setName(updatedCity.getName());
+
+        return cityRepository.save(cityToUpdate);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/common/BaseService.java b/core/src/main/java/cz/muni/fi/pa165/core/service/common/BaseService.java
new file mode 100644
index 0000000000000000000000000000000000000000..c85567d5c294f5751b878cc9f844e49daaa5e829
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/common/BaseService.java
@@ -0,0 +1,26 @@
+package cz.muni.fi.pa165.core.service.common;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * BaseService for common CRUD operations
+ *
+ * @param <E> Entity
+ * @param <K> Key
+ * @author martinslovik
+ */
+public interface BaseService<E, K> {
+
+    E save(E entity);
+
+    Optional<E> findById(K id);
+
+    List<E> findAll();
+
+    void deleteById(K id);
+
+    void deleteAll();
+
+    boolean existsById(K id);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/common/BaseServiceImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/service/common/BaseServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..d937a14a4659db714530cac1939f8d54ca677c9a
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/common/BaseServiceImpl.java
@@ -0,0 +1,60 @@
+package cz.muni.fi.pa165.core.service.common;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+
+@Service
+public abstract class BaseServiceImpl<E extends DomainEntity, K> implements BaseService<E, K> {
+
+    private final BaseRepository<E, K> repository;
+
+    @Autowired
+    private Validator validator;
+
+    @Autowired
+    protected BaseServiceImpl(BaseRepository<E, K> repository) {
+        this.repository = repository;
+    }
+
+    @Override
+    public E save(E entity) {
+        var violations = validator.validate(entity);
+        if (!violations.isEmpty()) {
+            throw new ConstraintViolationException(violations);
+        }
+        return repository.save(entity);
+    }
+
+    @Override
+    public Optional<E> findById(K id) {
+        return repository.findById(id);
+    }
+
+    @Override
+    public List<E> findAll() {
+        return (List<E>) repository.findAll();
+    }
+
+    @Override
+    public void deleteById(K id) {
+        repository.deleteById(id);
+    }
+
+    @Override
+    public void deleteAll() {
+        repository.deleteAll();
+    }
+
+    @Override
+    public boolean existsById(K id) {
+        return repository.existsById(id);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/country/CountryService.java b/core/src/main/java/cz/muni/fi/pa165/core/service/country/CountryService.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbe8b5f4c73f477c87e341b86acdf6485fa49016
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/country/CountryService.java
@@ -0,0 +1,13 @@
+package cz.muni.fi.pa165.core.service.country;
+
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.service.common.BaseService;
+
+import java.util.Optional;
+
+public interface CountryService extends BaseService<Country, Long> {
+
+    Optional<Country> findByName(String name);
+
+    Country update(Long id, Country updatedCountry);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/country/CountryServiceImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/service/country/CountryServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca3ce82c1a9561bbd9c3acf09bdaabf1eb37eac8
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/country/CountryServiceImpl.java
@@ -0,0 +1,40 @@
+package cz.muni.fi.pa165.core.service.country;
+
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.data.repository.country.CountryRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.service.common.BaseServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
+
+@Service
+public class CountryServiceImpl extends BaseServiceImpl<Country, Long> implements CountryService {
+
+    private final CountryRepository countryRepository;
+
+    @Autowired
+    public CountryServiceImpl(CountryRepository countryRepository) {
+        super(countryRepository);
+        this.countryRepository = countryRepository;
+    }
+
+    @Transactional
+    @Override
+    public Optional<Country> findByName(String name) {
+        return countryRepository.findByName(name);
+    }
+
+    @Transactional
+    @Override
+    public Country update(Long id, Country updatedCountry) {
+        Country countryToUpdate = countryRepository.findById(id)
+                .orElseThrow(() -> new ResourceNotFoundException("Country with id " + id + " not found."));
+
+        countryToUpdate.setName(updatedCountry.getName());
+
+        return countryRepository.save(countryToUpdate);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/flight/FlightService.java b/core/src/main/java/cz/muni/fi/pa165/core/service/flight/FlightService.java
new file mode 100644
index 0000000000000000000000000000000000000000..3a14ff88255f23d97f3dd633e81b12c39117a601
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/flight/FlightService.java
@@ -0,0 +1,13 @@
+package cz.muni.fi.pa165.core.service.flight;
+
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.service.common.BaseService;
+
+import java.util.Optional;
+
+public interface FlightService extends BaseService<Flight, Long> {
+
+    Optional<Flight> findByIdWithStewards(Long id);
+
+    Flight update(Long id, Flight newFlight);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/flight/FlightServiceImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/service/flight/FlightServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..abcc02c467237906b8608dac1b3196c9e448a149
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/flight/FlightServiceImpl.java
@@ -0,0 +1,38 @@
+package cz.muni.fi.pa165.core.service.flight;
+
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.data.repository.flight.FlightRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.service.common.BaseServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+public class FlightServiceImpl extends BaseServiceImpl<Flight, Long> implements FlightService {
+
+    private final FlightRepository flightRepository;
+
+    @Autowired
+    public FlightServiceImpl(FlightRepository flightRepository) {
+        super(flightRepository);
+        this.flightRepository = flightRepository;
+    }
+
+    @Override
+    public Optional<Flight> findByIdWithStewards(Long id) {
+        return flightRepository.findByIdWithStewards(id);
+    }
+
+    @Override
+    public Flight update(Long id, Flight newFlight) {
+        var entityToUpdate = flightRepository.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        entityToUpdate.setDepartureTime(newFlight.getDepartureTime());
+        entityToUpdate.setArrivalTime(newFlight.getArrivalTime());
+
+        return flightRepository.save(entityToUpdate);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/steward/StewardService.java b/core/src/main/java/cz/muni/fi/pa165/core/service/steward/StewardService.java
new file mode 100644
index 0000000000000000000000000000000000000000..9451f8741c179eff2c55db947f01ccff96930b1a
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/steward/StewardService.java
@@ -0,0 +1,24 @@
+package cz.muni.fi.pa165.core.service.steward;
+
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.data.domain.FlightSteward;
+import cz.muni.fi.pa165.core.data.domain.Steward;
+import cz.muni.fi.pa165.core.service.common.BaseService;
+
+import java.util.Optional;
+
+public interface StewardService extends BaseService<Steward, Long> {
+
+    /**
+     * Saves the assignment of Steward to a Flight
+     * @param flightSteward Flight<->Steward connection table
+     * @return FlightSteward connection table stored in DB
+     */
+    FlightSteward saveFlightStewards(FlightSteward flightSteward);
+
+    void deleteFlightStewards(Steward steward, Flight flight);
+
+    Optional<Steward> findByIdWithFlights(Long id);
+
+    Steward update(Long id, Steward newSteward);
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/service/steward/StewardServiceImpl.java b/core/src/main/java/cz/muni/fi/pa165/core/service/steward/StewardServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..110165813d83a06ba5f16960c5744b11d2edf925
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/service/steward/StewardServiceImpl.java
@@ -0,0 +1,55 @@
+package cz.muni.fi.pa165.core.service.steward;
+
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.data.domain.FlightSteward;
+import cz.muni.fi.pa165.core.data.domain.Steward;
+import cz.muni.fi.pa165.core.data.repository.flightsteward.FlightStewardRepository;
+import cz.muni.fi.pa165.core.data.repository.steward.StewardRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.core.service.common.BaseServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+public class StewardServiceImpl extends BaseServiceImpl<Steward, Long> implements StewardService {
+
+    private final StewardRepository stewardRepository;
+    private final FlightStewardRepository flightStewardRepository;
+
+    @Autowired
+    public StewardServiceImpl(StewardRepository stewardRepository, FlightStewardRepository flightStewardRepository) {
+        super(stewardRepository);
+        this.stewardRepository = stewardRepository;
+        this.flightStewardRepository = flightStewardRepository;
+    }
+
+    @Override
+    public FlightSteward saveFlightStewards(FlightSteward flightSteward) {
+        return flightStewardRepository.save(flightSteward);
+    }
+
+    @Override
+    public Optional<Steward> findByIdWithFlights(Long id) {
+        return stewardRepository.findByIdWithFlights(id);
+    }
+
+    @Override
+    public void deleteFlightStewards(Steward steward, Flight flight) {
+        var foundFlightStewards = flightStewardRepository.findByStewardAndFlight(steward, flight)
+                .orElseThrow(ResourceNotFoundException::new);
+        flightStewardRepository.delete(foundFlightStewards);
+    }
+
+    @Override
+    public Steward update(Long id, Steward newSteward) {
+        var entityToUpdate = stewardRepository.findById(id)
+                .orElseThrow(ResourceNotFoundException::new);
+
+        entityToUpdate.setFirstName(newSteward.getFirstName());
+        entityToUpdate.setLastName(newSteward.getLastName());
+
+        return stewardRepository.save(entityToUpdate);
+    }
+}
diff --git a/core/src/main/java/cz/muni/fi/pa165/core/validation/GpsLocationValidator.java b/core/src/main/java/cz/muni/fi/pa165/core/validation/GpsLocationValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd944f9e5217483de649969a936a5ce14055a128
--- /dev/null
+++ b/core/src/main/java/cz/muni/fi/pa165/core/validation/GpsLocationValidator.java
@@ -0,0 +1,42 @@
+package cz.muni.fi.pa165.core.validation;
+
+import cz.muni.fi.pa165.core.exceptions.InvalidGpsLocationException;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GpsLocationValidator {
+
+    private static final double MIN_LATITUDE = -90.0;
+    private static final double MAX_LATITUDE = 90.0;
+    private static final double MIN_LONGITUDE = -180.0;
+    private static final double MAX_LONGITUDE = 180.0;
+
+    public void validate(double latitude, double longitude) {
+        if (!validLatitude(latitude)) {
+            throw new InvalidGpsLocationException(
+                    formatExceptionMessage("latitude", MIN_LATITUDE, MAX_LATITUDE, latitude)
+            );
+        }
+        if (!validLongitude(longitude)) {
+            throw new InvalidGpsLocationException(
+                    formatExceptionMessage("longitude", MIN_LONGITUDE, MAX_LONGITUDE, longitude)
+            );
+        }
+    }
+
+    private static boolean validLatitude(double latitude) {
+        return MIN_LATITUDE <= latitude && latitude <= MAX_LATITUDE;
+    }
+
+    private static boolean validLongitude(double longitude) {
+        return MIN_LONGITUDE <= longitude && longitude <= MAX_LONGITUDE;
+    }
+
+    private String formatExceptionMessage(String indicator,
+                                          double lowerBound,
+                                          double upperBound,
+                                          double indicatorsValue) {
+        return String.format("Invalid value of %s given: expecting value to be from range [%.2f, %.2f], but got %.2f"
+                .formatted(indicator, lowerBound, upperBound, indicatorsValue));
+    }
+}
diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml
index 1174f8ddf871f57a787de9f2880346e620d27cbf..80d02907e8687ed9b94a764025a1871a44a9aebf 100644
--- a/core/src/main/resources/application.yml
+++ b/core/src/main/resources/application.yml
@@ -1,8 +1,42 @@
 spring:
+  h2:
+    console:
+      enabled: true
+      path: /h2
   datasource:
-    url: jdbc:h2:mem:exampleDb
+    url: jdbc:h2:mem:coreDb
     username: sa
     password: password
     driverClassName: org.h2.Driver
   jpa:
     database-platform: org.hibernate.dialect.H2Dialect
+    hibernate:
+      ddl-auto: update
+  security:
+    oauth2:
+      resourceserver:
+        opaque-token:
+          introspection-uri: https://oidc.muni.cz/oidc/introspect
+          # Martin Kuba's testing resource server
+          client-id: d57b3a8f-156e-46de-9f27-39c4daee05e1
+          client-secret: fa228ebc-4d54-4cda-901e-4d6287f8b1652a9c9c44-73c9-4502-973f-bcdb4a8ec96a
+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'
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/CoreApplicationIT.java b/core/src/test/java/cz/muni/fi/pa165/core/CoreApplicationIT.java
index f4ed9d9e4482dab96d99f2845e432d638f28d49d..957bdf30c80755f447c09612ec97b964e7ca279c 100644
--- a/core/src/test/java/cz/muni/fi/pa165/core/CoreApplicationIT.java
+++ b/core/src/test/java/cz/muni/fi/pa165/core/CoreApplicationIT.java
@@ -1,18 +1,25 @@
 package cz.muni.fi.pa165.core;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import cz.muni.fi.pa165.core.facade.country.CountryFacade;
 import cz.muni.fi.pa165.core.model.CountryDto;
 import cz.muni.fi.pa165.core.model.NewCountryDtoRequest;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
 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.http.MediaType;
 import org.springframework.test.web.servlet.MockMvc;
 
+import java.util.Optional;
+
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -22,7 +29,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  * Integration tests. Run by "maven verify".
  */
 @SpringBootTest
-@AutoConfigureMockMvc
+@AutoConfigureMockMvc(addFilters = false)
 class CoreApplicationIT {
 
     private static final Logger log = LoggerFactory.getLogger(CoreApplicationIT.class);
@@ -33,21 +40,30 @@ class CoreApplicationIT {
     @Autowired
     ObjectMapper objectMapper;
 
+    @MockBean
+    private CountryFacade<Long> mockCountryFacade;
+
     @Test
     void createCountryTest() throws Exception {
         log.debug("createCountryTest() running");
 
-        var expectedId = 42L;
-        var expectedName = "Uganda";
-        var newCountryDtoRequest = new NewCountryDtoRequest();
-        newCountryDtoRequest.setName("Canadaa");
+        final long expectedId = 42;
+        final String expectedName = "Uganda";
+
+        var request = new NewCountryDtoRequest().name(expectedName);
+        var expectedResponse = new CountryDto()
+                .id(expectedId)
+                .name(expectedName);
+
+        Mockito.when(mockCountryFacade.save(any(NewCountryDtoRequest.class)))
+                .thenReturn(expectedResponse);
 
         var response = mockMvc.perform(
-                post("/api/countries")
-                        .contentType(MediaType.APPLICATION_JSON)
-                        .content(objectMapper.writeValueAsString(newCountryDtoRequest))
+                        post("/api/countries")
+                                .contentType(MediaType.APPLICATION_JSON)
+                                .content(objectMapper.writeValueAsString(request))
                 )
-                .andExpect(status().isCreated())
+                .andExpect(status().isOk())
                 .andExpect(jsonPath("$.id").value(expectedId))
                 .andExpect(jsonPath("$.name").value(expectedName))
                 .andReturn().getResponse().getContentAsString();
@@ -65,7 +81,14 @@ class CoreApplicationIT {
         var expectedId = 4242L;
         var expectedName = "Fraaaance";
 
-        var response = mockMvc.perform(get("/api/countries/42"))
+        var expectedResponse = new CountryDto()
+                .id(expectedId)
+                .name(expectedName);
+
+        Mockito.when(mockCountryFacade.findById(eq(expectedId)))
+                .thenReturn(Optional.of(expectedResponse));
+
+        var response = mockMvc.perform(get("/api/countries/4242"))
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.id").value(expectedId))
                 .andExpect(jsonPath("$.name").value(expectedName))
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirplaneMapperTest.java b/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirplaneMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..79a04b5442a328a86a7213f72ca97cf13f93b2e1
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirplaneMapperTest.java
@@ -0,0 +1,55 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.model.AirplaneDto;
+import cz.muni.fi.pa165.core.model.NewAirplaneDtoRequest;
+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 AirplaneMapperTest {
+
+    @Autowired
+    private AirplaneMapper airplaneMapper;
+
+    @Test
+    void toEntityFromNewRequest() {
+        var newAirplaneDtoRequest = new NewAirplaneDtoRequest();
+        newAirplaneDtoRequest.setName("DL7132");
+        newAirplaneDtoRequest.setCapacity(191);
+
+        Airplane airplane = airplaneMapper.toEntityFromNewRequest(newAirplaneDtoRequest);
+
+        assertThat(airplane.getName())
+                .isEqualTo(newAirplaneDtoRequest.getName());
+        assertThat(airplane.getCapacity())
+                .isEqualTo(newAirplaneDtoRequest.getCapacity());
+    }
+
+    @Test
+    void toDto() {
+        Airplane airplane = new Airplane();
+        airplane.setId(1L);
+        airplane.setName("KL4242");
+        airplane.setCapacity(210);
+        var airplaneType = new AirplaneType();
+        airplaneType.setId(81L);
+        airplaneType.setName("Boeing 777-Max");
+        airplane.setType(airplaneType);
+
+        AirplaneDto airplaneDto = airplaneMapper.toDto(airplane);
+
+        assertThat(airplaneDto.getId())
+                .isEqualTo(airplane.getId());
+        assertThat(airplaneDto.getName())
+                .isEqualTo(airplane.getName());
+        assertThat(airplaneDto.getCapacity())
+                .isEqualTo(airplane.getCapacity());
+        assertThat(airplaneDto.getTypeId())
+                .isEqualTo(airplane.getType().getId());
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirplaneTypeMapperTest.java b/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirplaneTypeMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..683b1129a2a9528c473b0c5b565a2787e0b1f2c8
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirplaneTypeMapperTest.java
@@ -0,0 +1,67 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.model.AirplaneTypeDto;
+import cz.muni.fi.pa165.core.model.NewAirplaneTypeDtoRequest;
+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 static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class AirplaneTypeMapperTest {
+
+    @Autowired
+    private AirplaneTypeMapper airplaneTypeMapper;
+
+    @Test
+    void toEntityFromNewRequest() {
+        var newAirplaneTypeDtoRequest = new NewAirplaneTypeDtoRequest();
+        newAirplaneTypeDtoRequest.setName("A320");
+
+        AirplaneType airplaneType = airplaneTypeMapper.toEntityFromNewRequest(newAirplaneTypeDtoRequest);
+
+        assertThat(airplaneType.getName())
+                .isEqualTo(newAirplaneTypeDtoRequest.getName());
+    }
+
+    @Test
+    void toDto() {
+        var airplaneType = new AirplaneType();
+        airplaneType.setId(1L);
+        airplaneType.setName("A320");
+        airplaneType.setAirplanes(createAirplanes());
+
+        AirplaneTypeDto airplaneTypeDto = airplaneTypeMapper.toDto(airplaneType);
+
+        assertThat(airplaneTypeDto.getId())
+                .isEqualTo(airplaneType.getId());
+        assertThat(airplaneTypeDto.getName())
+                .isEqualTo(airplaneType.getName());
+        assertThat(airplaneTypeDto.getAirplanesIds())
+                .hasSameSizeAs(airplaneType.getAirplanes());
+        for (int i = 0; i < airplaneTypeDto.getAirplanesIds().size(); i++) {
+            assertThat(airplaneTypeDto.getAirplanesIds().get(i))
+                    .isEqualTo(airplaneType.getAirplanes().get(i).getId());
+        }
+    }
+
+    @Test
+    void getAirplanesTypeId() {
+        Long airplaneTypeId = airplaneTypeMapper.getAirplanesTypeId(null);
+
+        assertThat(airplaneTypeId)
+                .isZero();
+    }
+
+    private List<Airplane> createAirplanes() {
+        var a320Airplane = new Airplane();
+        a320Airplane.setId(42L);
+        a320Airplane.setName("DL123");
+        return List.of(a320Airplane);
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirportMapperTest.java b/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirportMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..393692fafc45605da70022c7dce7eefd598d0e20
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/mapper/AirportMapperTest.java
@@ -0,0 +1,120 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.model.AirportDto;
+import cz.muni.fi.pa165.core.model.GPSLocationDto;
+import cz.muni.fi.pa165.core.model.NewAirportDtoRequest;
+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 static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class AirportMapperTest {
+
+    @Autowired
+    private AirportMapper airportMapper;
+
+    @Test
+    void toEntityFromNewRequest() {
+        var newAirportDtoRequest = new NewAirportDtoRequest();
+        newAirportDtoRequest.setName("Hartsfield–Jackson Atlanta International Airport");
+        newAirportDtoRequest.setCode("ATL");
+        var gpsLocation = new GPSLocationDto();
+        gpsLocation.setLatitude(33.64);
+        gpsLocation.setLongitude(-84.42);
+        newAirportDtoRequest.setLocation(gpsLocation);
+
+        Airport airport = airportMapper.toEntityFromNewRequest(newAirportDtoRequest);
+
+        assertThat(airport.getName())
+                .isEqualTo(newAirportDtoRequest.getName());
+        assertThat(airport.getCode())
+                .isEqualTo(newAirportDtoRequest.getCode());
+        assertThat(airport.getLatitude())
+                .isEqualTo(newAirportDtoRequest.getLocation().getLatitude());
+        assertThat(airport.getLongitude())
+                .isEqualTo(newAirportDtoRequest.getLocation().getLongitude());
+    }
+
+    @Test
+    void toDto() {
+        Airport jacksonAtlantaAirport = new Airport();
+        jacksonAtlantaAirport.setId(42L);
+        jacksonAtlantaAirport.setName("Hartsfield–Jackson Atlanta International Airport");
+        jacksonAtlantaAirport.setCode("ATL");
+        var atlanta = new City();
+        atlanta.setId(2023L);
+        atlanta.setName("Atlanta");
+        jacksonAtlantaAirport.setCity(atlanta);
+        var jacksonAtlantaAirportLatitude = 33.64;
+        var jacksonAtlantaAirportLongitude = -84.41;
+        jacksonAtlantaAirport.setLatitude(jacksonAtlantaAirportLatitude);
+        jacksonAtlantaAirport.setLongitude(jacksonAtlantaAirportLongitude);
+        jacksonAtlantaAirport.setArrivingFlights(createArrivingFlights());
+        jacksonAtlantaAirport.setDepartingFlights(createDepartingFlights());
+
+        AirportDto airportDto = airportMapper.toDto(jacksonAtlantaAirport);
+
+        assertThat(airportDto.getId())
+                .isEqualTo(jacksonAtlantaAirport.getId());
+        assertThat(airportDto.getName())
+                .isEqualTo(jacksonAtlantaAirport.getName());
+        assertThat(airportDto.getCode())
+                .isEqualTo(jacksonAtlantaAirport.getCode());
+        assertThat(airportDto.getCityId())
+                .isEqualTo(jacksonAtlantaAirport.getCity().getId());
+        assertThat(airportDto.getLocation().getLatitude())
+                .isEqualTo(jacksonAtlantaAirport.getLatitude());
+        assertThat(airportDto.getLocation().getLongitude())
+                .isEqualTo(jacksonAtlantaAirport.getLongitude());
+        assertThat(airportDto.getArrivingFlightsIds())
+                .hasSameSizeAs(jacksonAtlantaAirport.getArrivingFlights());
+        for (int i = 0; i < airportDto.getArrivingFlightsIds().size(); i++) {
+            assertThat(airportDto.getArrivingFlightsIds().get(i))
+                    .isEqualTo(jacksonAtlantaAirport.getArrivingFlights().get(i).getId());
+        }
+        assertThat(airportDto.getDepartingFlightsIds())
+                .hasSameSizeAs(jacksonAtlantaAirport.getDepartingFlights());
+        for (int i = 0; i < airportDto.getDepartingFlightsIds().size(); i++) {
+            assertThat(airportDto.getDepartingFlightsIds().get(i))
+                    .isEqualTo(jacksonAtlantaAirport.getDepartingFlights().get(i).getId());
+        }
+    }
+
+    @Test
+    void getCityId() {
+        Long cityId = airportMapper.getCityId(null);
+
+        assertThat(cityId)
+                .isZero();
+    }
+
+    private List<Flight> createArrivingFlights() {
+        var delta700 = new Flight();
+        delta700.setId(701L);
+
+        var ke7282 = new Flight();
+        ke7282.setId(1L);
+
+        return List.of(delta700, ke7282);
+    }
+
+    private List<Flight> createDepartingFlights() {
+        var cv6101 = new Flight();
+        cv6101.setId(13L);
+
+        var ac7258 = new Flight();
+        ac7258.setId(72L);
+
+        var f91551 = new Flight();
+        f91551.setId(91L);
+
+        return List.of(cv6101, ac7258, f91551);
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/mapper/CityMapperTest.java b/core/src/test/java/cz/muni/fi/pa165/core/mapper/CityMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce1a9405944ffddb1ab05b7a51509df678679900
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/mapper/CityMapperTest.java
@@ -0,0 +1,86 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.model.CityDto;
+import cz.muni.fi.pa165.core.model.NewCityDtoRequest;
+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 static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class CityMapperTest {
+
+    @Autowired
+    private CityMapper cityMapper;
+
+    @Test
+    void toEntityFromNewRequest() {
+        var newCityDtoRequest = new NewCityDtoRequest();
+        newCityDtoRequest.setName("Winnipeg");
+
+        City city = cityMapper.toEntityFromNewRequest(newCityDtoRequest);
+
+        assertThat(city.getName())
+                .isEqualTo(newCityDtoRequest.getName());
+    }
+
+    @Test
+    void toDto() {
+        var city = new City();
+        city.setId(1L);
+        city.setName("Winnipeg");
+        var canada = new Country();
+        canada.setId(1L);
+        canada.setName("Canada");
+        city.setCountry(canada);
+        city.setAirports(createAirports());
+
+        CityDto cityDto = cityMapper.toDto(city);
+
+        assertThat(cityDto.getId())
+                .isEqualTo(city.getId());
+        assertThat(cityDto.getName())
+                .isEqualTo(city.getName());
+        assertThat(cityDto.getCountryId())
+                .isEqualTo(city.getCountry().getId());
+        assertThat(cityDto.getAirportsIds())
+                .hasSameSizeAs(city.getAirports());
+        for (int i = 0; i < cityDto.getAirportsIds().size(); i++) {
+            assertThat(cityDto.getAirportsIds().get(i))
+                    .isEqualTo(city.getAirports().get(i).getId());
+        }
+    }
+
+    @Test
+    void getCountryId() {
+        Long countryId = cityMapper.getCountryId(null);
+
+        assertThat(countryId)
+                .isZero();
+    }
+
+    private List<Airport> createAirports() {
+        var yvr = new Airport();
+        yvr.setId(42L);
+        yvr.setCode("YVR");
+        yvr.setName("Vancouver International Airport");
+
+        var yvz = new Airport();
+        yvz.setId(13L);
+        yvz.setCode("YVZ");
+        yvz.setName("Toronto Pearson International Airport");
+
+        var yyc = new Airport();
+        yyc.setId(181L);
+        yyc.setCode("YYC");
+        yyc.setName("Calgary International Airport");
+
+        return List.of(yvr, yvz, yyc);
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/mapper/CountryMapperTest.java b/core/src/test/java/cz/muni/fi/pa165/core/mapper/CountryMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..23036ea63c529bdd0be83a75ada2ca1c5b8eaccd
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/mapper/CountryMapperTest.java
@@ -0,0 +1,61 @@
+package cz.muni.fi.pa165.core.mapper;
+
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.model.CountryDto;
+import cz.muni.fi.pa165.core.model.NewCountryDtoRequest;
+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 static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class CountryMapperTest {
+
+    @Autowired
+    private CountryMapper countryMapper;
+
+    @Test
+    void toEntityFromNewRequest() {
+        var newCountryDtoRequest = new NewCountryDtoRequest();
+        newCountryDtoRequest.setName("Canada");
+
+        Country country = countryMapper.toEntityFromNewRequest(newCountryDtoRequest);
+
+        assertThat(country.getName())
+                .isEqualTo(newCountryDtoRequest.getName());
+    }
+
+    @Test
+    void toDto() {
+        var country = new Country();
+        country.setId(1L);
+        country.setName("Canada");
+        City toronto = createCity(1L, "Toronto");
+        City ottawa = createCity(2L, "Ottawa");
+        country.setCities(List.of(toronto, ottawa));
+
+        CountryDto countryDto = countryMapper.toDto(country);
+
+        assertThat(countryDto.getId())
+                .isEqualTo(country.getId());
+        assertThat(countryDto.getName())
+                .isEqualTo(country.getName());
+        assertThat(countryDto.getCitiesIds())
+                .hasSameSizeAs(country.getCities());
+        for (int i = 0; i < countryDto.getCitiesIds().size(); i++) {
+            assertThat(countryDto.getCitiesIds().get(i))
+                    .isEqualTo(country.getCities().get(i).getId());
+        }
+    }
+
+    private static City createCity(Long id, String name) {
+        var city = new City();
+        city.setId(id);
+        city.setName(name);
+        return city;
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/airplane/AirplaneServiceTest.java b/core/src/test/java/cz/muni/fi/pa165/core/service/airplane/AirplaneServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b4a1912b66e9353ddd90455d9b522caff868ab3
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/airplane/AirplaneServiceTest.java
@@ -0,0 +1,97 @@
+package cz.muni.fi.pa165.core.service.airplane;
+
+import cz.muni.fi.pa165.core.data.domain.Airplane;
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.data.repository.airplane.AirplaneRepository;
+import cz.muni.fi.pa165.core.data.repository.airplanetype.AirplaneTypeRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import org.junit.jupiter.api.BeforeAll;
+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.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@SpringBootTest
+class AirplaneServiceTest {
+
+    @Autowired
+    private AirplaneService airplaneService;
+
+    @MockBean
+    private AirplaneRepository airplaneRepository;
+
+    @MockBean
+    private AirplaneTypeRepository airplaneTypeRepository;
+
+    private static final Airplane airplane = new Airplane();
+    private static final AirplaneType airplaneType = new AirplaneType();
+
+    @BeforeAll
+    static void beforeAll() {
+        airplane.setId(42L);
+        airplane.setName("MH370");
+        airplaneType.setId(42L);
+        airplaneType.setName("Boeing 777-200ER");
+        airplane.setType(airplaneType);
+        airplane.setCapacity(239);
+    }
+
+    @Test
+    void updateWhenPresent() {
+        Airplane airplaneToUpdate = new Airplane();
+        airplaneToUpdate.setId(1L);
+        airplaneToUpdate.setName("MH17");
+
+        when(airplaneRepository.findById(1L))
+                .thenReturn(Optional.of(airplaneToUpdate));
+        when(airplaneTypeRepository.findById(42L))
+                .thenReturn(Optional.of(airplaneType));
+        when(airplaneRepository.save(any(Airplane.class)))
+                .thenReturn(airplane);
+
+        Airplane actualAirplane = airplaneService.update(1L, airplaneToUpdate);
+        assertThat(actualAirplane.getId()).isEqualTo(42L);
+        assertThat(actualAirplane.getName()).isEqualTo(airplane.getName());
+        assertThat(actualAirplane.getType()).isEqualTo(airplaneType);
+        assertThat(actualAirplane.getCapacity()).isEqualTo(airplane.getCapacity());
+    }
+
+    @Test
+    void updateWhenNotPresent() {
+        Airplane airplaneToUpdate = new Airplane();
+        airplaneToUpdate.setId(-1L);
+        airplaneToUpdate.setName("MH17");
+        airplaneToUpdate.setCapacity(298);
+
+        when(airplaneRepository.save(any(Airplane.class)))
+                .thenReturn(airplane);
+
+        assertThrows(ResourceNotFoundException.class, () -> airplaneService.update(-1L, airplaneToUpdate));
+    }
+
+    @Test
+    void save() {
+        when(airplaneRepository.save(any(Airplane.class)))
+                .thenReturn(airplane);
+
+        Airplane actualAirplane = airplaneService.save(airplane);
+
+        verify(airplaneRepository, times(1))
+                .save(airplane);
+        verifyNoMoreInteractions(airplaneRepository);
+        assertThat(actualAirplane.getId()).isEqualTo(airplane.getId());
+        assertThat(actualAirplane.getName()).isEqualTo(airplane.getName());
+        assertThat(actualAirplane.getType()).isEqualTo(airplane.getType());
+        assertThat(actualAirplane.getCapacity()).isEqualTo(airplane.getCapacity());
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeServiceTest.java b/core/src/test/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ab856bf2da7e95d14ee2bfbba75737156716b6f
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/airplanetype/AirplaneTypeServiceTest.java
@@ -0,0 +1,61 @@
+package cz.muni.fi.pa165.core.service.airplanetype;
+
+import cz.muni.fi.pa165.core.data.domain.AirplaneType;
+import cz.muni.fi.pa165.core.data.repository.airplanetype.AirplaneTypeRepository;
+import org.junit.jupiter.api.BeforeAll;
+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.util.Optional;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+class AirplaneTypeServiceTest {
+
+    @Autowired
+    private AirplaneTypeService airplaneTypeService;
+
+    @MockBean
+    private AirplaneTypeRepository airplaneTypeRepository;
+
+    private static final AirplaneType airplaneType = new AirplaneType();
+
+    @BeforeAll
+    static void beforeAll() {
+        airplaneType.setId(42L);
+        airplaneType.setName("A320");
+    }
+
+    @Test
+    void findByNameWhenPresent() {
+        when(airplaneTypeRepository.findByName(anyString()))
+                .thenReturn(Optional.of(airplaneType));
+
+        Optional<AirplaneType> actualAirplaneType = airplaneTypeService.findByName("A320");
+
+        verify(airplaneTypeRepository, times(1))
+                .findByName("A320");
+        verifyNoMoreInteractions(airplaneTypeRepository);
+        assertThat(actualAirplaneType.isPresent()).isTrue();
+        assertThat(actualAirplaneType.get().getId()).isEqualTo(airplaneType.getId());
+        assertThat(actualAirplaneType.get().getName()).isEqualTo(airplaneType.getName());
+    }
+
+    @Test
+    void findByNameWhenNotPresent() {
+        when(airplaneTypeRepository.findByName(anyString()))
+                .thenReturn(Optional.empty());
+
+        Optional<AirplaneType> actualAirplaneType = airplaneTypeService.findByName("A320");
+
+        verify(airplaneTypeRepository, times(1))
+                .findByName("A320");
+        verifyNoMoreInteractions(airplaneTypeRepository);
+        assertThat(actualAirplaneType.isEmpty()).isTrue();
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/airport/AirportServiceImplTest.java b/core/src/test/java/cz/muni/fi/pa165/core/service/airport/AirportServiceImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ee594ee3016456b38b4b4a2265a225f0ff8a0c1
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/airport/AirportServiceImplTest.java
@@ -0,0 +1,122 @@
+package cz.muni.fi.pa165.core.service.airport;
+
+import cz.muni.fi.pa165.core.data.domain.Airport;
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.repository.airport.AirportRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class AirportServiceImplTest {
+
+    private AirportServiceImpl airportService;
+    private AirportRepository airportRepository;
+
+    @BeforeEach
+    void setUp() {
+        airportRepository = mock(AirportRepository.class);
+        airportService = new AirportServiceImpl(airportRepository);
+    }
+
+    @Test
+    void findByNameTestSuccess() {
+        var airportName = "Paris Charles de Gaulle";
+        var airport = new Airport();
+        airport.setName(airportName);
+
+        when(airportRepository.findByName(airportName))
+                .thenReturn(Optional.of(airport));
+
+        var foundCityByNameOpt = airportService.findByName(airportName);
+
+        assertTrue(foundCityByNameOpt.isPresent());
+        assertEquals(airport.getName(), foundCityByNameOpt.get().getName());
+        assertEquals(airport, foundCityByNameOpt.get());
+    }
+
+    @Test
+    void findByCodeTestSuccess() {
+        var airportCode = "JFK";
+        var airport = new Airport();
+        airport.setCode(airportCode);
+
+        when(airportRepository.findByCode(airportCode))
+                .thenReturn(Optional.of(airport));
+
+        var foundCityByCodeOpt = airportService.findByCode(airportCode);
+
+        assertTrue(foundCityByCodeOpt.isPresent());
+        assertEquals(airport.getName(), foundCityByCodeOpt.get().getName());
+        assertEquals(airport, foundCityByCodeOpt.get());
+    }
+
+    @Test
+    void findByCityTestSuccess() {
+        var city = new City();
+        city.setName("Prague");
+
+        var airport = new Airport();
+        airport.setCity(city);
+
+        when(airportRepository.findByCity(city))
+                .thenReturn(List.of(airport));
+
+        var foundAirportsByCity = airportService.findByCity(city);
+
+        assertFalse(foundAirportsByCity.isEmpty());
+        assertEquals(airport.getName(), foundAirportsByCity.get(0).getName());
+        assertEquals(airport, foundAirportsByCity.get(0));
+    }
+
+    @Test
+    void findByCityTestFindsNoAirports() {
+        var city = new City();
+        city.setName("Namestovo");
+
+        when(airportRepository.findByCity(city))
+                .thenReturn(new ArrayList<>());
+
+        var foundAirportsByCity = airportService.findByCity(city);
+
+        assertTrue(foundAirportsByCity.isEmpty());
+    }
+
+    @Test
+    void updateTestSuccess() {
+        var airportToUpdate = new Airport();
+        airportToUpdate.setId(1L);
+        var heathrow = "Heathrow";
+        airportToUpdate.setName(heathrow);
+        airportToUpdate.setCity(new City());
+        airportToUpdate.getCity().setName("London");
+        var lhr = "LHR";
+        airportToUpdate.setCode(lhr);
+
+        var updatedAirportRequest = new Airport();
+        var stansted = "Stansted";
+        updatedAirportRequest.setName(stansted);
+        var stn = "STN";
+        updatedAirportRequest.setCode(stn);
+
+        when(airportRepository.findById(1L))
+                .thenReturn(Optional.of(airportToUpdate));
+        when(airportRepository.save(airportToUpdate))
+                .thenReturn(updatedAirportRequest);
+
+        var updatedCountry = airportService.update(1L, updatedAirportRequest);
+
+        assertEquals(airportToUpdate.getName(), updatedCountry.getName());
+        assertEquals(airportToUpdate, updatedCountry);
+        assertEquals(stansted, airportToUpdate.getName());
+        assertNotEquals(heathrow, airportToUpdate.getName());
+        assertEquals(stn, airportToUpdate.getCode());
+        assertNotEquals(lhr, airportToUpdate.getCode());
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/city/CityServiceImplTest.java b/core/src/test/java/cz/muni/fi/pa165/core/service/city/CityServiceImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..25d79c66bcbd1351adcc98b4422d3f644514ed35
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/city/CityServiceImplTest.java
@@ -0,0 +1,90 @@
+package cz.muni.fi.pa165.core.service.city;
+
+import cz.muni.fi.pa165.core.data.domain.City;
+import cz.muni.fi.pa165.core.data.repository.city.CityRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class CityServiceImplTest {
+
+    private CityServiceImpl cityService;
+    private CityRepository cityRepository;
+
+    @BeforeEach
+    void setUp() {
+        cityRepository = mock(CityRepository.class);
+        cityService = new CityServiceImpl(cityRepository);
+    }
+
+    @Test
+    void findByNameTestSuccess() {
+        var cityName = "Berlin";
+        var city = new City();
+        city.setId(1L);
+        city.setName(cityName);
+
+        when(cityRepository.findByName(cityName))
+                .thenReturn(Optional.of(city));
+
+        var foundCityByNameOpt = cityService.findByName(cityName);
+
+        assertTrue(foundCityByNameOpt.isPresent());
+        assertEquals(city.getName(), foundCityByNameOpt.get().getName());
+        assertEquals(city, foundCityByNameOpt.get());
+    }
+
+    @Test
+    void findByNameTestFindsEmpty() {
+        var cityName = "Prague";
+
+        when(cityRepository.findByName(cityName))
+                .thenReturn(Optional.empty());
+
+        var foundCityByNameOpt = cityService.findByName(cityName);
+
+        assertTrue(foundCityByNameOpt.isEmpty());
+    }
+
+    @Test
+    void updateTestNotFoundByIdThrowsException() {
+        var city = new City();
+        city.setId(-1L);
+        city.setName("Madrid");
+
+        when(cityService.findById(-1L))
+                .thenReturn(Optional.empty());
+
+        assertThrows(ResourceNotFoundException.class, () -> cityService.update(-1L, city));
+    }
+
+    @Test
+    void updateTestSuccess() {
+        var cityToUpdate = new City();
+        cityToUpdate.setId(1L);
+        var amsterdam = "Amsterdam";
+        cityToUpdate.setName(amsterdam);
+
+        var updatedCityRequest = new City();
+        var antwerp = "Antwerp";
+        updatedCityRequest.setName(antwerp);
+
+        when(cityRepository.findById(1L))
+                .thenReturn(Optional.of(cityToUpdate));
+        when(cityRepository.save(cityToUpdate))
+                .thenReturn(updatedCityRequest);
+
+        var updatedCountry = cityService.update(1L, updatedCityRequest);
+
+        assertEquals(cityToUpdate.getName(), updatedCountry.getName());
+        assertEquals(cityToUpdate, updatedCountry);
+        assertEquals(antwerp, cityToUpdate.getName());
+        assertNotEquals(amsterdam, cityToUpdate.getName());
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/common/BaseServiceTest.java b/core/src/test/java/cz/muni/fi/pa165/core/service/common/BaseServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b37ae53212ac802fb32a3ba69c109dc0dea79af0
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/common/BaseServiceTest.java
@@ -0,0 +1,95 @@
+package cz.muni.fi.pa165.core.service.common;
+
+import org.junit.jupiter.api.BeforeAll;
+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.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * In this test we use {@code BaseServiceImpl<TestObject,Long>}.
+ * However, for the sake of not writing everywhere {@code <TestObject,Long>},
+ * we created the aliases {@link TestObjectRepository}, {@link TestObjectService}
+ * and {@link TestObjectServiceImpl}.
+ */
+@SpringBootTest
+class BaseServiceTest {
+
+    @Autowired
+    private TestObjectService testObjectService;
+
+    @MockBean
+    private TestObjectRepository testObjectRepository;
+
+    private static final TestObject sampleTestObject = new TestObject("description");
+    private static final TestObject sampleTestObject1 = new TestObject("desc1");
+    private static final TestObject sampleTestObject2 = new TestObject("desc2");
+    private static final List<TestObject> allSampleTestObjects = new ArrayList<>();
+
+    @BeforeAll
+    static void beforeAll() {
+        sampleTestObject.setId(42L);
+        sampleTestObject1.setId(1L);
+        sampleTestObject2.setId(2L);
+        allSampleTestObjects.addAll(List.of(sampleTestObject1, sampleTestObject2));
+    }
+
+    @Test
+    void save() {
+        when(testObjectRepository.save(any(TestObject.class)))
+                .thenReturn(sampleTestObject);
+        TestObject actualObject = testObjectService.save(new TestObject(""));
+        assertThat(actualObject).isEqualTo(sampleTestObject);
+    }
+
+    @Test
+    void findById() {
+        when(testObjectRepository.findById(42L))
+                .thenReturn(Optional.of(sampleTestObject));
+        Optional<TestObject> actualObject = testObjectService.findById(42L);
+        assertThat(actualObject.isPresent()).isTrue();
+        assertThat(actualObject.get()).isEqualTo(sampleTestObject);
+    }
+
+    @Test
+    void findAll() {
+        when(testObjectRepository.findAll())
+                .thenReturn(allSampleTestObjects);
+        List<TestObject> actualObjects = testObjectService.findAll();
+        assertThat(actualObjects.size()).isEqualTo(2);
+        assertThat(actualObjects.get(0)).isEqualTo(sampleTestObject1);
+        assertThat(actualObjects.get(1)).isEqualTo(sampleTestObject2);
+    }
+
+    @Test
+    void deleteById() {
+        testObjectService.deleteById(2L);
+
+        verify(testObjectRepository, times(1)).deleteById(2L);
+        verifyNoMoreInteractions(testObjectRepository);
+    }
+
+    @Test
+    void deleteAll() {
+        testObjectService.deleteAll();
+
+        verify(testObjectRepository, times(1)).deleteAll();
+        verifyNoMoreInteractions(testObjectRepository);
+    }
+
+    @Test
+    void existsById() {
+        testObjectService.existsById(1L);
+
+        verify(testObjectRepository, times(1)).existsById(1L);
+        verifyNoMoreInteractions(testObjectRepository);
+    }
+}
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObject.java b/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObject.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f27f445b33b8b76c377d06fefc14eb2011abe34
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObject.java
@@ -0,0 +1,26 @@
+package cz.muni.fi.pa165.core.service.common;
+
+import cz.muni.fi.pa165.core.data.domain.common.DomainEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+
+/**
+ * Sample object representing entity for the purpose of tests.
+ */
+@Entity
+public class TestObject extends DomainEntity {
+
+    @Column
+    private String description;
+
+    public TestObject() {
+    }
+
+    public TestObject(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectRepository.java b/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..958a594a9ec390645256c7b4655db9d9132b592e
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectRepository.java
@@ -0,0 +1,9 @@
+package cz.muni.fi.pa165.core.service.common;
+
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface TestObjectRepository extends BaseRepository<TestObject, Long> {
+
+}
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectService.java b/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectService.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b8bd69ca8b54c5d8d9bb4268bf57faac18483dc
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectService.java
@@ -0,0 +1,8 @@
+package cz.muni.fi.pa165.core.service.common;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public interface TestObjectService extends BaseService<TestObject, Long> {
+
+}
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectServiceImpl.java b/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..e6abe97108cbcc3b14e48d0aa8663fa28f5c3cde
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/common/TestObjectServiceImpl.java
@@ -0,0 +1,17 @@
+package cz.muni.fi.pa165.core.service.common;
+
+import cz.muni.fi.pa165.core.data.repository.common.BaseRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TestObjectServiceImpl extends BaseServiceImpl<TestObject, Long> implements TestObjectService{
+
+    private final TestObjectRepository testObjectRepository;
+
+    @Autowired
+    public TestObjectServiceImpl(TestObjectRepository testObjectRepository) {
+        super(testObjectRepository);
+        this.testObjectRepository = testObjectRepository;
+    }
+}
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/country/CountryServiceImplTest.java b/core/src/test/java/cz/muni/fi/pa165/core/service/country/CountryServiceImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..83a045171eddfd1003606399f381c7c02841dbfd
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/country/CountryServiceImplTest.java
@@ -0,0 +1,74 @@
+package cz.muni.fi.pa165.core.service.country;
+
+import cz.muni.fi.pa165.core.data.domain.Country;
+import cz.muni.fi.pa165.core.data.repository.country.CountryRepository;
+import cz.muni.fi.pa165.core.exceptions.ResourceNotFoundException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class CountryServiceImplTest {
+
+    private CountryServiceImpl countryService;
+    private CountryRepository countryRepository;
+
+    @BeforeEach
+    void setUp() {
+        countryRepository = mock(CountryRepository.class);
+        countryService = new CountryServiceImpl(countryRepository);
+    }
+
+    @Test
+    void findByNameTestSuccess() {
+        var countryName = "Italy";
+        var country = new Country();
+        country.setId(1L);
+        country.setName(countryName);
+
+        when(countryRepository.findByName(countryName))
+                .thenReturn(Optional.of(country));
+
+        var foundCountryByNameOpt = countryService.findByName(countryName);
+
+        assertTrue(foundCountryByNameOpt.isPresent());
+        assertEquals(country.getName(), foundCountryByNameOpt.get().getName());
+        assertEquals(country, foundCountryByNameOpt.get());
+    }
+
+    @Test
+    void updateTestNotFoundByIdThrowsException() {
+        var country = new Country();
+        country.setId(-1L);
+        country.setName("France");
+
+        when(countryRepository.findById(-1L))
+                .thenReturn(Optional.empty());
+
+        assertThrows(ResourceNotFoundException.class, () -> countryService.update(-1L, country));
+    }
+
+    @Test
+    void updateTestSuccess() {
+        var countryToUpdate = new Country();
+        countryToUpdate.setId(1L);
+        countryToUpdate.setName("France");
+
+        var updatedCountryRequest = new Country();
+        updatedCountryRequest.setName("Switzerland");
+
+        when(countryRepository.findById(1L))
+                .thenReturn(Optional.of(countryToUpdate));
+        when(countryRepository.save(countryToUpdate))
+                .thenReturn(updatedCountryRequest);
+
+        var updatedCountry = countryService.update(1L, updatedCountryRequest);
+
+        assertEquals(countryToUpdate.getName(), updatedCountry.getName());
+        assertEquals(countryToUpdate, updatedCountry);
+    }
+}
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/flight/FlightServiceImplTests.java b/core/src/test/java/cz/muni/fi/pa165/core/service/flight/FlightServiceImplTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..264273639ef4ac8cb99e4ef183ae6cbb71280684
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/flight/FlightServiceImplTests.java
@@ -0,0 +1,95 @@
+package cz.muni.fi.pa165.core.service.flight;
+
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.data.repository.flight.FlightRepository;
+import cz.muni.fi.pa165.core.service.flight.FlightServiceImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+
+public class FlightServiceImplTests {
+    private FlightServiceImpl flightService;
+    private FlightRepository flightRepository;
+
+
+    @BeforeEach
+    void setUp() {
+        flightRepository = mock(FlightRepository.class);
+        flightService = new FlightServiceImpl(flightRepository);
+    }
+
+    @Test
+    void findByIdTestSuccess() {
+        var flightDepartureTime = OffsetDateTime.now();
+        var flightArrivalTime = OffsetDateTime.now();
+        var flight = new Flight();
+        flight.setArrivalTime(flightArrivalTime);
+        flight.setDepartureTime(flightDepartureTime);
+
+        when(flightRepository.findById(flight.getId()))
+                .thenReturn(Optional.of(flight));
+
+        var foundFlightByIdOpt = flightService.findById(flight.getId());
+
+        assertTrue(foundFlightByIdOpt.isPresent());
+        assertEquals(flight.getArrivalTime(), foundFlightByIdOpt.get().getArrivalTime());
+        assertEquals(flight.getDepartureTime(), foundFlightByIdOpt.get().getDepartureTime());
+    }
+
+    @Test
+    void findWithStewardsTestSuccess() {
+        var flightDepartureTime = OffsetDateTime.now();
+        var flightArrivalTime = OffsetDateTime.now();
+        var flight = new Flight();
+        flight.setArrivalTime(flightArrivalTime);
+        flight.setDepartureTime(flightDepartureTime);
+
+        when(flightRepository.findByIdWithStewards(flight.getId()))
+                .thenReturn(Optional.of(flight));
+
+        var foundFlightByIdOpt = flightService.findByIdWithStewards(flight.getId());
+
+        assertTrue(foundFlightByIdOpt.isPresent());
+        assertEquals(flight.getFlightStewards(), foundFlightByIdOpt.get().getFlightStewards());
+    }
+
+    @Test
+    void updateTestSuccess() {
+        var flightToUpdate = new Flight();
+        flightToUpdate.setId(1L);
+        var time = OffsetDateTime.of(2000, 4, 9, 20, 15, 45, 345875000, ZoneOffset.of("+07:00"));
+        flightToUpdate.setDepartureTime(time);
+        flightToUpdate.setArrivalTime(time);
+
+
+        var updatedFlightRequest = new Flight();
+        var newTime = OffsetDateTime.of(1980, 4, 9, 20, 15, 45, 345875000, ZoneOffset.of("+07:00"));
+        updatedFlightRequest.setDepartureTime(newTime);
+        updatedFlightRequest.setArrivalTime(newTime);
+
+        when(flightRepository.findById(1L))
+                .thenReturn(Optional.of(flightToUpdate));
+        when(flightRepository.save(flightToUpdate))
+                .thenReturn(updatedFlightRequest);
+
+
+        assertEquals(time, flightToUpdate.getDepartureTime());
+        assertNotEquals(newTime, flightToUpdate.getDepartureTime());
+        assertEquals(time, flightToUpdate.getArrivalTime());
+        assertNotEquals(newTime, flightToUpdate.getArrivalTime());
+
+        var afterUpdate = flightService.update(1L, updatedFlightRequest);
+
+        assertEquals(afterUpdate.getDepartureTime(), flightToUpdate.getDepartureTime());
+
+    }
+
+}
diff --git a/core/src/test/java/cz/muni/fi/pa165/core/service/steward/StewardServiceImplTests.java b/core/src/test/java/cz/muni/fi/pa165/core/service/steward/StewardServiceImplTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..44575b2f0dd31de53021d54fe07c802dce0e0755
--- /dev/null
+++ b/core/src/test/java/cz/muni/fi/pa165/core/service/steward/StewardServiceImplTests.java
@@ -0,0 +1,105 @@
+package cz.muni.fi.pa165.core.service.steward;
+
+import cz.muni.fi.pa165.core.data.domain.Flight;
+import cz.muni.fi.pa165.core.data.domain.FlightSteward;
+import cz.muni.fi.pa165.core.data.domain.Steward;
+import cz.muni.fi.pa165.core.data.repository.flightsteward.FlightStewardRepository;
+import cz.muni.fi.pa165.core.data.repository.steward.StewardRepository;
+import cz.muni.fi.pa165.core.service.steward.StewardServiceImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import java.time.OffsetDateTime;
+import java.util.Optional;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+
+public class StewardServiceImplTests {
+    private StewardServiceImpl stewardService;
+    private StewardRepository stewardRepository;
+
+    @BeforeEach
+    void setUp() {
+        stewardRepository = mock(StewardRepository.class);
+        FlightStewardRepository flightStewardRepository = mock(FlightStewardRepository.class);
+        stewardService = new StewardServiceImpl(stewardRepository,
+                flightStewardRepository);
+    }
+
+    @Test
+    void findByIdTestSuccess() {
+        var firstName = "Ondra";
+        var lastName = "Nový";
+        var steward = new Steward();
+        steward.setFirstName(firstName);
+        steward.setLastName(lastName);
+
+        when(stewardRepository.findById(steward.getId()))
+                .thenReturn(Optional.of(steward));
+
+        var foundStewardByIdOpt = stewardService.findById(steward.getId());
+
+        assertTrue(foundStewardByIdOpt.isPresent());
+        assertEquals(foundStewardByIdOpt.get().getFirstName(), firstName);
+        assertEquals(foundStewardByIdOpt.get().getLastName(), lastName);
+    }
+
+    @Test
+    void assignToFlight() {
+        var firstName = "Ondra";
+        var lastName = "Nový";
+        var steward = new Steward();
+        steward.setFirstName(firstName);
+        steward.setLastName(lastName);
+
+        var flightDepartureTime = OffsetDateTime.now();
+        var flightArrivalTime = OffsetDateTime.now();
+        var flight = new Flight();
+        flight.setArrivalTime(flightArrivalTime);
+        flight.setDepartureTime(flightDepartureTime);
+
+        FlightSteward flightSteward = new FlightSteward();
+
+        flightSteward.setFlight(flight);
+        flightSteward.setSteward(steward);
+
+        assertEquals(flightSteward.getSteward(), steward);
+        assertEquals(flightSteward.getFlight(), flight);
+    }
+
+
+    @Test
+    void updateTestSuccess() {
+        var stewardToUpdate = new Steward();
+        stewardToUpdate.setId(1L);
+        var firstName = "Ondra";
+        var lastName = "Nový";
+        stewardToUpdate.setFirstName(firstName);
+        stewardToUpdate.setLastName(lastName);
+
+
+        var updatedStewardRequest = new Steward();
+        var newFirstName = "Jirka";
+        var newLastName = "Starý";
+        updatedStewardRequest.setFirstName(newFirstName);
+        updatedStewardRequest.setLastName(newLastName);
+
+        when(stewardRepository.findById(1L))
+                .thenReturn(Optional.of(stewardToUpdate));
+        when(stewardRepository.save(stewardToUpdate))
+                .thenReturn(updatedStewardRequest);
+
+
+        assertEquals(firstName, stewardToUpdate.getFirstName());
+        assertNotEquals(newFirstName, stewardToUpdate.getFirstName());
+        assertEquals(lastName, stewardToUpdate.getLastName());
+        assertNotEquals(newLastName, stewardToUpdate.getLastName());
+
+        var afterUpdate = stewardService.update(1L, updatedStewardRequest);
+
+        assertEquals(afterUpdate.getFirstName(), stewardToUpdate.getFirstName());
+    }
+
+}
+
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/locust-scenarios/locustfile.py b/locust-scenarios/locustfile.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c9665f114e8bf1ed6487af92c137e2774e4df79
--- /dev/null
+++ b/locust-scenarios/locustfile.py
@@ -0,0 +1,139 @@
+from locust import HttpUser, task
+
+
+class Admin(HttpUser):
+    auth_header = {'Authorization': 'Bearer eyJraWQiOiJyc2ExIiwidHlwIjoiYXQrand0IiwiYWxnIjoiUlMyNTYifQ.eyJhdWQiOiI3ZTAyYTBhOS00NDZhLTQxMmQtYWQyYi05MGFkZDQ3YjBmZGQiLCJzdWIiOiI0OTMyMzdAbXVuaS5jeiIsImFjciI6Imh0dHBzOi8vcmVmZWRzLm9yZy9wcm9maWxlL3NmYSIsInNjb3BlIjoidGVzdF80IHRlc3RfMyB0ZXN0XzIgdGVzdF8xIHRlc3RfcmVhZCBvcGVuaWQgcHJvZmlsZSBlZHVwZXJzb25fc2NvcGVkX2FmZmlsaWF0aW9uIHRlc3Rfd3JpdGUgZW1haWwgdGVzdF81IiwiYXV0aF90aW1lIjoxNjgzNDk1NDgwLCJpc3MiOiJodHRwczovL29pZGMubXVuaS5jei9vaWRjLyIsImV4cCI6MTY4MzQ5OTEzOSwiaWF0IjoxNjgzNDk1NTM5LCJjbGllbnRfaWQiOiI3ZTAyYTBhOS00NDZhLTQxMmQtYWQyYi05MGFkZDQ3YjBmZGQiLCJqdGkiOiIzMjgxZmJiYy01MWYxLTQ0ZGEtOGMzNi1hY2Q5OTM1N2QxYzUifQ.BVLruvROGTvJh6CmhtjKIhURSheMCOVPADKgI6P-qhTmbhi_jTT4gvnLHsAwImGehmwDvo2seLAGahRdv84iM3iYJ7WTblPTGhu9CI5SAE-59ROlGtUaA-3q8zL2xdOVtF5in_KjF8lrk2gI8uY4i3iRE-PZnm9EyK4qmgzcCXIiu9K3TAPnDtKPs8sileFj7_V6Xq4qmjq7g1dr_jGOcelesUgnq6fQlDLvb2guUwWHFTkWZQSnS-cEAX6ZkROkuzPi7d53YlQaPSVngjMYpd81y_DypDcUqbrzNYK28y5cBLbLZ2y2CWKqKyE4ENryc237AH_UcaVdFWwf6Vh2gQ'}
+    def on_start(self):
+
+    #create airplane type - just once because creating airplane type more times would report fail due to not unique name
+        self.client.post(":8080/api/airplaneTypes", json=
+        {
+            "name": "Práškovač 2000"
+        },
+        headers = self.auth_header)
+
+        response  = self.client.get(":8080/api/airplaneTypes/1")
+
+        if response.status_code == 200:
+            self.client.post(":8080/api/airplanes", json=
+            {
+                "name": "Prášek",
+                "capacity": 2,
+                "typeId": 1
+            },
+            headers = self.auth_header)
+
+        self.client.post(":8080/api/countries", json=
+        {
+            "name": "Slovensko"
+        },
+                         headers = self.auth_header)
+        self.client.post(":8080/api/cities", json=
+        {
+            "name": "Holíč"
+        },
+                         headers = self.auth_header)
+        self.client.post(":8080/api/cities", json=
+        {
+            "name": "Senica"
+        },
+                         headers = self.auth_header)
+        self.client.post(":8080/api/cities/1/countries/1", json=
+        {
+            "name": "Senica"
+        },
+                         headers = self.auth_header)
+        self.client.post(":8080/api/cities/2/countries/1", json=
+        {
+            "name": "Senica"
+        },
+                         headers = self.auth_header)
+        #create 2 airports - just once because airport need to have unique code
+        self.client.post(":8080/api/airports", json=
+        {
+            "name": "Travnik Holic",
+            "code": "THL",
+            "location": {
+                "latitude": 41.40338,
+                "longitude": 2.17403
+            }
+        },
+                         headers = self.auth_header)
+        self.client.post(":8080/api/airports", json=
+        {
+            "name": "Hliniste Senica",
+            "code": "HSL",
+            "location": {
+                "latitude": 41.40338,
+                "longitude": 2.17403
+            }
+        },
+                         headers = self.auth_header)
+
+    @task
+    def create_steward(self):
+        self.client.post(":8080/api/stewards", json=
+        {
+            "firstName": "John",
+            "lastName": "Doe"
+        },
+                         headers = self.auth_header)
+
+    @task
+    def create_flight(self):
+        self.client.post(":8080/api/flights", json=
+        {
+            "departureTime": "2023-12-22T12:04:04.493908908+01:00",
+            "arrivalTime": "2023-12-22T12:04:04.493908908+01:00",
+            "airplaneId": 1
+        },
+                         headers = self.auth_header)
+
+    @task
+    def assign_steward_flight(self):
+        response_steward = self.client.get(":8080/api/stewards/1", headers = self.auth_header)
+        response_flight = self.client.get(":8080/api/flights/1", headers = self.auth_header)
+
+        if response_steward.status_code == 200 and response_flight.status_code == 200:
+            self.client.post(":8080/api/stewards/1/flights/1", headers = self.auth_header)
+            self.client.delete(":8080/api/stewards/1/flights/1", headers = self.auth_header)
+
+    @task
+    def assign_airport_flight(self):
+        response_airport1 = self.client.get(":8080/api/airports/1", headers = self.auth_header)
+        response_airport2 = self.client.get(":8080/api/airports/2", headers = self.auth_header)
+        response_flight = self.client.get(":8080/api/flights/1", headers = self.auth_header)
+
+        if response_airport1.status_code == 200 and response_flight.status_code == 200 and response_airport2.status_code == 200:
+            self.client.post(":8080/api/airports/1/departingFlights/1", headers = self.auth_header)
+            self.client.post(":8080/api/airports/2/arrivingFlights/1", headers = self.auth_header)
+            self.client.delete(":8080/api/airports/1/departingFlights/1", headers = self.auth_header)
+            self.client.delete(":8080/api/airports/2/arrivingFlights/1", headers = self.auth_header)
+
+
+class BasicUser(HttpUser):
+    min_wait = 5000
+    max_wait = 15000
+    auth_header = {'Authorization': 'Bearer eyJraWQiOiJyc2ExIiwidHlwIjoiYXQrand0IiwiYWxnIjoiUlMyNTYifQ.eyJhdWQiOiI3ZTAyYTBhOS00NDZhLTQxMmQtYWQyYi05MGFkZDQ3YjBmZGQiLCJzdWIiOiI0OTMyMzdAbXVuaS5jeiIsImFjciI6Imh0dHBzOi8vcmVmZWRzLm9yZy9wcm9maWxlL3NmYSIsInNjb3BlIjoidGVzdF80IHRlc3RfMyB0ZXN0XzIgdGVzdF8xIHRlc3RfcmVhZCBvcGVuaWQgcHJvZmlsZSBlZHVwZXJzb25fc2NvcGVkX2FmZmlsaWF0aW9uIHRlc3Rfd3JpdGUgZW1haWwgdGVzdF81IiwiYXV0aF90aW1lIjoxNjgzNDk1NDgwLCJpc3MiOiJodHRwczovL29pZGMubXVuaS5jei9vaWRjLyIsImV4cCI6MTY4MzQ5OTEzOSwiaWF0IjoxNjgzNDk1NTM5LCJjbGllbnRfaWQiOiI3ZTAyYTBhOS00NDZhLTQxMmQtYWQyYi05MGFkZDQ3YjBmZGQiLCJqdGkiOiIzMjgxZmJiYy01MWYxLTQ0ZGEtOGMzNi1hY2Q5OTM1N2QxYzUifQ.BVLruvROGTvJh6CmhtjKIhURSheMCOVPADKgI6P-qhTmbhi_jTT4gvnLHsAwImGehmwDvo2seLAGahRdv84iM3iYJ7WTblPTGhu9CI5SAE-59ROlGtUaA-3q8zL2xdOVtF5in_KjF8lrk2gI8uY4i3iRE-PZnm9EyK4qmgzcCXIiu9K3TAPnDtKPs8sileFj7_V6Xq4qmjq7g1dr_jGOcelesUgnq6fQlDLvb2guUwWHFTkWZQSnS-cEAX6ZkROkuzPi7d53YlQaPSVngjMYpd81y_DypDcUqbrzNYK28y5cBLbLZ2y2CWKqKyE4ENryc237AH_UcaVdFWwf6Vh2gQ'}
+
+    @task
+    def get_airplane(self):
+        self.client.get(":8080/api/airplanes", headers = self.auth_header)
+    @task
+    def get_stewards(self):
+        self.client.get(":8080/api/stewards", headers = self.auth_header)
+    @task
+    def get_airports(self):
+        self.client.get(":8080/api/airports", headers = self.auth_header)
+    @task
+    def get_flights(self):
+        self.client.get(":8080/api/flights", headers = self.auth_header)
+    @task
+    def generate_airplane_report(self):
+        self.client.get(":8085/api/reports/airplane/1", headers = self.auth_header)
+    @task
+    def generate_airport_report(self):
+        self.client.get(":8085/api/reports/airport/1", headers = self.auth_header)
+    @task
+    def generate_flight_report(self):
+        self.client.get(":8085/api/reports/flight/1", headers = self.auth_header)
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000000000000000000000000000000000000..8d937f4c14f11f8c3aded556054af0b0097eaede
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+      else
+        JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=$(java-config --jre-home)
+  fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+    JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="$(which javac)"
+  if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=$(which readlink)
+    if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+      if $darwin ; then
+        javaHome="$(dirname "\"$javaExecutable\"")"
+        javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+      else
+        javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+      fi
+      javaHome="$(dirname "\"$javaExecutable\"")"
+      javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=$(cd "$wdir/.." || exit 1; pwd)
+    fi
+    # end of workaround
+  done
+  printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    # Remove \r in case we run on Windows within Git Bash
+    # and check out the repository with auto CRLF management
+    # enabled. Otherwise, we may read lines that are delimited with
+    # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+    # splitting rules.
+    tr -s '\r\n' ' ' < "$1"
+  fi
+}
+
+log() {
+  if [ "$MVNW_VERBOSE" = true ]; then
+    printf '%s\n' "$1"
+  fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+    log "Found $wrapperJarPath"
+else
+    log "Couldn't find $wrapperJarPath, downloading it ..."
+
+    if [ -n "$MVNW_REPOURL" ]; then
+      wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    else
+      wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    fi
+    while IFS="=" read -r key value; do
+      # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+      safeValue=$(echo "$value" | tr -d '\r')
+      case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+      esac
+    done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+    log "Downloading from: $wrapperUrl"
+
+    if $cygwin; then
+      wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+    fi
+
+    if command -v wget > /dev/null; then
+        log "Found wget ... using wget"
+        [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        else
+            wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        log "Found curl ... using curl"
+        [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+        else
+            curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+        fi
+    else
+        log "Falling back to using Java to download"
+        javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaSource=$(cygpath --path --windows "$javaSource")
+          javaClass=$(cygpath --path --windows "$javaClass")
+        fi
+        if [ -e "$javaSource" ]; then
+            if [ ! -e "$javaClass" ]; then
+                log " - Compiling MavenWrapperDownloader.java ..."
+                ("$JAVA_HOME/bin/javac" "$javaSource")
+            fi
+            if [ -e "$javaClass" ]; then
+                log " - Running MavenWrapperDownloader.java ..."
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+  case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+  esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+  wrapperSha256Result=false
+  if command -v sha256sum > /dev/null; then
+    if echo "$wrapperSha256Sum  $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+      wrapperSha256Result=true
+    fi
+  elif command -v shasum > /dev/null; then
+    if echo "$wrapperSha256Sum  $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+      wrapperSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+    echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+    exit 1
+  fi
+  if [ $wrapperSha256Result = false ]; then
+    echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+    echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+    echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000000000000000000000000000000000000..c4586b564e6fa1bc22ae740b1308b258581f1c35
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %WRAPPER_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+    powershell -Command "&{"^
+       "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+       "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+       "  Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+       "  Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+       "  Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+       "  exit 1;"^
+       "}"^
+       "}"
+    if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+  %JVM_CONFIG_MAVEN_PROPS% ^
+  %MAVEN_OPTS% ^
+  %MAVEN_DEBUG_OPTS% ^
+  -classpath %WRAPPER_JAR% ^
+  "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
index 23299b4757d0ae1324a999ddc78c392bc514436e..903a024b151357f7411a228b4db8b6083d8729a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,11 +7,11 @@
     <version>1.0-SNAPSHOT</version>
     <modules>
         <module>core</module>
-        <module>authorization</module>
-        <module>authorization-client</module>
+        <module>user</module>
         <module>report</module>
         <module>weather</module>
         <module>core-client</module>
+        <module>user-client</module>
         <module>report-client</module>
         <module>weather-client</module>
     </modules>
@@ -29,6 +29,18 @@
             <id>540485</id>
             <name>Martin Slovik</name>
         </developer>
+        <developer>
+            <id>492778</id>
+            <name>Matej Hrica</name>
+        </developer>
+        <developer>
+            <id>492892</id>
+            <name>Adam Krídl</name>
+        </developer>
+        <developer>
+            <id>493237</id>
+            <name>Ján Macháček</name>
+        </developer>
     </developers>
     <parent>
         <groupId>org.springframework.boot</groupId>
@@ -38,6 +50,8 @@
         <!-- lookup parent from repository -->
     </parent>
     <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <maven.compiler.source>17</maven.compiler.source>
         <maven.compiler.target>17</maven.compiler.target>
         <lombok.version>1.18.26</lombok.version>
@@ -61,7 +75,7 @@
             </dependency>
             <dependency>
                 <groupId>cz.muni.fi.pa165</groupId>
-                <artifactId>authorization</artifactId>
+                <artifactId>user</artifactId>
                 <version>${version}</version>
             </dependency>
             <dependency>
@@ -79,6 +93,11 @@
                 <artifactId>mapstruct</artifactId>
                 <version>${org.mapstruct.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.modelmapper</groupId>
+                <artifactId>modelmapper</artifactId>
+                <version>3.0.0</version>
+            </dependency>
             <dependency>
                 <groupId>org.springdoc</groupId>
                 <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
@@ -104,11 +123,6 @@
                 <artifactId>swagger-annotations</artifactId>
                 <version>1.6.9</version>
             </dependency>
-            <dependency>
-                <groupId>javax.validation</groupId>
-                <artifactId>validation-api</artifactId>
-                <version>2.0.1.Final</version>
-            </dependency>
             <dependency>
                 <groupId>javax.annotation</groupId>
                 <artifactId>javax.annotation-api</artifactId>
@@ -124,6 +138,11 @@
                 <artifactId>swagger-annotations-jakarta</artifactId>
                 <version>${swagger-jakarta-version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+                <version>3.0.4</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
     <build>
@@ -176,7 +195,7 @@
                 <plugin>
                     <groupId>org.openapitools</groupId>
                     <artifactId>openapi-generator-maven-plugin</artifactId>
-                    <version>6.4.0</version>
+                    <version>6.5.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.springdoc</groupId>
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/Dockerfile b/report/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d131222e38617e1c16b7e0e8e265b68f253b1849
--- /dev/null
+++ b/report/Dockerfile
@@ -0,0 +1,3 @@
+FROM eclipse-temurin:17-jdk-alpine
+COPY target/*.jar /app.jar
+ENTRYPOINT ["java", "-jar", "/app.jar"]
\ No newline at end of file
diff --git a/report/LICENSE.md b/report/LICENSE.md
new file mode 100644
index 0000000000000000000000000000000000000000..34cba43a0f19b73f2ceda1ee4ab7429e50ee365e
--- /dev/null
+++ b/report/LICENSE.md
@@ -0,0 +1,651 @@
+GNU Affero General Public License
+=================================
+
+_Version 3, 19 November 2007_
+_Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+## Preamble
+
+The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+Developers that use our General Public Licenses protect your rights
+with two steps: **(1)** assert copyright on the software, and **(2)** offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+## TERMS AND CONDITIONS
+
+### 0. Definitions
+
+“This License” refers to version 3 of the GNU Affero General Public License.
+
+“Copyright” also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+“The Program” refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as “you”.  “Licensees” and
+“recipients” may be individuals or organizations.
+
+To “modify” a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a “modified version” of the
+earlier work or a work “based on” the earlier work.
+
+A “covered work” means either the unmodified Program or a work based
+on the Program.
+
+To “propagate” a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+To “convey” a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+An interactive user interface displays “Appropriate Legal Notices”
+to the extent that it includes a convenient and prominently visible
+feature that **(1)** displays an appropriate copyright notice, and **(2)**
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+### 1. Source Code
+
+The “source code” for a work means the preferred form of the work
+for making modifications to it.  “Object code” means any non-source
+form of a work.
+
+A “Standard Interface” means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+The “System Libraries” of an executable work include anything, other
+than the work as a whole, that **(a)** is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and **(b)** serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+“Major Component”, in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+The “Corresponding Source” for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+The Corresponding Source for a work in source code form is that
+same work.
+
+### 2. Basic Permissions
+
+All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
+
+No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+### 4. Conveying Verbatim Copies
+
+You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+### 5. Conveying Modified Source Versions
+
+You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+* **a)** The work must carry prominent notices stating that you modified
+  it, and giving a relevant date.
+* **b)** The work must carry prominent notices stating that it is
+  released under this License and any conditions added under section 7.
+  This requirement modifies the requirement in section 4 to
+  “keep intact all notices”.
+* **c)** You must license the entire work, as a whole, under this
+  License to anyone who comes into possession of a copy.  This
+  License will therefore apply, along with any applicable section 7
+  additional terms, to the whole of the work, and all its parts,
+  regardless of how they are packaged.  This License gives no
+  permission to license the work in any other way, but it does not
+  invalidate such permission if you have separately received it.
+* **d)** If the work has interactive user interfaces, each must display
+  Appropriate Legal Notices; however, if the Program has interactive
+  interfaces that do not display Appropriate Legal Notices, your
+  work need not make them do so.
+
+A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+“aggregate” if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+### 6. Conveying Non-Source Forms
+
+You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+* **a)** Convey the object code in, or embodied in, a physical product
+  (including a physical distribution medium), accompanied by the
+  Corresponding Source fixed on a durable physical medium
+  customarily used for software interchange.
+* **b)** Convey the object code in, or embodied in, a physical product
+  (including a physical distribution medium), accompanied by a
+  written offer, valid for at least three years and valid for as
+  long as you offer spare parts or customer support for that product
+  model, to give anyone who possesses the object code either **(1)** a
+  copy of the Corresponding Source for all the software in the
+  product that is covered by this License, on a durable physical
+  medium customarily used for software interchange, for a price no
+  more than your reasonable cost of physically performing this
+  conveying of source, or **(2)** access to copy the
+  Corresponding Source from a network server at no charge.
+* **c)** Convey individual copies of the object code with a copy of the
+  written offer to provide the Corresponding Source.  This
+  alternative is allowed only occasionally and noncommercially, and
+  only if you received the object code with such an offer, in accord
+  with subsection 6b.
+* **d)** Convey the object code by offering access from a designated
+  place (gratis or for a charge), and offer equivalent access to the
+  Corresponding Source in the same way through the same place at no
+  further charge.  You need not require recipients to copy the
+  Corresponding Source along with the object code.  If the place to
+  copy the object code is a network server, the Corresponding Source
+  may be on a different server (operated by you or a third party)
+  that supports equivalent copying facilities, provided you maintain
+  clear directions next to the object code saying where to find the
+  Corresponding Source.  Regardless of what server hosts the
+  Corresponding Source, you remain obligated to ensure that it is
+  available for as long as needed to satisfy these requirements.
+* **e)** Convey the object code using peer-to-peer transmission, provided
+  you inform other peers where the object code and Corresponding
+  Source of the work are being offered to the general public at no
+  charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+A “User Product” is either **(1)** a “consumer product”, which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or **(2)** anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, “normally used” refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+“Installation Information” for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+### 7. Additional Terms
+
+“Additional permissions” are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+* **a)** Disclaiming warranty or limiting liability differently from the
+  terms of sections 15 and 16 of this License; or
+* **b)** Requiring preservation of specified reasonable legal notices or
+  author attributions in that material or in the Appropriate Legal
+  Notices displayed by works containing it; or
+* **c)** Prohibiting misrepresentation of the origin of that material, or
+  requiring that modified versions of such material be marked in
+  reasonable ways as different from the original version; or
+* **d)** Limiting the use for publicity purposes of names of licensors or
+  authors of the material; or
+* **e)** Declining to grant rights under trademark law for use of some
+  trade names, trademarks, or service marks; or
+* **f)** Requiring indemnification of licensors and authors of that
+  material by anyone who conveys the material (or modified versions of
+  it) with contractual assumptions of liability to the recipient, for
+  any liability that these contractual assumptions directly impose on
+  those licensors and authors.
+
+All other non-permissive additional terms are considered “further
+restrictions” within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+### 8. Termination
+
+You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated **(a)**
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and **(b)** permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+### 9. Acceptance Not Required for Having Copies
+
+You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+### 10. Automatic Licensing of Downstream Recipients
+
+Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+An “entity transaction” is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+### 11. Patents
+
+A “contributor” is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's “contributor version”.
+
+A contributor's “essential patent claims” are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, “control” includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+In the following three paragraphs, a “patent license” is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To “grant” such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either **(1)** cause the Corresponding Source to be so
+available, or **(2)** arrange to deprive yourself of the benefit of the
+patent license for this particular work, or **(3)** arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  “Knowingly relying” means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+A patent license is “discriminatory” if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license **(a)** in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or **(b)** primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+### 12. No Surrender of Others' Freedom
+
+If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+### 13. Remote Network Interaction; Use with the GNU General Public License
+
+Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+### 14. Revised Versions of this License
+
+The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License “or any later version” applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+### 15. Disclaimer of Warranty
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+### 16. Limitation of Liability
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+### 17. Interpretation of Sections 15 and 16
+
+If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+_END OF TERMS AND CONDITIONS_
+
+## How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the “copyright” line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a “Source” link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+You should also get your employer (if you work as a programmer) or school,
+if any, to sign a “copyright disclaimer” for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+&lt;<http://www.gnu.org/licenses/>&gt;.
\ No newline at end of file
diff --git a/report/openapi.yaml b/report/openapi.yaml
index 5d4b521de2e8d3a1ddee8746fc4c75ca871eaea8..487f7a9a85290dc90577c7892886d352baed4919 100644
--- a/report/openapi.yaml
+++ b/report/openapi.yaml
@@ -27,6 +27,8 @@ servers:
 tags:
   - name: Report
     description: Microservice for report pdf report.
+security:
+  - BearerAuth: []
 paths:
   /api/reports/flight/{id}:
     get:
@@ -57,6 +59,12 @@ paths:
             application/json:
               schema:
                 $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
+        "500":
+          description: Internal server error
+          content:
+            application/json:
+              schema:
+                $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
 
   /api/reports/airport/{id}:
     get:
@@ -87,6 +95,12 @@ paths:
             application/json:
               schema:
                 $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
+        "500":
+          description: Internal server error
+          content:
+            application/json:
+              schema:
+                $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
 
   /api/reports/airplane/{id}:
     get:
@@ -116,4 +130,16 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
\ No newline at end of file
+                $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
+        "500":
+          description: Internal server error
+          content:
+            application/json:
+              schema:
+                $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
+components:
+  securitySchemes:
+    BearerAuth:
+      type: http
+      description: "OAuth2 Resource Server, provide a valid access token"
+      scheme: bearer
diff --git a/report/pom.xml b/report/pom.xml
index cec25612c55c5f8820a7d635cf33eab759bd23d9..8594269c13bf2e55d23d39706ca63f2b98e87122 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>
@@ -41,11 +53,6 @@
             <artifactId>jakarta.annotation-api</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>jakarta.validation</groupId>
-            <artifactId>jakarta.validation-api</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>io.swagger.core.v3</groupId>
             <artifactId>swagger-models-jakarta</artifactId>
@@ -62,13 +69,14 @@
         </dependency>
 
         <dependency>
-            <groupId>javax.validation</groupId>
-            <artifactId>validation-api</artifactId>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>org.springdoc</groupId>
-            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <groupId>cz.muni.fi.pa165</groupId>
+            <artifactId>user-client</artifactId>
+            <version>1.0-SNAPSHOT</version>
         </dependency>
 
         <!-- for pagination from JPA without actually using JPA -->
@@ -89,18 +97,34 @@
             <artifactId>itextpdf</artifactId>
             <version>5.5.10</version>
         </dependency>
-
         <dependency>
             <groupId>org.apache.pdfbox</groupId>
             <artifactId>pdfbox</artifactId>
             <version>2.0.4</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
             <version>1.56</version>
         </dependency>
+        
+        <dependency>
+            <groupId>cz.muni.fi.pa165</groupId>
+            <artifactId>core-client</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cz.muni.fi.pa165</groupId>
+            <artifactId>user-client</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/ReportController.java b/report/src/main/java/cz/muni/fi/pa165/report/server/ReportController.java
deleted file mode 100644
index 4e0ed11a53822f7cf4470017e836e8d854e042c9..0000000000000000000000000000000000000000
--- a/report/src/main/java/cz/muni/fi/pa165/report/server/ReportController.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package cz.muni.fi.pa165.report.server;
-import cz.muni.fi.pa165.report.server.api.ReportApiDelegate;
-import org.springframework.core.io.ByteArrayResource;
-import org.springframework.core.io.Resource;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.io.*;
-
-
-@RestController
-public class ReportController implements ReportApiDelegate{
-
-    @Override
-    public ResponseEntity<Resource> getReportFlightById(Long id){
-
-        ClassLoader classLoader = getClass().getClassLoader();
-
-        HttpHeaders headers = new HttpHeaders();
-        headers.add("Content-Type", "application/pdf");
-        headers.add("Content-Disposition", "attachment; filename=labels.pdf");
-
-        // the stream holding the file content
-
-        // funny, if can use Java 7, please uses Files.readAllBytes(path)
-        try(InputStream inputStream = classLoader.getResourceAsStream("sample.pdf")){
-            byte[] bytes = new byte[inputStream.available()];
-            inputStream.read(bytes);
-            ByteArrayResource resource = new ByteArrayResource(bytes);
-            return new ResponseEntity<>(resource, headers, HttpStatus.OK);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public ResponseEntity<Resource> getReportAirportById(Long id){
-
-        return getReportFlightById(id);
-    }
-    @Override
-    public ResponseEntity<Resource> getReportAirplaneById(Long id){
-
-        return getReportFlightById(id);
-    }
-}
-
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/ReportType.java b/report/src/main/java/cz/muni/fi/pa165/report/server/ReportType.java
new file mode 100644
index 0000000000000000000000000000000000000000..302271f3eec33a75c886a53d12fd38ed3a267132
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/ReportType.java
@@ -0,0 +1,5 @@
+package cz.muni.fi.pa165.report.server;
+
+public enum ReportType {
+    AIRPLANE, AIRPORT, FLIGHT
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/config/AppConfig.java b/report/src/main/java/cz/muni/fi/pa165/report/server/config/AppConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5c3a4299a9aeb80b84b467e021bf24e847194d0
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/config/AppConfig.java
@@ -0,0 +1,31 @@
+package cz.muni.fi.pa165.report.server.config;
+
+import cz.muni.fi.pa165.user.client.Authorities;
+import cz.muni.fi.pa165.user.client.UserServiceInterceptionConfigurer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@Import(UserServiceInterceptionConfigurer.class)
+public class AppConfig {
+
+    @Bean
+    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http.authorizeHttpRequests(x -> x
+                        // swagger:
+                        .requestMatchers("/swagger-ui/**").permitAll()
+                        .requestMatchers("/v3/api-docs/**").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/swagger-ui.html").permitAll()
+                        // Manager has access to all reports
+                        .anyRequest().hasAuthority(Authorities.MANAGER)
+                )
+                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
+        return http.build();
+    }
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/InternalServerErrorAdvice.java b/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/InternalServerErrorAdvice.java
new file mode 100644
index 0000000000000000000000000000000000000000..ead20c06a22c89bd5a84d05c66ff3abdefbeedbf
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/InternalServerErrorAdvice.java
@@ -0,0 +1,27 @@
+package cz.muni.fi.pa165.report.server.exceptions;
+
+import cz.muni.fi.pa165.report.server.model.ErrorMessage;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import java.time.OffsetDateTime;
+
+@ControllerAdvice
+public class InternalServerErrorAdvice {
+
+    @ExceptionHandler(ResourceNotFoundException.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    ResponseEntity<ErrorMessage> handleInternalServerErrorException(InternalServerErrorException ex) {
+        ErrorMessage errorMessage = new ErrorMessage();
+        errorMessage.setTimestamp(OffsetDateTime.now());
+        errorMessage.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+        errorMessage.setError(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
+        errorMessage.setMessage(ex.getMessage());
+        errorMessage.setPath(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
+        return new ResponseEntity<>(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/InternalServerErrorException.java b/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/InternalServerErrorException.java
new file mode 100644
index 0000000000000000000000000000000000000000..55166249371715d43ab9bcc8fb84d2a5571a377f
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/InternalServerErrorException.java
@@ -0,0 +1,24 @@
+package cz.muni.fi.pa165.report.server.exceptions;
+
+public class InternalServerErrorException extends RuntimeException {
+
+    public InternalServerErrorException() {
+        super("Internal Server Error");
+    }
+
+    public InternalServerErrorException(String message) {
+        super(message);
+    }
+
+    public InternalServerErrorException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InternalServerErrorException(Throwable cause) {
+        super(cause);
+    }
+
+    public InternalServerErrorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/ResourceNotFoundAdvice.java b/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/ResourceNotFoundAdvice.java
new file mode 100644
index 0000000000000000000000000000000000000000..f92a78bca368a7b48c40d9beee2b5ce3d519c03c
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/ResourceNotFoundAdvice.java
@@ -0,0 +1,27 @@
+package cz.muni.fi.pa165.report.server.exceptions;
+
+import cz.muni.fi.pa165.report.server.model.ErrorMessage;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import java.time.OffsetDateTime;
+
+@ControllerAdvice
+public class ResourceNotFoundAdvice {
+
+    @ExceptionHandler(ResourceNotFoundException.class)
+    @ResponseStatus(HttpStatus.NOT_FOUND)
+    ResponseEntity<ErrorMessage> handleResourceNotFoundException(ResourceNotFoundException ex) {
+        ErrorMessage errorMessage = new ErrorMessage();
+        errorMessage.setTimestamp(OffsetDateTime.now());
+        errorMessage.setStatus(HttpStatus.NOT_FOUND.value());
+        errorMessage.setError(HttpStatus.NOT_FOUND.getReasonPhrase());
+        errorMessage.setMessage(ex.getMessage());
+        errorMessage.setPath(ServletUriComponentsBuilder.fromCurrentRequestUri().toUriString());
+        return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND);
+    }
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/ResourceNotFoundException.java b/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/ResourceNotFoundException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a48981c59af402fef4edd4145ae1449f97b48bb
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/exceptions/ResourceNotFoundException.java
@@ -0,0 +1,24 @@
+package cz.muni.fi.pa165.report.server.exceptions;
+
+public class ResourceNotFoundException extends RuntimeException {
+
+    public ResourceNotFoundException() {
+        super("Resource Not Found");
+    }
+
+    public ResourceNotFoundException(String message) {
+        super(message);
+    }
+
+    public ResourceNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ResourceNotFoundException(Throwable cause) {
+        super(cause);
+    }
+
+    public ResourceNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/facade/ReportFacade.java b/report/src/main/java/cz/muni/fi/pa165/report/server/facade/ReportFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..32a1712751e17f3e52d97598f0f90d8ea4080d13
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/facade/ReportFacade.java
@@ -0,0 +1,12 @@
+package cz.muni.fi.pa165.report.server.facade;
+
+import org.springframework.core.io.Resource;
+
+public interface ReportFacade {
+
+    Resource getReportFlightById(Long id);
+
+    Resource getReportAirportById(Long id);
+
+    Resource getReportAirplaneById(Long id);
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/facade/ReportFacadeImpl.java b/report/src/main/java/cz/muni/fi/pa165/report/server/facade/ReportFacadeImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..a84d5c3cbce71cd69454ba2634eb9b31f292e01a
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/facade/ReportFacadeImpl.java
@@ -0,0 +1,176 @@
+package cz.muni.fi.pa165.report.server.facade;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import cz.muni.fi.pa165.core.client.*;
+import cz.muni.fi.pa165.core.client.invoker.ApiClient;
+import cz.muni.fi.pa165.core.client.invoker.ApiException;
+import cz.muni.fi.pa165.core.client.model.*;
+import cz.muni.fi.pa165.report.server.ReportType;
+import cz.muni.fi.pa165.report.server.exceptions.ResourceNotFoundException;
+import cz.muni.fi.pa165.report.server.service.ReportDocumentService;
+import cz.muni.fi.pa165.user.client.UserApi;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
+import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReportFacadeImpl implements ReportFacade{
+
+    private final ReportDocumentService reportDocumentService;
+    Map<String, List<String>> documentData;
+
+    public ReportFacadeImpl(ReportDocumentService reportDocumentService){
+        this.reportDocumentService = reportDocumentService;
+        this.documentData = new HashMap<>();
+    }
+
+    ApiClient getApiClient() {
+        var client = new ApiClient();
+        client.setRequestInterceptor((builder -> {
+            var authentication = SecurityContextHolder.getContext().getAuthentication();
+            var authPrincipal = (OAuth2IntrospectionAuthenticatedPrincipal) authentication.getPrincipal();
+            var token = (OAuth2AccessToken) (((BearerTokenAuthentication) authentication).getToken());
+            builder.header("Authorization", "Bearer " + token.getTokenValue());
+        }));
+        return client;
+    }
+
+    @Override
+    public Resource getReportAirportById(Long id){
+
+        AirportDto airport;
+        CityDto cityDto;
+        CountryDto countryDto;
+
+        var client = getApiClient();
+        CountryApi countryApi = new CountryApi(client);
+        CityApi cityApi = new CityApi(client);
+        AirportApi airportApi = new AirportApi(client);
+        List<String> airportData = new ArrayList<>();
+        List<String> cityData = new ArrayList<>();
+
+        //Getting airport data and then calling service creating pdf document from them
+        try {
+            airport = airportApi.getAirportById(id);
+
+            airportData.add("Airport " + airport.getName());
+            airportData.add("The code of airport is " + airport.getCode());
+            airportData.add("Location of airport " + airport.getName() + " is: \n"
+            + airport.getLocation());
+            documentData.put("airport", airportData);
+
+            if(airport.getCityId() != null && airport.getCityId() != 0) {
+                cityDto = cityApi.getCityById(airport.getCityId());
+                cityData.add("Airport is located in the city " + cityDto.getName());
+
+                if (cityDto.getCountryId() != null && cityDto.getCountryId() != 0){
+                    countryDto = countryApi.getCountryById(cityDto.getCountryId());
+                    cityData.add("Country of this city is " + countryDto.getName());
+                }
+            }
+            documentData.put("airportCity", cityData);
+
+
+        } catch (ApiException e) {
+            throw new ResourceNotFoundException(e);
+        }
+        return new ByteArrayResource(reportDocumentService.reportDocument(documentData, "test", ReportType.AIRPORT).toByteArray());
+    }
+
+    @Override
+    public Resource getReportFlightById(Long id){
+        var client = getApiClient();
+        FlightApi flightApi = new FlightApi(client);
+        StewardApi stewardApi = new StewardApi(client);
+        AirplaneApi airplaneApi = new AirplaneApi(client);
+        AirportApi airportApi = new AirportApi(client);
+        AirplaneDto airplaneDto;
+        AirportDto arrivalAirportDto;
+        AirportDto departureAirportDto;
+        FlightDto flight;
+        List<String> flightData = new ArrayList<>();
+        List<String> stewardsData = new ArrayList<>();
+        List<String> airplaneData = new ArrayList<>();
+        List<String> airportsData = new ArrayList<>();
+
+        //Getting flight data and then calling service creating pdf document from them
+        try {
+            flight = flightApi.getFlightById(id);
+
+            flightData.add("Arrival time of flight is " + flight.getArrivalTime());
+            flightData.add("Departure time of flight is " + flight.getDepartureTime());
+            documentData.put("flight", flightData);
+
+            if( flight.getAssignedStewardIds() != null){
+            flight.getAssignedStewardIds().forEach(stewardId ->
+                    {
+                        StewardDto stewardDto;
+                        try {
+                            stewardDto = stewardApi.getSteward(stewardId);
+                        } catch (ApiException e) {
+                            throw new ResourceNotFoundException(e);
+                        }
+                        stewardsData.add(stewardDto.getFirstName() + " " + stewardDto.getLastName());
+                    });
+            }
+            documentData.put("stewards", stewardsData);
+
+            if(flight.getAirplaneId() != null && flight.getAirplaneId() != 0) {
+                airplaneDto = airplaneApi.getAirplaneById(flight.getAirplaneId());
+                airplaneData.add("Airplane name is " + airplaneDto.getName());
+                airplaneData.add("Airplane capacity is " + airplaneDto.getCapacity());
+            }
+            documentData.put("airplane", airplaneData);
+
+            if (flight.getArrivalAirportId() != null && flight.getArrivalAirportId() != 0) {
+                arrivalAirportDto = airportApi.getAirportById(flight.getArrivalAirportId());
+                airportsData.add("Arrival airport is " + arrivalAirportDto.getName());
+            }
+            if (flight.getDepartureAirportId() != null && flight.getDepartureAirportId() != 0) {
+                departureAirportDto = airportApi.getAirportById(flight.getDepartureAirportId());
+                airportsData.add("Departure airport is " + departureAirportDto.getName());
+            }
+            documentData.put("airports", airportsData);
+
+        } catch (ApiException e) {
+            throw new ResourceNotFoundException(e);
+        }
+        return new ByteArrayResource(reportDocumentService.reportDocument(documentData, "Flight", ReportType.FLIGHT).toByteArray());
+    }
+
+    @Override
+    public Resource getReportAirplaneById(Long id){
+        var client = getApiClient();
+        AirplaneApi airplaneApi = new AirplaneApi(client);
+        AirplaneTypeApi airplaneTypeApi = new AirplaneTypeApi(client);
+        AirplaneTypeDto airplaneTypeDto;
+        AirplaneDto airplane;
+        List<String> airplaneData = new ArrayList<>();
+        List<String> airplaneTypeData = new ArrayList<>();
+
+        //Getting airplane data and then calling service creating pdf document from them
+        try {
+            airplane = airplaneApi.getAirplaneById(id);
+
+            airplaneData.add("Airplane name: " + airplane.getName());
+            airplaneData.add("Airplane capacity: " + airplane.getCapacity());
+            documentData.put("airplane", airplaneData);
+
+            airplaneTypeDto = airplaneTypeApi.getAirplaneTypeById(airplane.getTypeId());
+            airplaneTypeData.add("Airplane type name: " + airplaneTypeDto.getName());
+            documentData.put("airplaneType", airplaneTypeData);
+
+        } catch (ApiException e) {
+            throw new ResourceNotFoundException(e);
+        }
+        return new ByteArrayResource(reportDocumentService.reportDocument(documentData, "Airplane", ReportType.AIRPLANE).toByteArray());
+    }
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/rest/ReportController.java b/report/src/main/java/cz/muni/fi/pa165/report/server/rest/ReportController.java
new file mode 100644
index 0000000000000000000000000000000000000000..2cf3b9ee7f7b7f7828ce61b7df68dc1c1a6ed810
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/rest/ReportController.java
@@ -0,0 +1,54 @@
+package cz.muni.fi.pa165.report.server.rest;
+import cz.muni.fi.pa165.report.server.api.ReportApiDelegate;
+import cz.muni.fi.pa165.report.server.facade.ReportFacade;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@RestController
+public class ReportController implements ReportApiDelegate{
+    private final ReportFacade reportFacade;
+    @Autowired
+    public ReportController(ReportFacade reportFacade) {
+        this.reportFacade = reportFacade;
+    }
+    HttpHeaders headers = new HttpHeaders();
+    private HttpHeaders initializeHeaders(){
+        headers.clear();
+        headers.add("Content-Type", "application/pdf");
+        headers.add("Content-Disposition", "attachment; filename=report.pdf");
+        return headers;
+    }
+    @Override
+    public ResponseEntity<Resource> getReportFlightById(Long id){
+
+        Resource resource = reportFacade.getReportFlightById(id);
+
+        headers = initializeHeaders();
+
+        return new ResponseEntity<>(resource, headers, HttpStatus.OK);
+    }
+    @Override
+    public ResponseEntity<Resource> getReportAirportById(Long id){
+
+        Resource resource = reportFacade.getReportAirportById(id);
+
+        headers = initializeHeaders();
+
+        return new ResponseEntity<>(resource, headers, HttpStatus.OK);
+    }
+    @Override
+    public ResponseEntity<Resource> getReportAirplaneById(Long id){
+
+        Resource resource = reportFacade.getReportAirplaneById(id);
+
+        headers = initializeHeaders();
+
+        return new ResponseEntity<>(resource, headers, HttpStatus.OK);
+    }
+}
+
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/service/ReportDocumentService.java b/report/src/main/java/cz/muni/fi/pa165/report/server/service/ReportDocumentService.java
new file mode 100644
index 0000000000000000000000000000000000000000..c76044329542021a580df9ab0624cf75b1fcd7fe
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/service/ReportDocumentService.java
@@ -0,0 +1,11 @@
+package cz.muni.fi.pa165.report.server.service;
+
+import cz.muni.fi.pa165.report.server.ReportType;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import java.util.Map;
+
+public interface ReportDocumentService {
+    ByteArrayOutputStream reportDocument(Map<String, List<String>> data, String name, ReportType reportType);
+}
diff --git a/report/src/main/java/cz/muni/fi/pa165/report/server/service/ReportDocumentServiceImpl.java b/report/src/main/java/cz/muni/fi/pa165/report/server/service/ReportDocumentServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..89bb0d1f858f727e6be81d6cacfbe3125ecebc31
--- /dev/null
+++ b/report/src/main/java/cz/muni/fi/pa165/report/server/service/ReportDocumentServiceImpl.java
@@ -0,0 +1,121 @@
+package cz.muni.fi.pa165.report.server.service;
+
+import com.itextpdf.text.*;
+import java.util.List;
+import com.itextpdf.text.pdf.PdfWriter;
+import cz.muni.fi.pa165.report.server.ReportType;
+import cz.muni.fi.pa165.report.server.exceptions.InternalServerErrorException;
+import org.springframework.stereotype.Service;
+import java.io.ByteArrayOutputStream;
+import java.util.Map;
+
+@Service
+public class ReportDocumentServiceImpl implements ReportDocumentService{
+
+    private static final Font CATFONT = new Font(Font.FontFamily.TIMES_ROMAN, 18,
+            Font.BOLD);
+    private static final Font SUBFONT = new Font(Font.FontFamily.TIMES_ROMAN, 16,
+            Font.BOLD);
+
+    @Override
+    public ByteArrayOutputStream reportDocument(Map<String, List<String>> data, String name, ReportType reportType) {
+        // Creating empty document and according to type calling methods inserting data to it
+        com.itextpdf.text.Document document = new com.itextpdf.text.Document(PageSize.A4, 50, 50, 50, 50);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        PdfWriter writer;
+        try {
+            writer = PdfWriter.getInstance(document, out);
+        } catch (DocumentException e) {
+            throw new InternalServerErrorException(e);
+        }
+        document.open();
+        try {
+            addMetaData(document, name, name, name);
+            switch (reportType) {
+                case AIRPLANE -> airplaneData(document, data);
+                case FLIGHT -> flightData(document, data);
+                case AIRPORT -> airportData(document, data);
+            }
+        }
+        catch (DocumentException e) {
+            throw new InternalServerErrorException(e);
+        }
+        document.close();
+        writer.close();
+        return out;
+    }
+
+    private static void airplaneData(Document document, Map<String, List<String>> data) throws DocumentException{
+        newDataAnchor(document, data.get("airplane"), "Airplane", 1);
+        newDataAnchor(document, data.get("airplaneType"), "Airplane type", 2);
+    }
+
+    private static void flightData(Document document, Map<String, List<String>> data)
+            throws DocumentException{
+        newDataAnchor(document, data.get("flight"), "Flight", 1);
+        newListAnchor(document, data.get("stewards"), "Stewards", 2);
+        newDataAnchor(document, data.get("airplane"), "Airplane", 3);
+        newDataAnchor(document, data.get("airports"), "Airports", 4);
+    }
+
+    private static void airportData(Document document, Map<String, List<String>> data) throws DocumentException{
+        newDataAnchor(document, data.get("airport"), "Airport", 1);
+        newDataAnchor(document, data.get("airportCity"), "Airport city", 2);
+    }
+
+    private static void createList(Section section, List<String> listData) {
+        // Creating pdf document list from data
+        com.itextpdf.text.List list = new com.itextpdf.text.List(true, false, 10);
+        if (listData != null) {
+            listData.forEach(list::add);
+        }
+        section.add(list);
+    }
+
+    private static void addMetaData(Document document, String title, String subject, String keywords) {
+        document.addTitle(title);
+        document.addSubject(subject);
+        document.addKeywords(keywords);
+        document.addAuthor("PA165 - Airport Manager");
+        document.addCreator("iText");
+    }
+
+    private static void addEmptyLines(Paragraph paragraph, int numberOfLines) {
+        for (int i = 0; i < numberOfLines; i++) {
+            paragraph.add(new Paragraph(" "));
+        }
+    }
+
+    private static void  newDataAnchor (Document document, List<String> data, String name, Integer chapterNumber)
+            throws DocumentException{
+        Anchor anchor = new Anchor(name, CATFONT);
+        anchor.setName(name);
+
+        Chapter chapter = new Chapter(new Paragraph(anchor), chapterNumber);
+        Section dataSection= chapter.addSection(new Paragraph(name + " data", SUBFONT));
+        if(data != null) {
+            data.forEach(value ->
+                    dataSection.add(new Paragraph(value)));
+        }
+        Paragraph paragraph = new Paragraph();
+        addEmptyLines(paragraph, 5);
+        dataSection.add(paragraph);
+        //adding all created components to the document
+        document.add(chapter);
+    }
+
+    private static void  newListAnchor (Document document, List<String> data, String name, Integer chapterNumber)
+            throws DocumentException{
+        Anchor anchor = new Anchor(name, CATFONT);
+        anchor.setName(name);
+
+        Chapter chapter = new Chapter(new Paragraph(anchor), chapterNumber);
+        Section listSection= chapter.addSection(new Paragraph(name + " data", SUBFONT));
+        createList(listSection, data);
+        Paragraph paragraph = new Paragraph();
+        addEmptyLines(paragraph, 5);
+        listSection.add(paragraph);
+        // now add all this to the document
+        document.add(chapter);
+    }
+}
diff --git a/report/src/main/resources/application.yaml b/report/src/main/resources/application.yaml
index 0abcd741f27e93ab0d76b682b58e413b874d2c92..f0c91d878b6ce53ccba20a1f0a6ba47aad0c345c 100644
--- a/report/src/main/resources/application.yaml
+++ b/report/src/main/resources/application.yaml
@@ -6,7 +6,35 @@ spring:
     driverClassName: org.h2.Driver
   jpa:
     database-platform: org.hibernate.dialect.H2Dialect
+  security:
+    oauth2:
+      resourceserver:
+        opaque-token:
+          introspection-uri: https://oidc.muni.cz/oidc/introspect
+          # Martin Kuba's testing resource server
+          client-id: d57b3a8f-156e-46de-9f27-39c4daee05e1
+          client-secret: fa228ebc-4d54-4cda-901e-4d6287f8b1652a9c9c44-73c9-4502-973f-bcdb4a8ec96a
 # Let's make weather microservice run on port 8088
 # 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/report/src/main/resources/report(1).pdf b/report/src/main/resources/report(1).pdf
new file mode 100644
index 0000000000000000000000000000000000000000..669890a9711a69ae10be8e60dd475f79ceae28e4
Binary files /dev/null and b/report/src/main/resources/report(1).pdf differ
diff --git a/report/src/main/resources/report.pdf b/report/src/main/resources/report.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..7ea01b7d8820fbe706c6949fd7e4c7674319d149
Binary files /dev/null and b/report/src/main/resources/report.pdf differ
diff --git a/report/src/main/resources/reportAirplane.pdf b/report/src/main/resources/reportAirplane.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..47578b65448a91e14fbf9aa53dd393c94edc16b7
Binary files /dev/null and b/report/src/main/resources/reportAirplane.pdf differ
diff --git a/report/src/main/resources/sample.pdf b/report/src/main/resources/sample.pdf
deleted file mode 100644
index c0e31a076aeb1fa7729e82279943b3504f85338d..0000000000000000000000000000000000000000
--- a/report/src/main/resources/sample.pdf
+++ /dev/null
@@ -1,198 +0,0 @@
-%PDF-1.3
-%âãÏÓ
-
-1 0 obj
-<<
-/Type /Catalog
-/Outlines 2 0 R
-/Pages 3 0 R
->>
-endobj
-
-2 0 obj
-<<
-/Type /Outlines
-/Count 0
->>
-endobj
-
-3 0 obj
-<<
-/Type /Pages
-/Count 2
-/Kids [ 4 0 R 6 0 R ] 
->>
-endobj
-
-4 0 obj
-<<
-/Type /Page
-/Parent 3 0 R
-/Resources <<
-/Font <<
-/F1 9 0 R 
->>
-/ProcSet 8 0 R
->>
-/MediaBox [0 0 612.0000 792.0000]
-/Contents 5 0 R
->>
-endobj
-
-5 0 obj
-<< /Length 1074 >>
-stream
-2 J
-BT
-0 0 0 rg
-/F1 0027 Tf
-57.3750 722.2800 Td
-( A Simple PDF File ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 688.6080 Td
-( This is a small demonstration .pdf file - ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 664.7040 Td
-( just for use in the Virtual Mechanics tutorials. More text. And more ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 652.7520 Td
-( text. And more text. And more text. And more text. ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 628.8480 Td
-( And more text. And more text. And more text. And more text. And more ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 616.8960 Td
-( text. And more text. Boring, zzzzz. And more text. And more text. And ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 604.9440 Td
-( more text. And more text. And more text. And more text. And more text. ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 592.9920 Td
-( And more text. And more text. ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 569.0880 Td
-( And more text. And more text. And more text. And more text. And more ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 557.1360 Td
-( text. And more text. And more text. Even more. Continued on page 2 ...) Tj
-ET
-endstream
-endobj
-
-6 0 obj
-<<
-/Type /Page
-/Parent 3 0 R
-/Resources <<
-/Font <<
-/F1 9 0 R 
->>
-/ProcSet 8 0 R
->>
-/MediaBox [0 0 612.0000 792.0000]
-/Contents 7 0 R
->>
-endobj
-
-7 0 obj
-<< /Length 676 >>
-stream
-2 J
-BT
-0 0 0 rg
-/F1 0027 Tf
-57.3750 722.2800 Td
-( Simple PDF File 2 ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 688.6080 Td
-( ...continued from page 1. Yet more text. And more text. And more text. ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 676.6560 Td
-( And more text. And more text. And more text. And more text. And more ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 664.7040 Td
-( text. Oh, how boring typing this stuff. But not as boring as watching ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 652.7520 Td
-( paint dry. And more text. And more text. And more text. And more text. ) Tj
-ET
-BT
-/F1 0010 Tf
-69.2500 640.8000 Td
-( Boring.  More, a little more text. The end, and just as well. ) Tj
-ET
-endstream
-endobj
-
-8 0 obj
-[/PDF /Text]
-endobj
-
-9 0 obj
-<<
-/Type /Font
-/Subtype /Type1
-/Name /F1
-/BaseFont /Helvetica
-/Encoding /WinAnsiEncoding
->>
-endobj
-
-10 0 obj
-<<
-/Creator (Rave \(http://www.nevrona.com/rave\))
-/Producer (Nevrona Designs)
-/CreationDate (D:20060301072826)
->>
-endobj
-
-xref
-0 11
-0000000000 65535 f
-0000000019 00000 n
-0000000093 00000 n
-0000000147 00000 n
-0000000222 00000 n
-0000000390 00000 n
-0000001522 00000 n
-0000001690 00000 n
-0000002423 00000 n
-0000002456 00000 n
-0000002574 00000 n
-
-trailer
-<<
-/Size 11
-/Root 1 0 R
-/Info 10 0 R
->>
-
-startxref
-2714
-%%EOF
diff --git a/report/src/test/java/cz/muni/fi/pa165/report/server/ReportIT.java b/report/src/test/java/cz/muni/fi/pa165/report/server/ReportIT.java
index 99c343405652f8df59637f9749daa1bd7a105df5..47edce289ce4a925f376337b76ebf74d0695a764 100644
--- a/report/src/test/java/cz/muni/fi/pa165/report/server/ReportIT.java
+++ b/report/src/test/java/cz/muni/fi/pa165/report/server/ReportIT.java
@@ -1,99 +1,127 @@
 package cz.muni.fi.pa165.report.server;
 
+
+import cz.muni.fi.pa165.report.server.service.ReportDocumentService;
+import cz.muni.fi.pa165.report.server.service.ReportDocumentServiceImpl;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
-
-import com.fasterxml.jackson.databind.ObjectMapper;;
+import com.fasterxml.jackson.databind.ObjectMapper;
 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.core.io.ByteArrayResource;
-import org.springframework.test.web.servlet.MockMvc;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
 
 /**
  * Integration tests. Run by "maven verify".
  */
 @SpringBootTest
-@AutoConfigureMockMvc
+@AutoConfigureMockMvc(addFilters = false)
 class ReportIT {
 
     private static final Logger log = LoggerFactory.getLogger(ReportIT.class);
 
-    @Autowired
-    private MockMvc mockMvc;
+    ReportDocumentService reportDocumentService = new ReportDocumentServiceImpl();
+
+    Map<String, List<String>> documentData = new HashMap<>();
 
     @Autowired
     ObjectMapper objectMapper;
 
     @Test
-    void getReportFlightByIdTest() throws Exception {
+    void getReportFlightByIdTest(){
         log.debug("getReportFlightByIdTest() running");
+        List<String> flightData = new ArrayList<>();
+        List<String> stewardsData;
+        List<String> airplaneData = new ArrayList<>();
+        List<String> airportsData = new ArrayList<>();
+
+        flightData.add("Arrival time of flight is 23:00");
+        flightData.add("Departure time of flight is 17:00");
+        documentData.put("flight", flightData);
+
+        stewardsData = List.of(new String[]{"Adam Krídl", "Matej Hrica", "Martin Slovík", "Ján Macháček"});
+        documentData.put("stewards", stewardsData);
+
+
+        airplaneData.add("Airplane name is TestPlane");
+        airplaneData.add("Airplane capacity is 2000");
+
+        documentData.put("airplane", airplaneData);
+
+        airportsData.add("Arrival airport is Arrival Test Airport");
+        airportsData.add("Departure airport is Departure Test Airport");
+
+        documentData.put("airports", airportsData);
+
+        var document = reportDocumentService.reportDocument(documentData, "flight test", ReportType.FLIGHT);
 
-        ClassLoader classLoader = getClass().getClassLoader();
-
-        try(InputStream inputStream = classLoader.getResourceAsStream("sample.pdf")){
-            byte[] bytes = new byte[inputStream.available()];
-            inputStream.read(bytes);
-            ByteArrayResource resource = new ByteArrayResource(bytes);
-            var response = mockMvc.perform(get("/api/reports/flight/1"))
-                    .andExpect(status().isOk())
-                    .andReturn().getResponse().getContentAsByteArray();
-            assertThat(Arrays.equals(response, resource.getByteArray()));
-            log.debug("response: {}", response);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
+        assertThat(document.toString()).contains("Type/Pages/Count 4");
+        assertThat(document.toString()).contains("Flight");
+        assertThat(document.toString()).contains("Stewards");
+        assertThat(document.toString()).contains("Airplane");
+        assertThat(document.toString()).contains("Airports");
+        assertThat(document.toString()).contains("flight test");
+
+        log.debug("Document: {}", document);
 
     }
 
     @Test
-    void getReportAirportByIdTest() throws Exception {
-        log.debug("getReportFlightByIdTest() running");
+    void getReportAirportByIdTest(){
+        log.debug("getReportAirportByIdTest() running");
+        List<String> airportData = new ArrayList<>();
+        List<String> airportCityData = new ArrayList<>();
+
+        airportData.add("Airport test airport");
+        airportData.add("The code of airport is tas");
+        airportData.add("Location of airport test airport is 41,2665 12,5994");
+        documentData.put("airport", airportData);
+
+        airportCityData.add("Airport is located in the city TestCity");
+        airportCityData.add("Country of this city is TestCountry");
+        documentData.put("airportCity", airportCityData);
 
-        ClassLoader classLoader = getClass().getClassLoader();
-
-        try(InputStream inputStream = classLoader.getResourceAsStream("sample.pdf")){
-            byte[] bytes = new byte[inputStream.available()];
-            inputStream.read(bytes);
-            ByteArrayResource resource = new ByteArrayResource(bytes);
-            var response = mockMvc.perform(get("/api/reports/airport/1"))
-                    .andExpect(status().isOk())
-                    .andReturn().getResponse().getContentAsByteArray();
-            assertThat(Arrays.equals(response, resource.getByteArray()));
-            log.debug("response: {}", response);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
+        var document = reportDocumentService.reportDocument(documentData, "airport test", ReportType.AIRPORT);
+
+        assertThat(document.toString()).contains("Type/Pages/Count 2");
+        assertThat(document.toString()).contains("Airport");
+        assertThat(document.toString()).contains("Airport city");
+        assertThat(document.toString()).contains("airport test");
+
+        log.debug("Document: {}", document);
 
     }
 
     @Test
-    void getReportAirplaneByIdTest() throws Exception {
-        log.debug("getReportFlightByIdTest() running");
+    void getReportAirplaneByIdTest(){
 
-        ClassLoader classLoader = getClass().getClassLoader();
-
-        try(InputStream inputStream = classLoader.getResourceAsStream("sample.pdf")){
-            byte[] bytes = new byte[inputStream.available()];
-            inputStream.read(bytes);
-            ByteArrayResource resource = new ByteArrayResource(bytes);
-            var response = mockMvc.perform(get("/api/reports/airplane/1"))
-                    .andExpect(status().isOk())
-                    .andReturn().getResponse().getContentAsByteArray();
-            assertThat(Arrays.equals(response, resource.getByteArray()));
-            log.debug("response: {}", response);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
+        log.debug("getReportAirplaneByIdTest() running");
+        List<String> airplaneData = new ArrayList<>();
+        List<String> airplaneTypeData = new ArrayList<>();
 
+        airplaneData.add("Airplane name: Test plane");
+        airplaneData.add("Airplane capacity: 150");
+        documentData.put("airplane", airplaneData);
+
+        airplaneTypeData.add("Airplane type name: Test type");
+        documentData.put("airplaneType", airplaneTypeData);
+
+        var document = reportDocumentService.reportDocument(documentData, "airplane test", ReportType.AIRPLANE);
+
+        assertThat(document.toString()).contains("Type/Pages/Count 2");
+        assertThat(document.toString()).contains("Airplane");
+        assertThat(document.toString()).contains("Airplane type");
+        assertThat(document.toString()).contains("airplane test");
+
+        log.debug("Document: {}", document);
     }
+
+
+
 }
diff --git a/authorization-client/mvnw b/user-client/mvnw
similarity index 100%
rename from authorization-client/mvnw
rename to user-client/mvnw
diff --git a/authorization-client/mvnw.cmd b/user-client/mvnw.cmd
similarity index 100%
rename from authorization-client/mvnw.cmd
rename to user-client/mvnw.cmd
diff --git a/authorization-client/pom.xml b/user-client/pom.xml
similarity index 77%
rename from authorization-client/pom.xml
rename to user-client/pom.xml
index 4a56609f619917d38f6329a046862d9ba70f74ee..271d8348da70c1212f612c78cf8438d94aed7350 100644
--- a/authorization-client/pom.xml
+++ b/user-client/pom.xml
@@ -9,12 +9,12 @@
     <version>1.0-SNAPSHOT</version>
   </parent>
 
-  <artifactId>authorization-client</artifactId>
+  <artifactId>user-client</artifactId>
   <version>1.0-SNAPSHOT</version>
-  <name>authorization-client</name>
+  <name>user-client</name>
 
   <build>
-    <finalName>authorization-client-lib</finalName>
+    <finalName>user-client-lib</finalName>
     <defaultGoal>install</defaultGoal>
 
     <plugins>
@@ -28,11 +28,11 @@
             </goals>
             <configuration>
               <!-- see https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-maven-plugin/README.md -->
-              <inputSpec>${project.basedir}/../authorization/openapi.yaml</inputSpec>
+              <inputSpec>${project.basedir}/../user/openapi.yaml</inputSpec>
               <generatorName>java</generatorName>
-              <apiPackage>cz.muni.fi.pa165.authorization.client</apiPackage>
-              <modelPackage>cz.muni.fi.pa165.authorization.client.model</modelPackage>
-              <invokerPackage>cz.muni.chat.fi.pa165.authorization.client.invoker</invokerPackage>
+              <apiPackage>cz.muni.fi.pa165.user.client</apiPackage>
+              <modelPackage>cz.muni.fi.pa165.user.client.model</modelPackage>
+              <invokerPackage>cz.muni.chat.fi.pa165.user.client.invoker</invokerPackage>
               <verbose>false</verbose>
               <generateApiTests>false</generateApiTests>
               <generateModelTests>false</generateModelTests>
@@ -89,5 +89,17 @@
       <groupId>javax.annotation</groupId>
       <artifactId>javax.annotation-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+    </dependency>
+      <dependency>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-oauth2-client</artifactId>
+      </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+    </dependency>
   </dependencies>
 </project>
diff --git a/user-client/src/main/java/cz/muni/fi/pa165/user/client/Authorities.java b/user-client/src/main/java/cz/muni/fi/pa165/user/client/Authorities.java
new file mode 100644
index 0000000000000000000000000000000000000000..290d039e48ffb1c2679483dbd5b5cc20f0eb11b2
--- /dev/null
+++ b/user-client/src/main/java/cz/muni/fi/pa165/user/client/Authorities.java
@@ -0,0 +1,7 @@
+package cz.muni.fi.pa165.user.client;
+
+public class Authorities {
+    public static final String MANAGER = "SCOPE_test_1";
+    public static final String ADMINISTRATOR = "SCOPE_test_2";
+    public static final String AUDITOR = "SCOPE_test_3";
+}
\ No newline at end of file
diff --git a/user-client/src/main/java/cz/muni/fi/pa165/user/client/UserServiceInterceptionConfigurer.java b/user-client/src/main/java/cz/muni/fi/pa165/user/client/UserServiceInterceptionConfigurer.java
new file mode 100644
index 0000000000000000000000000000000000000000..604c869f94a07c203653930249eaab45f962e537
--- /dev/null
+++ b/user-client/src/main/java/cz/muni/fi/pa165/user/client/UserServiceInterceptionConfigurer.java
@@ -0,0 +1,25 @@
+package cz.muni.fi.pa165.user.client;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+@ComponentScan
+public class UserServiceInterceptionConfigurer implements WebMvcConfigurer {
+
+    private final UserServiceRequestInterceptor interceptor;
+
+    @Autowired
+    public UserServiceInterceptionConfigurer(UserServiceRequestInterceptor interceptor) {
+        this.interceptor = interceptor;
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // Add the user interceptor to intercept all requests to your REST endpoints
+        registry.addInterceptor(interceptor).addPathPatterns("/api/**");
+    }
+}
diff --git a/user-client/src/main/java/cz/muni/fi/pa165/user/client/UserServiceRequestInterceptor.java b/user-client/src/main/java/cz/muni/fi/pa165/user/client/UserServiceRequestInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..8caeabae9083dc20f060b8bab61ffbe853eef2e7
--- /dev/null
+++ b/user-client/src/main/java/cz/muni/fi/pa165/user/client/UserServiceRequestInterceptor.java
@@ -0,0 +1,55 @@
+package cz.muni.fi.pa165.user.client;
+
+import cz.muni.chat.fi.pa165.user.client.invoker.ApiClient;
+import cz.muni.chat.fi.pa165.user.client.invoker.ApiException;
+
+import cz.muni.fi.pa165.user.client.model.NewActionDto;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
+import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+
+@Component
+public class UserServiceRequestInterceptor implements HandlerInterceptor {
+
+    public void sendRequest(HttpServletRequest request) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null) {
+            return;
+        }
+        var authPrincipal = (OAuth2IntrospectionAuthenticatedPrincipal) authentication.getPrincipal();
+        var token = (OAuth2AccessToken) (((BearerTokenAuthentication) authentication).getToken());
+
+        // would lead to infinite recursion
+        if (request.getRequestURI().equals("/api/users/action")) {
+            return;
+        }
+
+        try {
+            var client = new ApiClient();
+            client.setRequestInterceptor((builder -> {
+                builder.header("Authorization", "Bearer " + token.getTokenValue());
+            }));
+            var api = new UserApi(client);
+            api.registerUserAction(new NewActionDto()
+                    .url(request.getRequestURI())
+                    .httpMethod(request.getMethod())
+            );
+            request.getRequestURI();
+        } catch (ApiException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        sendRequest(request);
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/authorization/.mvn/wrapper/maven-wrapper.jar b/user/.mvn/wrapper/maven-wrapper.jar
similarity index 100%
rename from authorization/.mvn/wrapper/maven-wrapper.jar
rename to user/.mvn/wrapper/maven-wrapper.jar
diff --git a/authorization/.mvn/wrapper/maven-wrapper.properties b/user/.mvn/wrapper/maven-wrapper.properties
similarity index 100%
rename from authorization/.mvn/wrapper/maven-wrapper.properties
rename to user/.mvn/wrapper/maven-wrapper.properties
diff --git a/user/Dockerfile b/user/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d131222e38617e1c16b7e0e8e265b68f253b1849
--- /dev/null
+++ b/user/Dockerfile
@@ -0,0 +1,3 @@
+FROM eclipse-temurin:17-jdk-alpine
+COPY target/*.jar /app.jar
+ENTRYPOINT ["java", "-jar", "/app.jar"]
\ No newline at end of file
diff --git a/authorization/mvnw b/user/mvnw
similarity index 100%
rename from authorization/mvnw
rename to user/mvnw
diff --git a/authorization/mvnw.cmd b/user/mvnw.cmd
similarity index 100%
rename from authorization/mvnw.cmd
rename to user/mvnw.cmd
diff --git a/user/openapi.yaml b/user/openapi.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..93c53413d16f1a5cb14e63c9c8233716e22fca41
--- /dev/null
+++ b/user/openapi.yaml
@@ -0,0 +1,196 @@
+openapi: 3.0.3
+info:
+  title: Airport Manager uesr microservice
+  description: Airport Manager user microservice
+  version: 1.0.0
+servers:
+  - url: "{scheme}://{server}:{port}"
+    description: my server
+    variables:
+      scheme:
+        default: http
+        enum:
+          - http
+          - https
+      server:
+        default: localhost
+      port:
+        default: "8083"
+security:
+  - BearerAuth: []
+paths:
+  /api/users/:
+    get:
+      tags:
+        - User
+      summary: List all system users
+      description: Get an array of all system users
+      operationId: getAllUsers
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/UserDto'
+  /api/users/{id}:
+    get:
+      tags:
+        - User
+      summary: Get user by id.
+      description: Returns an object representing an user.
+      operationId: getUserById
+      parameters:
+        - name: id
+          in: path
+          required: true
+          schema:
+            type: integer
+            format: int64
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/UserDto'
+
+  /api/users/{id}/actions:
+    get:
+      tags:
+        - User
+      summary: Get actions of a user.
+      description: Returns an actions of the user.
+      operationId: getUserActions
+      parameters:
+        - name: id
+          in: path
+          required: true
+          schema:
+            type: integer
+            format: int64
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/ActionDto'
+
+  /api/users/action:
+    post:
+      tags:
+        - User
+      summary: Record action for a user.
+      description: Record action for a user.
+      operationId: registerUserAction
+      requestBody:
+        description: Data of the action
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/NewActionDto'
+      responses:
+        "200":
+          description: OK
+        "404":
+          description: Not Found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+        "400":
+          description: Invalid input
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+components:
+  securitySchemes:
+    BearerAuth:
+      type: http
+      description: "OAuth2 Resource Server, provide a valid access token"
+      scheme: bearer
+  schemas:
+    DomainEntity:
+      title: Domain Entity
+      description: Represents a Domain Entity
+      type: object
+      required:
+        - id
+      properties:
+        id:
+          type: integer
+          description: unique id
+          format: int64
+          example: 1
+      discriminator:
+        propertyName: objectType
+
+    ErrorMessage:
+      title: Error Message
+      description: Response body for HTML statuses.
+      type: object
+      properties:
+        message:
+          type: string
+          description: reason for error
+          example: entity not found
+
+    UserDto:
+      allOf:
+        - $ref: '#/components/schemas/DomainEntity'
+      type: object
+      title: User
+      description: Represents a system user.
+      required:
+        - externalIdentifier
+      properties:
+        externalIdentifier:
+          type: string
+          description: identfifier of the user
+          example: 492778@muni.cz
+
+    NewActionDto:
+      type: object
+      title: User
+      description: Represents a user.
+      required:
+        - url
+        - httpMethod
+      properties:
+        url:
+          type: string
+          description: the url of the method
+          example: /api/weatherForecast
+        httpMethod:
+          type: string
+          description: the method the user called
+          example: GET
+
+    ActionDto:
+      type: object
+      title: User
+      description: Represents a user.
+      required:
+        - url
+        - httpMethod
+        - timestamp
+      properties:
+        url:
+          type: string
+          description: the url of the method
+          example: /api/weatherForecast
+        httpMethod:
+          type: string
+          description: the method the user called
+          example: GET
+        timestamp:
+          type: string
+          format: date-time
+          description: the method the user called
\ No newline at end of file
diff --git a/authorization/pom.xml b/user/pom.xml
similarity index 76%
rename from authorization/pom.xml
rename to user/pom.xml
index 6313a119d4b1c680eb2a35e2b2f95333fcd2301d..8e8fe61b130aeda815a3b7b5e182bf44e822ea5f 100644
--- a/authorization/pom.xml
+++ b/user/pom.xml
@@ -8,10 +8,10 @@
         <version>1.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>authorization</artifactId>
+    <artifactId>user</artifactId>
 
-    <name>authorization</name>
-    <description>Authorization service for Airport Manager</description>
+    <name>user</name>
+    <description>User service for Airport Manager</description>
 
     <dependencies>
 
@@ -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>
@@ -41,11 +53,6 @@
             <artifactId>jakarta.annotation-api</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>jakarta.validation</groupId>
-            <artifactId>jakarta.validation-api</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>io.swagger.core.v3</groupId>
             <artifactId>swagger-models-jakarta</artifactId>
@@ -62,15 +69,25 @@
         </dependency>
 
         <dependency>
-            <groupId>javax.validation</groupId>
-            <artifactId>validation-api</artifactId>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>org.springdoc</groupId>
-            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cz.muni.fi.pa165</groupId>
+            <artifactId>user-client</artifactId>
+            <version>1.0-SNAPSHOT</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-client</artifactId>
+        </dependency>
         <!-- for pagination from JPA without actually using JPA -->
         <dependency>
             <groupId>org.springframework.data</groupId>
@@ -98,12 +115,12 @@
                         <configuration>
                             <inputSpec>${project.basedir}/openapi.yaml</inputSpec>
                             <generatorName>spring</generatorName>
-                            <apiPackage>cz.muni.fi.pa165.authorization.server.api</apiPackage>
-                            <modelPackage>cz.muni.fi.pa165.authorization.server.model</modelPackage>
+                            <apiPackage>cz.muni.fi.pa165.user.server.api</apiPackage>
+                            <modelPackage>cz.muni.fi.pa165.user.server.model</modelPackage>
                             <!-- https://openapi-generator.tech/docs/generators/spring -->
                             <configOptions>
-                                <basePackage>cz.muni.fi.pa165.authorization.server</basePackage>
-                                <configPackage>cz.muni.fi.pa165.authorization.server.config</configPackage>
+                                <basePackage>cz.muni.fi.pa165.user.server</basePackage>
+                                <configPackage>cz.muni.fi.pa165.user.server.config</configPackage>
                                 <useSpringBoot3>true</useSpringBoot3>
                                 <useTags>true</useTags>
                                 <delegatePattern>true</delegatePattern>
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/config/AppConfig.java b/user/src/main/java/cz/muni/fi/pa165/user/server/config/AppConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b35b95f1dd7cd80b653e54ae2c1eaae42a977dc
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/config/AppConfig.java
@@ -0,0 +1,39 @@
+package cz.muni.fi.pa165.user.server.config;
+
+import cz.muni.fi.pa165.user.client.Authorities;
+import cz.muni.fi.pa165.user.client.UserServiceInterceptionConfigurer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@Import(UserServiceInterceptionConfigurer.class)
+public class AppConfig {
+
+    @Bean
+    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http.authorizeHttpRequests(x -> x
+                        // swagger:
+                        .requestMatchers("/swagger-ui/**").permitAll()
+                        .requestMatchers("/v3/api-docs/**").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/swagger-ui.html").permitAll()
+
+                        // used under the authority of the user using the original api
+                        .requestMatchers("/api/users/action").authenticated()
+
+                        // require audit authority for reading from this service
+                        .requestMatchers(HttpMethod.GET, "/api/users").hasAuthority(Authorities.AUDITOR)
+                        .requestMatchers(HttpMethod.GET, "/api/users/**").hasAuthority(Authorities.AUDITOR)
+
+                        // default deny
+                        .anyRequest().denyAll()
+                )
+                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
+        return http.build();
+    }
+}
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/data/domain/Action.java b/user/src/main/java/cz/muni/fi/pa165/user/server/data/domain/Action.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d88eaa18d811ff11a9236cebbacabd0e79fc88d
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/data/domain/Action.java
@@ -0,0 +1,32 @@
+package cz.muni.fi.pa165.user.server.data.domain;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.CreationTimestamp;
+
+import java.time.OffsetDateTime;
+
+@Data
+@NoArgsConstructor
+@Entity(name = "actions")
+public class Action {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "user_id")
+    private User user;
+
+    @Column
+    private String url;
+
+    @Column
+    private String httpMethod;
+
+    @CreationTimestamp
+    @Temporal(TemporalType.TIMESTAMP)
+    private OffsetDateTime timestamp;
+}
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/data/domain/User.java b/user/src/main/java/cz/muni/fi/pa165/user/server/data/domain/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a91f2e32ab52380e15c82e74a3da6f42879b44e
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/data/domain/User.java
@@ -0,0 +1,31 @@
+package cz.muni.fi.pa165.user.server.data.domain;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+
+// name is important - "user" and "system_user" are reserved and produce weird errors
+@Entity(name = "users")
+@Data
+@NoArgsConstructor
+public class User {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
+    private List<Action> actions;
+
+    public void addAction(Action action) {
+        actions.add(action);
+    }
+
+    @Column(unique = true)
+    private String externalIdentifier;
+}
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/data/repository/ActionRepository.java b/user/src/main/java/cz/muni/fi/pa165/user/server/data/repository/ActionRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0d6ea0325cfe3ae4468878cf292383628ca18e7
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/data/repository/ActionRepository.java
@@ -0,0 +1,10 @@
+package cz.muni.fi.pa165.user.server.data.repository;
+
+import cz.muni.fi.pa165.user.server.data.domain.Action;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ActionRepository  extends CrudRepository<Action, Long>  {
+
+}
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/data/repository/UserRepository.java b/user/src/main/java/cz/muni/fi/pa165/user/server/data/repository/UserRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..063d55fabadbd8ca643bd8591f439e6fdb1ecfed
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/data/repository/UserRepository.java
@@ -0,0 +1,16 @@
+package cz.muni.fi.pa165.user.server.data.repository;
+
+import cz.muni.fi.pa165.user.server.data.domain.User;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends CrudRepository<User, Long> {
+
+    @Query("SELECT u FROM users u WHERE u.externalIdentifier = :externalIdentifier")
+    Optional<User> findByExternalIndetifier(@Param("externalIdentifier") String externalIdentifier);
+}
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/facade/UserFacade.java b/user/src/main/java/cz/muni/fi/pa165/user/server/facade/UserFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a12fcfbd9264d87d338986ae559d069163fd626
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/facade/UserFacade.java
@@ -0,0 +1,48 @@
+package cz.muni.fi.pa165.user.server.facade;
+
+import cz.muni.fi.pa165.user.server.mapper.ActionMapper;
+import cz.muni.fi.pa165.user.server.mapper.UserMapper;
+import cz.muni.fi.pa165.user.server.model.ActionDto;
+import cz.muni.fi.pa165.user.server.model.UserDto;
+import cz.muni.fi.pa165.user.server.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.StreamSupport;
+
+@Service
+public class UserFacade {
+
+    private final UserService userService;
+    private final UserMapper userMapper;
+    private ActionMapper actionMapper;
+
+    @Autowired
+    public UserFacade(UserService userService, UserMapper userMapper, ActionMapper actionMapper) {
+        this.userService = userService;
+        this.userMapper = userMapper;
+        this.actionMapper = actionMapper;
+    }
+
+    public Optional<UserDto> findById(Long id) {
+        return userService.findById(id).map(userMapper::toDto);
+    }
+
+    public List<UserDto> getAllUsers() {
+        return StreamSupport.stream(userService.getAllUsers().spliterator(), false)
+                .map(userMapper::toDto)
+                .toList();
+    }
+
+    public void registerUserAction(String foreignIdentifier, String url, String httpMethod) {
+        userService.registerUserAction(foreignIdentifier, url, httpMethod);
+    }
+
+    public List<ActionDto> getActionsOfUser(Long id) {
+        return StreamSupport.stream(userService.getActionsOfUser(id).spliterator(), false)
+                .map(actionMapper::toDto)
+                .toList();
+    }
+}
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/mapper/ActionMapper.java b/user/src/main/java/cz/muni/fi/pa165/user/server/mapper/ActionMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..d9f17c5b86774337f59a3ac46c7e753b8cb32291
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/mapper/ActionMapper.java
@@ -0,0 +1,12 @@
+package cz.muni.fi.pa165.user.server.mapper;
+
+import cz.muni.fi.pa165.user.server.data.domain.Action;
+import cz.muni.fi.pa165.user.server.model.ActionDto;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface ActionMapper {
+    Action toEntity(ActionDto actionDto);
+
+    ActionDto toDto(Action user);
+}
\ No newline at end of file
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/mapper/UserMapper.java b/user/src/main/java/cz/muni/fi/pa165/user/server/mapper/UserMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c02af85c78c87024d6172ba51c3f20af0787b9e
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/mapper/UserMapper.java
@@ -0,0 +1,12 @@
+package cz.muni.fi.pa165.user.server.mapper;
+
+import cz.muni.fi.pa165.user.server.data.domain.User;
+import cz.muni.fi.pa165.user.server.model.UserDto;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface UserMapper {
+    User toEntity(UserDto userDto);
+
+    UserDto toDto(User user);
+}
\ No newline at end of file
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/rest/UserController.java b/user/src/main/java/cz/muni/fi/pa165/user/server/rest/UserController.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4ac4a8ef9409d72326060b29d4de606123decff
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/rest/UserController.java
@@ -0,0 +1,51 @@
+package cz.muni.fi.pa165.user.server.rest;
+
+import cz.muni.fi.pa165.user.server.api.UserApiDelegate;
+import cz.muni.fi.pa165.user.server.facade.UserFacade;
+import cz.muni.fi.pa165.user.server.model.ActionDto;
+import cz.muni.fi.pa165.user.server.model.NewActionDto;
+import cz.muni.fi.pa165.user.server.model.UserDto;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+public class UserController implements UserApiDelegate {
+
+    private final UserFacade userFacade;
+
+    @Autowired
+    public UserController(UserFacade userFacade) {
+        this.userFacade = userFacade;
+    }
+
+    @Override
+    public ResponseEntity<UserDto> getUserById(Long id) {
+        return ResponseEntity.of(userFacade.findById(id));
+    }
+
+    @Override
+    public ResponseEntity<List<UserDto>> getAllUsers() {
+        return ResponseEntity.ok(userFacade.getAllUsers());
+    }
+
+    @Override
+    public ResponseEntity<Void> registerUserAction(NewActionDto newActivityDto) {
+        var principal = (OAuth2IntrospectionAuthenticatedPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+        userFacade.registerUserAction(
+                principal.getAttribute("sub"),
+                newActivityDto.getUrl(),
+                newActivityDto.getHttpMethod()
+        );
+        return ResponseEntity.ok().build();
+    }
+
+    @Override
+    public ResponseEntity<List<ActionDto>> getUserActions(Long id) {
+        return ResponseEntity.ok(userFacade.getActionsOfUser(id));
+    }
+}
diff --git a/user/src/main/java/cz/muni/fi/pa165/user/server/service/UserService.java b/user/src/main/java/cz/muni/fi/pa165/user/server/service/UserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e9e86469728ede0099f6007bea93fffbe84a717
--- /dev/null
+++ b/user/src/main/java/cz/muni/fi/pa165/user/server/service/UserService.java
@@ -0,0 +1,53 @@
+package cz.muni.fi.pa165.user.server.service;
+
+import cz.muni.fi.pa165.user.server.data.domain.Action;
+import cz.muni.fi.pa165.user.server.data.domain.User;
+import cz.muni.fi.pa165.user.server.data.repository.ActionRepository;
+import cz.muni.fi.pa165.user.server.data.repository.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.Optional;
+
+import static com.fasterxml.jackson.databind.type.LogicalType.Collection;
+
+@Service
+public class UserService {
+
+    private final UserRepository userRepository;
+    private final ActionRepository actionRepository;
+
+    @Autowired
+    public UserService(UserRepository userRepository, ActionRepository actionRepository) {
+        this.userRepository = userRepository;
+        this.actionRepository = actionRepository;
+    }
+
+    public Optional<User> findById(Long id) {
+        return userRepository.findById(id);
+    }
+
+    public Iterable<User> getAllUsers() {
+        return userRepository.findAll();
+    }
+
+    public void registerUserAction(String externalIdentifier, String url, String httpMethod) {
+        Optional<User> maybeUser = userRepository.findByExternalIndetifier(externalIdentifier);
+        User user = maybeUser.orElseGet(() -> {
+            var newUser = new User();
+            newUser.setExternalIdentifier(externalIdentifier);
+            newUser = userRepository.save(newUser);
+            return newUser;
+        });
+        var action = new Action();
+        action.setUrl(url);
+        action.setHttpMethod(httpMethod);
+        action.setUser(user);
+        actionRepository.save(action);
+    }
+
+    public Iterable<Action> getActionsOfUser(Long id) {
+        return userRepository.findById(id).map(User::getActions).orElse(Collections.EMPTY_LIST);
+    }
+}
diff --git a/user/src/main/resources/application.yml b/user/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..76d917933a8b4fc49d17cb7af49948cfea1b986f
--- /dev/null
+++ b/user/src/main/resources/application.yml
@@ -0,0 +1,38 @@
+spring:
+  datasource:
+    url: jdbc:h2:mem:exampleDb
+    username: sa
+    password: password
+    driverClassName: org.h2.Driver
+  jpa:
+    database-platform: org.hibernate.dialect.H2Dialect
+  security:
+    oauth2:
+      resourceserver:
+        opaque-token:
+          introspection-uri: https://oidc.muni.cz/oidc/introspect
+          # Martin Kuba's testing resource server
+          client-id: d57b3a8f-156e-46de-9f27-39c4daee05e1
+          client-secret: fa228ebc-4d54-4cda-901e-4d6287f8b1652a9c9c44-73c9-4502-973f-bcdb4a8ec96a
+server:
+  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/weather/Dockerfile b/weather/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d131222e38617e1c16b7e0e8e265b68f253b1849
--- /dev/null
+++ b/weather/Dockerfile
@@ -0,0 +1,3 @@
+FROM eclipse-temurin:17-jdk-alpine
+COPY target/*.jar /app.jar
+ENTRYPOINT ["java", "-jar", "/app.jar"]
\ No newline at end of file
diff --git a/weather/openapi.yaml b/weather/openapi.yaml
index 458c08ad7f59e878060030fc07240ce579b479f9..d2479d57a50fc705c503ddb7958c609a40205bf0 100644
--- a/weather/openapi.yaml
+++ b/weather/openapi.yaml
@@ -26,6 +26,8 @@ servers:
 tags:
   - name: Weather
     description: Microservice for weather.
+security:
+  - BearerAuth: []
 paths:
   /api/isSafeToCreateFlight/{departureAirportId}/{arrivalAirportId}:
     get:
@@ -72,7 +74,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/FlightCreationAdvice'
+                $ref: '#/components/schemas/FlightCreationAdviceDto'
         "400":
           description: Input data not correct
           content:
@@ -107,7 +109,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/WeatherForecast'
+                $ref: '#/components/schemas/WeatherForecastDto'
         "400":
           description: Input data not correct
           content:
@@ -115,35 +117,31 @@ paths:
               schema:
                 $ref: '../core/openapi.yaml#/components/schemas/ErrorMessage'
 components:
+  securitySchemes:
+    BearerAuth:
+      type: http
+      description: "OAuth2 Resource Server, provide a valid access token"
+      scheme: bearer
+      bearerFormat: "jwt"
   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 +158,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 52f2201f00a20f7df0173d7e3ca9813d1be89e1d..40f4bb519ff6156fc49375e272d0eb18ce3a7018 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>
@@ -41,11 +65,6 @@
             <artifactId>jakarta.annotation-api</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>jakarta.validation</groupId>
-            <artifactId>jakarta.validation-api</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>io.swagger.core.v3</groupId>
             <artifactId>swagger-models-jakarta</artifactId>
@@ -62,13 +81,19 @@
         </dependency>
 
         <dependency>
-            <groupId>javax.validation</groupId>
-            <artifactId>validation-api</artifactId>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>org.springdoc</groupId>
-            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cz.muni.fi.pa165</groupId>
+            <artifactId>user-client</artifactId>
+            <version>1.0-SNAPSHOT</version>
         </dependency>
 
         <!-- for pagination from JPA without actually using JPA -->
@@ -77,11 +102,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/config/AppConfig.java b/weather/src/main/java/cz/muni/fi/pa165/weather/server/config/AppConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..70e7db1d163bbcc2921763c7d80a0431ca1310e4
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/config/AppConfig.java
@@ -0,0 +1,31 @@
+package cz.muni.fi.pa165.weather.server.config;
+
+import cz.muni.fi.pa165.user.client.Authorities;
+import cz.muni.fi.pa165.user.client.UserServiceInterceptionConfigurer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@Import(UserServiceInterceptionConfigurer.class)
+public class AppConfig {
+
+    @Bean
+    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http.authorizeHttpRequests(x -> x
+                        // swagger:
+                        .requestMatchers("/swagger-ui/**").permitAll()
+                        .requestMatchers("/v3/api-docs/**").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/").permitAll()
+                        .requestMatchers(HttpMethod.GET, "/swagger-ui.html").permitAll()
+                        // MANAGER has access to everything in this service
+                        .anyRequest().hasAuthority(Authorities.MANAGER)
+                )
+                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
+        return http.build();
+    }
+}
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..187127ec2d8d6697f3ef82a8bbe916d740370260
--- /dev/null
+++ b/weather/src/main/java/cz/muni/fi/pa165/weather/server/service/WeatherServiceImpl.java
@@ -0,0 +1,188 @@
+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.ApiClient;
+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.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
+import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
+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 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 {
+            var authentication = SecurityContextHolder.getContext().getAuthentication();
+            var token = (OAuth2AccessToken) (((BearerTokenAuthentication) authentication).getToken());
+            var client = new ApiClient();
+            client.setRequestInterceptor((builder -> {
+                builder.header("Authorization", "Bearer " + token.getTokenValue());
+            }));
+            AirportApi airportClient = new AirportApi(client);
+            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..ac1e77f44374277a01de56cee1e5fc288cbd4aa2 100644
--- a/weather/src/main/resources/application.yml
+++ b/weather/src/main/resources/application.yml
@@ -6,7 +6,35 @@ spring:
     driverClassName: org.h2.Driver
   jpa:
     database-platform: org.hibernate.dialect.H2Dialect
+  security:
+    oauth2:
+      resourceserver:
+        opaque-token:
+          introspection-uri: https://oidc.muni.cz/oidc/introspect
+          # Martin Kuba's testing resource server
+          client-id: d57b3a8f-156e-46de-9f27-39c4daee05e1
+          client-secret: fa228ebc-4d54-4cda-901e-4d6287f8b1652a9c9c44-73c9-4502-973f-bcdb4a8ec96a
 # 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..2e96bcc410662cc95b3b5393c41e15418e57cb73 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;
@@ -21,7 +28,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  * Integration tests. Run by "mvn verify".
  */
 @SpringBootTest
-@AutoConfigureMockMvc
+@AutoConfigureMockMvc(addFilters = false)
 class WeatherApplicationIT {
 
     private static final Logger log = LoggerFactory.getLogger(WeatherApplicationIT.class);
@@ -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