diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..b0b42d7
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,56 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+# TS/JS-Files
+[*.{ts,js}]
+indent_size = 2
+
+# JSON-Files
+[*.json]
+indent_style = tab
+
+# ReST-Files
+[*.rst]
+indent_size = 4
+max_line_length = 80
+
+# YAML-Files
+[*.{yaml,yml}]
+indent_size = 2
+
+# NEON-Files
+[*.neon]
+indent_size = 2
+indent_style = tab
+
+# package.json
+[package.json]
+indent_size = 2
+
+# TypoScript
+[*.{typoscript,tsconfig}]
+indent_size = 2
+
+# XLF-Files
+[*.xlf]
+indent_style = tab
+
+# SQL-Files
+[*.sql]
+indent_style = tab
+indent_size = 2
+
+# .htaccess
+[{_.htaccess,.htaccess}]
+indent_style = tab
\ No newline at end of file
diff --git a/Classes/Controller/BookedController.php b/Classes/Controller/BookedController.php
new file mode 100644
index 0000000..51e0310
--- /dev/null
+++ b/Classes/Controller/BookedController.php
@@ -0,0 +1,49 @@
+bookedService = $bookedService;
+ }
+
+ /**
+ * @return ResponseInterface
+ * @throws AspectNotFoundException
+ * @throws JsonException
+ */
+ public function listAction(): ResponseInterface
+ {
+ $booked['booked'] = $this->bookedService->getBooked($this->request->getArguments());
+
+ $this->view->setVariablesToRender(['bookedList']);
+ $this->view->assign('bookedList', $booked);
+
+ return $this->jsonResponse();
+ }
+}
diff --git a/Classes/Domain/Model/Dto/ApiConfiguration.php b/Classes/Domain/Model/Dto/ApiConfiguration.php
new file mode 100644
index 0000000..d345d14
--- /dev/null
+++ b/Classes/Domain/Model/Dto/ApiConfiguration.php
@@ -0,0 +1,97 @@
+getConfiguration(ConstantsUtility::EXTENSION_KEY);
+
+ empty($configuration['authenticationUsername']) ?: $this->setAuthenticationUsername($configuration['authenticationUsername']);
+ empty($configuration['authenticationPassword']) ?: $this->setAuthenticationPassword($configuration['authenticationPassword']);
+ empty($configuration['requestUri']) ?: $this->setRequestUri($configuration['requestUri']);
+ }
+
+ /**
+ * @return string
+ */
+ public function getAuthenticationUsername(): string
+ {
+ return $this->authenticationUsername;
+ }
+
+ /**
+ * @param string $authenticationUsername
+ */
+ public function setAuthenticationUsername(string $authenticationUsername = ''): void
+ {
+ $this->authenticationUsername = $authenticationUsername;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAuthenticationPassword(): string
+ {
+ return $this->authenticationPassword;
+ }
+
+ /**
+ * @param string $authenticationPassword
+ */
+ public function setAuthenticationPassword(string $authenticationPassword = ''): void
+ {
+ $this->authenticationPassword = $authenticationPassword;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRequestUri(): string
+ {
+ return $this->requestUri;
+ }
+
+ /**
+ * @param string $requestUri
+ */
+ public function setRequestUri(string $requestUri = ''): void
+ {
+ $this->requestUri = $requestUri;
+ }
+
+ /**
+ * @param string $extensionKey
+ * @return array
+ */
+ protected function getConfiguration(string $extensionKey = ''): array
+ {
+ /** @var ExtensionConfiguration $extensionConfiguration */
+ $extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
+
+ try {
+ return $extensionConfiguration->get($extensionKey);
+ } catch (Exception $e) {
+ return [];
+ }
+ }
+}
diff --git a/Classes/Http/Request.php b/Classes/Http/Request.php
new file mode 100644
index 0000000..4a94c0d
--- /dev/null
+++ b/Classes/Http/Request.php
@@ -0,0 +1,108 @@
+ [
+ 'Cache-Control' => 'no-cache'
+ ],
+ 'allow_redirects' => false
+ ];
+ protected LoggerInterface $logger;
+ protected RequestFactoryInterface $requestFactory;
+
+ /**
+ * @param LoggerInterface $logger
+ * @param RequestFactoryInterface $requestFactory
+ */
+ public function __construct(
+ LoggerInterface $logger,
+ RequestFactoryInterface $requestFactory
+ ) {
+ $this->logger = $logger;
+ $this->requestFactory = $requestFactory;
+ }
+
+ /**
+ * @param string $uri
+ * @param string $method
+ * @param array $options
+ * @return array|null
+ */
+ public function process(string $uri = '', string $method = 'GET', array $options = []): ?array
+ {
+ try {
+ $options = $this->mergeOptions($this->options, $options);
+ $response = $this->requestFactory->request($uri, $method, $options);
+
+ return $this->getContent($response, $uri);
+ } catch (RequestException $e) {
+ /** @extensionScannerIgnoreLine */
+ $this->logger->error($e->getMessage());
+
+ return null;
+ }
+ }
+
+ /**
+ * @param array $default
+ * @param array $new
+ * @return array
+ */
+ protected function mergeOptions(array $default, array $new): array
+ {
+ if (count($new) > 0) {
+ ArrayUtility::mergeRecursiveWithOverrule($default, $new);
+ }
+
+ return $default;
+ }
+
+ /**
+ * @param ResponseInterface $response
+ * @param string $uri
+ * @return array|null
+ */
+ protected function getContent(ResponseInterface $response, string $uri = ''): ?array
+ {
+ $content = '';
+
+ if ($response->getStatusCode() === 200 &&
+ strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0
+ ) {
+ $content = (array)json_decode($response->getBody()->getContents(), true);
+ }
+
+ if (empty($content)) {
+ $this->logger->warning(
+ 'Requesting {request} was not successful, got status code {status} ({reason})',
+ [
+ 'request' => $uri,
+ 'status' => $response->getStatusCode(),
+ 'reason' => $response->getReasonPhrase(),
+ ]
+ );
+
+ return null;
+ }
+
+ return $content;
+ }
+}
diff --git a/Classes/Mvc/View/JsonView.php b/Classes/Mvc/View/JsonView.php
new file mode 100644
index 0000000..59bd77a
--- /dev/null
+++ b/Classes/Mvc/View/JsonView.php
@@ -0,0 +1,60 @@
+ [
+ * '_descendAll' => [
+ * '_exclude' => [
+ * 'categories',
+ * 'contact',
+ * 'discipline'
+ * ]
+ * ]
+ * ]
+ */
+ protected array $bookedConfiguration = [
+ 'bookedList' => [
+ '_only' => [
+ 'booked'
+ ],
+ 'booked' => [
+ '_descendAll' => [
+ '_only' => [
+ 'referenceNumber',
+ 'startDate',
+ 'endDate',
+ 'resourceName',
+ 'title',
+ 'description',
+ 'duration'
+ ],
+ ]
+ ]
+ ]
+ ];
+
+ public function __construct()
+ {
+ $this->setConfiguration($this->bookedConfiguration);
+ }
+}
diff --git a/Classes/Routing/UriGenerator.php b/Classes/Routing/UriGenerator.php
new file mode 100644
index 0000000..4228bb6
--- /dev/null
+++ b/Classes/Routing/UriGenerator.php
@@ -0,0 +1,54 @@
+apiConfiguration = $apiConfiguration;
+ }
+
+ /**
+ * @return string
+ */
+ public function buildAuthenticationUri(): string
+ {
+ /** @extensionScannerIgnoreLine */
+ return $this->apiConfiguration->getRequestUri() . 'Authentication/Authenticate';
+ }
+
+ /**
+ * @return string
+ */
+ public function buildUsersUri(): string
+ {
+ /** @extensionScannerIgnoreLine */
+ return $this->apiConfiguration->getRequestUri() . 'Users/';
+ }
+
+ /**
+ * @return string
+ */
+ public function buildReservationsUri(): string
+ {
+ /** @extensionScannerIgnoreLine */
+ return $this->apiConfiguration->getRequestUri() . 'Reservations/';
+ }
+}
diff --git a/Classes/Sanitization/BookedArgumentSanitization.php b/Classes/Sanitization/BookedArgumentSanitization.php
new file mode 100644
index 0000000..96baa32
--- /dev/null
+++ b/Classes/Sanitization/BookedArgumentSanitization.php
@@ -0,0 +1,41 @@
+ 0];
+ }
+
+ $this->sanitizeInteger('user', $arguments['user']);
+
+ return $this->sanitizedArguments;
+ }
+
+ /**
+ * @param string $key
+ * @param string $value
+ */
+ protected function sanitizeInteger(string $key = '', string $value = ''): void
+ {
+ empty($value) ?: $this->sanitizedArguments[$key] = (int)$value;
+ }
+}
diff --git a/Classes/Service/AuthenticationService.php b/Classes/Service/AuthenticationService.php
new file mode 100644
index 0000000..84de77b
--- /dev/null
+++ b/Classes/Service/AuthenticationService.php
@@ -0,0 +1,86 @@
+apiConfiguration = $apiConfiguration;
+ $this->request = $request;
+ $this->uriGenerator = $uriGenerator;
+ }
+
+ /**
+ * @return array
+ * @throws JsonException
+ */
+ public function getHeaders(): array
+ {
+ $authentication = $this->getAuthentication();
+
+ if ($authentication['isAuthenticated'] === true) {
+ return [
+ 'headers' => [
+ 'X-Booked-SessionToken' => $authentication['sessionToken'],
+ 'X-Booked-UserId' => $authentication['userId']
+ ]
+ ];
+ }
+
+ return [];
+ }
+
+ /**
+ * @return array
+ * @throws JsonException
+ */
+ protected function getAuthentication(): array
+ {
+ $uri = $this->uriGenerator->buildAuthenticationUri();
+ /** @extensionScannerIgnoreLine */
+ $options = $this->getOptions();
+
+ return $this->request->process($uri, 'POST', $options) ?? [];
+ }
+
+ /**
+ * @return array
+ * @throws JsonException
+ */
+ protected function getOptions(): array
+ {
+ return [
+ 'body' => json_encode([
+ 'username' => $this->apiConfiguration->getAuthenticationUsername(),
+ 'password' => $this->apiConfiguration->getAuthenticationPassword()
+ ], JSON_THROW_ON_ERROR)
+ ];
+ }
+}
diff --git a/Classes/Service/BookedService.php b/Classes/Service/BookedService.php
new file mode 100644
index 0000000..c01ec31
--- /dev/null
+++ b/Classes/Service/BookedService.php
@@ -0,0 +1,60 @@
+authenticationService = $authenticationService;
+ $this->bookedArgumentSanitization = $bookedArgumentSanitization;
+ $this->reservationService = $reservationService;
+ $this->userService = $userService;
+ }
+
+ /**
+ * @param array $arguments
+ * @return array
+ * @throws JsonException
+ */
+ public function getBooked(array $arguments): array
+ {
+ $sanitizedArguments = $this->bookedArgumentSanitization->sanitizeArguments($arguments);
+ $requestOptions = $this->authenticationService->getHeaders();
+
+ // There is no direct way to get the user nor his reservations.
+ // Collect all reservations and users and pick those who are relevant.
+ $reservations = $this->reservationService->getData($requestOptions);
+ $users = $this->userService->getData($requestOptions);
+ $currentUser = $this->userService->findByUserName($users, (string)$sanitizedArguments['user']);
+
+ return $this->reservationService->findByUserId($reservations, (string)$currentUser['id']);
+ }
+}
diff --git a/Classes/Service/ReservationService.php b/Classes/Service/ReservationService.php
new file mode 100644
index 0000000..1f5c468
--- /dev/null
+++ b/Classes/Service/ReservationService.php
@@ -0,0 +1,62 @@
+request = $request;
+ $this->uriGenerator = $uriGenerator;
+ }
+
+ /**
+ * @param array $reservations
+ * @param string $userId
+ * @return array
+ */
+ public function findByUserId(array $reservations, string $userId): array
+ {
+ $userReservations = [];
+
+ foreach ($reservations as $reservation) {
+ if ($reservation['userId'] === $userId) {
+ $userReservations[] = $reservation;
+ }
+ }
+
+ return $userReservations;
+ }
+
+ /**
+ * @param array $options
+ * @return array
+ */
+ public function getData(array $options): array
+ {
+ $uri = $this->uriGenerator->buildReservationsUri();
+
+ return $this->request->process($uri, 'GET', $options)['reservations'] ?? [];
+ }
+}
diff --git a/Classes/Service/UserService.php b/Classes/Service/UserService.php
new file mode 100644
index 0000000..ce0518f
--- /dev/null
+++ b/Classes/Service/UserService.php
@@ -0,0 +1,60 @@
+request = $request;
+ $this->uriGenerator = $uriGenerator;
+ }
+
+ /**
+ * @param array $users
+ * @param string $userName
+ * @return array
+ */
+ public function findByUserName(array $users, string $userName): array
+ {
+ foreach ($users as $user) {
+ if ($user['userName'] === $userName) {
+ return $user;
+ }
+ }
+
+ return [];
+ }
+
+ /**
+ * @param array $options
+ * @return array
+ */
+ public function getData(array $options): array
+ {
+ $uri = $this->uriGenerator->buildUsersUri();
+
+ return $this->request->process($uri, 'GET', $options)['users'] ?? [];
+ }
+}
diff --git a/Classes/Utility/ConstantsUtility.php b/Classes/Utility/ConstantsUtility.php
new file mode 100644
index 0000000..0f3e61e
--- /dev/null
+++ b/Classes/Utility/ConstantsUtility.php
@@ -0,0 +1,18 @@
+getLanguageById($languageUid);
+ }
+
+ /**
+ * @return int
+ * @throws AspectNotFoundException
+ */
+ public static function getUid(): int
+ {
+ /** @var Context $context */
+ $context = GeneralUtility::makeInstance(Context::class);
+
+ return (int)$context->getPropertyFromAspect('language', 'id');
+ }
+
+ /**
+ * @param int $pageUid
+ * @return Site
+ */
+ protected static function getSite(int $pageUid): Site
+ {
+ /** @var SiteFinder $siteFinder */
+ $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
+
+ try {
+ return $siteFinder->getSiteByPageId($pageUid);
+ } catch (SiteNotFoundException $e) {
+ throw new UnexpectedValueException(
+ 'A site not found by "' . $pageUid . '".',
+ 1490360742
+ );
+ }
+ }
+}
diff --git a/Configuration/Icons.php b/Configuration/Icons.php
new file mode 100644
index 0000000..cff8368
--- /dev/null
+++ b/Configuration/Icons.php
@@ -0,0 +1,14 @@
+ [
+ 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
+ 'source' => 'EXT:slub_profile_booked/Resources/Public/Icons/Wizard/booked-list.svg'
+ ],
+ 'slubprofilebooked-overlay-extension' => [
+ 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
+ 'source' => 'EXT:slub_profile_booked/Resources/Public/Icons/Overlay/extension.svg'
+ ]
+];
diff --git a/Configuration/Routes/BookedList.yaml b/Configuration/Routes/BookedList.yaml
new file mode 100644
index 0000000..f8da053
--- /dev/null
+++ b/Configuration/Routes/BookedList.yaml
@@ -0,0 +1,10 @@
+routeEnhancers:
+ SlubBookedBookedList:
+ type: Simple
+ limitToPages: [23]
+ routePath: '/{user}'
+ requirements:
+ user: '[0-9]{1,10}'
+ _arguments:
+ user: 'tx_slubprofilebooked_bookedlist/user'
+
diff --git a/Configuration/Routes/Default.yaml b/Configuration/Routes/Default.yaml
new file mode 100644
index 0000000..634f535
--- /dev/null
+++ b/Configuration/Routes/Default.yaml
@@ -0,0 +1,5 @@
+# This route enhancer config is not final
+# But you can simply include it in your site configuration:
+imports:
+ - resource: 'EXT:slub_profile_booked/Configuration/Routes/BookedList.yaml'
+
diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml
new file mode 100644
index 0000000..36fab67
--- /dev/null
+++ b/Configuration/Services.yaml
@@ -0,0 +1,26 @@
+services:
+ _defaults:
+ autowire: true
+ autoconfigure: true
+ public: false
+
+ Slub\SlubProfileBooked\:
+ resource: '../Classes/*'
+
+ Slub\SlubProfileBooked\Http\Request:
+ public: true
+
+ Slub\SlubProfileBooked\Routing\UriGenerator:
+ public: true
+
+ Slub\SlubProfileBooked\Service\AuthenticationService:
+ public: true
+
+ Slub\SlubProfileBooked\Service\BookedService:
+ public: true
+
+ Slub\SlubProfileBooked\Service\ReservationService:
+ public: true
+
+ Slub\SlubProfileBooked\Service\UserService:
+ public: true
diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php
new file mode 100644
index 0000000..b8c7b47
--- /dev/null
+++ b/Configuration/TCA/Overrides/sys_template.php
@@ -0,0 +1,10 @@
+ 'LLL:EXT:' . $extensionKey . '/Resources/Private/Language/locallang_backend.xlf',
+ 'core' => 'LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf',
+ 'frontend' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf',
+ ];
+
+ // Add new group to ctype selector
+ TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItemGroup(
+ 'tt_content',
+ 'CType',
+ $extensionName,
+ $ll['backend'] . ':plugin.title',
+ 'after:default' // Should be the same like "common" in page tsconfig
+ );
+
+ foreach ($pluginNames as $pluginName) {
+ // Merge content element definition
+ TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA']['tt_content'], [
+ 'ctrl' => [
+ 'typeicon_classes' => [
+ $extensionName . '_' . $pluginName => $extensionName . '-wizard-' . $pluginName,
+ ],
+ ],
+ 'types' => [
+ $extensionName . '_' . $pluginName => [
+ 'showitem' => '
+ --div--;' . $ll['core'] . ':general,
+ --palette--;;general,
+ --palette--;;headers,
+ --div--;' . $ll['core'] . ':language,
+ --palette--;;language,
+ --div--;' . $ll['core'] . ':access,
+ --palette--;;hidden,
+ --palette--;;access,
+ --div--;' . $ll['core'] . ':notes,
+ rowDescription,
+ --div--;' . $ll['core'] . ':extended',
+ ],
+ ],
+ ]);
+
+ // Add item to select field list (ctype)
+ TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
+ 'tt_content',
+ 'CType',
+ [
+ $ll['backend'] . ':plugin.' . $pluginName . '.title', // Title
+ $extensionName . '_' . $pluginName, // CType
+ $extensionName . '-wizard-' . $pluginName, // Icon identifier
+ $extensionName // Item group id
+ ]
+ );
+ }
+})(
+ 'slub_profile_booked',
+ ['bookedlist']
+);
diff --git a/Configuration/TsConfig/Page.tsconfig b/Configuration/TsConfig/Page.tsconfig
new file mode 100644
index 0000000..7b5c4f4
--- /dev/null
+++ b/Configuration/TsConfig/Page.tsconfig
@@ -0,0 +1,18 @@
+mod {
+ wizards.newContentElement.wizardItems.slubprofilebooked {
+ after = common
+ header = LLL:EXT:slub_profile_booked/Resources/Private/Language/locallang_backend.xlf:plugin.title
+
+ elements {
+ slubprofilebooked_bookedlist {
+ iconIdentifier = slubprofilebooked-wizard-bookedlist
+ iconOverlay = slubprofilebooked-overlay-extension
+ title = LLL:EXT:slub_profile_booked/Resources/Private/Language/locallang_backend.xlf:plugin.bookedlist.title
+ description = LLL:EXT:slub_profile_booked/Resources/Private/Language/locallang_backend.xlf:plugin.bookedlist.description
+ tt_content_defValues.CType = slubprofilebooked_bookedlist
+ }
+ }
+
+ show := addToList(slubprofilebooked_bookedlist)
+ }
+}
diff --git a/Configuration/TypoScript/constants.typoscript b/Configuration/TypoScript/constants.typoscript
new file mode 100644
index 0000000..45ca925
--- /dev/null
+++ b/Configuration/TypoScript/constants.typoscript
@@ -0,0 +1,4 @@
+plugin.tx_slubprofilebooked {
+ # cat=plugin.tx_slubprofilebooked//a; type=string; label=Default storage PID
+ settings.storagePid = 0
+}
diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript
new file mode 100644
index 0000000..fe7f9c4
--- /dev/null
+++ b/Configuration/TypoScript/setup.typoscript
@@ -0,0 +1,10 @@
+plugin.tx_slubprofilebooked {
+ mvc {
+ callDefaultActionIfActionCantBeResolved = 1
+ throwPageNotFoundExceptionIfActionCantBeResolved = 1
+ }
+
+ features {
+ skipDefaultArguments = 1
+ }
+}
diff --git a/README.md b/README.md
index 2a26cca..2eb3100 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,41 @@
-# slub-profile-booked
\ No newline at end of file
+# TYPO3 Extension `slub_profile_booked`
+
+[![TYPO3](https://img.shields.io/badge/TYPO3-11-orange.svg)](https://typo3.org/)
+
+SLUB profile service booked rooms extension for TYPO3.
+
+## 1 Usage
+
+### 1.1 Installation using Composer
+
+The recommended way to install the extension is using [Composer][1].
+
+Run the following command within your Composer based TYPO3 project:
+
+```
+composer require slub/slub-profile-booked
+```
+
+## 2 API
+
+This extension communicates with another system to provide booked.
+
+### 2.1 Routes
+
+Please check the routes' configuration. You have to set the matching page (limitToPages). If not the routes will not work properly.
+
+### 2.2 Booked
+
+A list of booked rooms from a given user.
+
+- **Uri DDEV local:** https://ddev-slub-profile-service.ddev.site/raumbuchungen/###USER_ID###
+- **Uri general:** https://###YOUR-DOMAIN###/raumbuchungen/###USER_ID###
+
+#### 2.2.1 Extension configuration
+
+- **Uri:** Address or domain to request the data. The uri has to begin with "https://". If you connect to another ddev container, please use "https://ddev-###YOUR-CONTAINER###-web".
+- **Argument identifier:** When you request data from this extension to the booked api (external extension), you use additional parameters too. These parameters are wrapped with the "argument identifier". The default value is "tx_slubfindbooked_bookedlist". Change only if you know what you do.
+
+[1]: https://getcomposer.org/
+
+
diff --git a/Resources/Private/Language/de.locallang_backend.xlf b/Resources/Private/Language/de.locallang_backend.xlf
new file mode 100644
index 0000000..95363dd
--- /dev/null
+++ b/Resources/Private/Language/de.locallang_backend.xlf
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+ Raumbuchung
+
+
+
+
+ Raumbuchungsliste
+
+
+
+ Auflistung von gebuchten Räumen.
+
+
+
+
+ URI: Die URI muss mit "https://" anfangen. Ein anderer DDEV-Container kann direkt über den Container mit "https://ddev-###YOUR-CONTAINER###-web" angesprochen werden oder die Domain, wenn "external_links" konfiguriert.
+
+
+
+ Authentifizierung Benutzer
+
+
+
+ Authentifizierung Passwort
+
+
+
+
diff --git a/Resources/Private/Language/locallang_backend.xlf b/Resources/Private/Language/locallang_backend.xlf
new file mode 100644
index 0000000..ded20d8
--- /dev/null
+++ b/Resources/Private/Language/locallang_backend.xlf
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/Public/Icons/Extension.svg b/Resources/Public/Icons/Extension.svg
new file mode 100644
index 0000000..7c5965b
--- /dev/null
+++ b/Resources/Public/Icons/Extension.svg
@@ -0,0 +1 @@
+
diff --git a/Resources/Public/Icons/Overlay/extension.svg b/Resources/Public/Icons/Overlay/extension.svg
new file mode 100644
index 0000000..537358f
--- /dev/null
+++ b/Resources/Public/Icons/Overlay/extension.svg
@@ -0,0 +1,6 @@
+
diff --git a/Resources/Public/Icons/Wizard/booked-list.svg b/Resources/Public/Icons/Wizard/booked-list.svg
new file mode 100644
index 0000000..a1921eb
--- /dev/null
+++ b/Resources/Public/Icons/Wizard/booked-list.svg
@@ -0,0 +1,14 @@
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..9dd56bc
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "slub/slub-profile-booked",
+ "type": "typo3-cms-extension",
+ "description": "SLUB profile service booked rooms extension for TYPO3",
+ "authors": [
+ {
+ "name": "Andreas Pfeiffer",
+ "role": "Developer"
+ }
+ ],
+ "version": "0.1.0",
+ "license": "GPL-2.0-or-later",
+ "require": {
+ "typo3/cms-core": "^11"
+ },
+ "replace": {
+ "typo3-ter/slub-profile-booked": "self.version"
+ },
+ "autoload": {
+ "psr-4": {
+ "Slub\\SlubProfileBooked\\": "Classes/"
+ }
+ },
+ "extra": {
+ "typo3/cms": {
+ "extension-key": "slub_profile_booked"
+ }
+ }
+}
diff --git a/ext_conf_template.txt b/ext_conf_template.txt
new file mode 100644
index 0000000..ff60823
--- /dev/null
+++ b/ext_conf_template.txt
@@ -0,0 +1,8 @@
+# cat=API booked; type=string; label=LLL:EXT:slub_profile_booked/Resources/Private/Language/locallang_backend.xlf:extensionConfiguration.requestUri
+requestUri =
+
+# cat=API booked; type=string; label=LLL:EXT:slub_profile_booked/Resources/Private/Language/locallang_backend.xlf:extensionConfiguration.authenticationUsername
+authenticationUsername =
+
+# cat=API booked; type=string; label=LLL:EXT:slub_profile_booked/Resources/Private/Language/locallang_backend.xlf:extensionConfiguration.authenticationPassword
+authenticationPassword =
diff --git a/ext_emconf.php b/ext_emconf.php
new file mode 100644
index 0000000..7601e4a
--- /dev/null
+++ b/ext_emconf.php
@@ -0,0 +1,28 @@
+ 'SLUB profile booked',
+ 'description' => 'SLUB profile service booked rooms extension for TYPO3',
+ 'category' => 'fe',
+ 'author' => 'Andreas Pfeiffer',
+ 'author_email' => 'andreas.pfeiffer@e-pixler.com',
+ 'author_company' => 'e-pixler',
+ 'shy' => '',
+ 'priority' => '',
+ 'module' => '',
+ 'state' => 'stable',
+ 'internal' => '',
+ 'uploadfolder' => 1,
+ 'createDirs' => '',
+ 'modify_tables' => '',
+ 'clearCacheOnLoad' => 1,
+ 'lockType' => '',
+ 'version' => '0.1.0',
+ 'constraints' => [
+ 'depends' => [
+ 'typo3' => '11.0.0-11.5.99'
+ ],
+ 'conflicts' => [],
+ 'suggests' => [],
+ ],
+];
diff --git a/ext_localconf.php b/ext_localconf.php
new file mode 100644
index 0000000..3d931f9
--- /dev/null
+++ b/ext_localconf.php
@@ -0,0 +1,25 @@
+ 'list'
+ ],
+ [
+ BookedController::class => 'list'
+ ],
+ ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
+);