5 Commits
19 changed files with 624 additions and 102 deletions
+11 -3
View File
@@ -6,7 +6,9 @@ Powered by:
- PHP Symfony. - PHP Symfony.
- Easyadmin. - Easyadmin.
## Commands: ## Get started:
### Dev commands
Build containers: Build containers:
```bash ```bash
@@ -33,7 +35,7 @@ XDEBUG_LOG=/dev/stdout
``` ```
### Prod: ### Production commands:
When deploying to a production environment a production build should be used. When deploying to a production environment a production build should be used.
Then, deployment can be done as containers or using docker-compose. Then, deployment can be done as containers or using docker-compose.
@@ -47,7 +49,7 @@ docker build -t somniarooms:prod --target frankenphp_prod .
**Deploy prod image** **Deploy prod image**
You may use a env file to configure the SomniaRoom container: You may use a env file to configure the SomniaRoom container (`.env.prod.local`):
```shell ```shell
SERVER_NAME=localhost SERVER_NAME=localhost
@@ -83,6 +85,12 @@ If you want your app and DB to comunicate using it's hostname (like on this exam
docker network create somniarooms-network docker network create somniarooms-network
``` ```
Check [deploy docs](docs/deploy.md) for complete examples on how to deploy SomniaRooms (backend+frontend) using Docker and Docker-compose.
<warning>
Examples shown on deploy docs may be out-of-date. Make sure all readme recommendations are applied before use on production.
</warning>
## Running the app! ## Running the app!
+1
View File
@@ -16,6 +16,7 @@
"easycorp/easyadmin-bundle": "^4.10", "easycorp/easyadmin-bundle": "^4.10",
"nelmio/api-doc-bundle": "^4.30", "nelmio/api-doc-bundle": "^4.30",
"nelmio/cors-bundle": "^2.4", "nelmio/cors-bundle": "^2.4",
"php-ds/php-ds": "^1.5",
"phpdocumentor/reflection-docblock": "^5.4", "phpdocumentor/reflection-docblock": "^5.4",
"phpstan/phpdoc-parser": "^1.28", "phpstan/phpdoc-parser": "^1.28",
"runtime/frankenphp-symfony": "^0.2.0", "runtime/frankenphp-symfony": "^0.2.0",
Generated
+56 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "974cb22b15d6dc64bd6dac49da5d671d", "content-hash": "03b299c4c2e514090c879c8f71219e56",
"packages": [ "packages": [
{ {
"name": "composer/semver", "name": "composer/semver",
@@ -1686,6 +1686,61 @@
}, },
"time": "2023-11-30T16:41:19+00:00" "time": "2023-11-30T16:41:19+00:00"
}, },
{
"name": "php-ds/php-ds",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/php-ds/polyfill.git",
"reference": "7b2c5f1843466d50769a0682ce6fa9ddaaa99cb4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-ds/polyfill/zipball/7b2c5f1843466d50769a0682ce6fa9ddaaa99cb4",
"reference": "7b2c5f1843466d50769a0682ce6fa9ddaaa99cb4",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=7.0"
},
"provide": {
"ext-ds": "1.5.0"
},
"require-dev": {
"php-ds/tests": "^1.5"
},
"suggest": {
"ext-ds": "to improve performance and reduce memory usage"
},
"type": "library",
"autoload": {
"psr-4": {
"Ds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rudi Theunissen",
"email": "[email protected]"
}
],
"keywords": [
"data structures",
"ds",
"php",
"polyfill"
],
"support": {
"issues": "https://github.com/php-ds/polyfill/issues",
"source": "https://github.com/php-ds/polyfill/tree/v1.5.0"
},
"time": "2023-12-19T16:52:21+00:00"
},
{ {
"name": "phpdocumentor/reflection-common", "name": "phpdocumentor/reflection-common",
"version": "2.2.0", "version": "2.2.0",
+4
View File
@@ -24,5 +24,9 @@ services:
arguments: arguments:
$targetDirectory: 'images/' $targetDirectory: 'images/'
App\Service\ExampleDataService:
arguments:
$sampleDirectory: '../samples/'
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones # please note that last definitions always *replace* previous ones
+182 -36
View File
@@ -1,54 +1,200 @@
<!-- omit in toc -->
# SomniaRooms - App Configuration Variables. # SomniaRooms - App Configuration Variables.
- [Config Sections](#config-sections)
- [Config implementation status](#config-implementation-status)
- [Company Information](#company-information)
- [Front-end Config](#front-end-config)
- [Var `NightAmountForContactDialog`](#var-nightamountforcontactdialog)
- [Var `GuestNumberForContactDialog`](#var-guestnumberforcontactdialog)
- [Var `ContactDialogShowBehaviour`](#var-contactdialogshowbehaviour)
- [Var `ShowNonAvailableRooms`](#var-shownonavailablerooms)
- [Var `SomniaAttributionNotice`](#var-somniaattributionnotice)
- [Var `PhotoAspectRatio`](#var-photoaspectratio)
- [Price Management](#price-management)
- [Var `TotalPriceMethod`.](#var-totalpricemethod)
- [Var `FareSelectionMethod`](#var-fareselectionmethod)
- [API Config.](#api-config)
- [Var `MaxAllowedNightAmount`](#var-maxallowednightamount)
- [Var `MaxAllowedGuestNumber`](#var-maxallowedguestnumber)
---
This document contains a list of all config variables of SomniaRooms. Variables are stored in DB (table _`config_variable`_) and can be configured from admin panel. This document contains a list of all config variables of SomniaRooms. Variables are stored in DB (table _`config_variable`_) and can be configured from admin panel.
Variables are divided in sections. Some sections are ony accesible from SomniaRoom, but others can be retrieved by anyone using config endpoint (_`/config`_). Variables are divided in sections. Some sections are ony accesible from SomniaRoom, but others can be retrieved by anyone using config endpoint (_`/config`_).
## List of sections: ## Config Sections
WIP | Section | SectionName | Description | Availability |
| :------------------------------------------ | :-------------- | :--------------------------------------------- | :----------: |
| [Company Information](#company-information) | COMPANY | Configure company information | Public |
| [Front-end config](#front-end-config) | FRONTEND_CONFIG | Configure front-end behaviour | Public |
| [Price API Management](#price-management) | API_PRICES | Configure how prices are calculated for a stay | Private |
| [API Config](#api-config) | API_CONFIG | Configure API | Private |
TODO -> Add default table with basic information to all sections.
### **Company** ### Config implementation status
Variables in this section contains information about the Hotel.
- SectionName -> company Not all variables are fully implemented, some are experimental or have options that are.
- Availability -> public Current status for a variable and it's options:
- Nº variables -> X - New!: :sparkles:
- Fully available: :heavy_check_mark:
- Partially available: :construction:
- Experimental: :bulb:
- Not yet: :x:
**Variable list:**
- Name - Name of hotel. ## Company Information
Variables in this section contains information about the Company. Those are used to identify the business and to customize frontend.
| Variable | Type | Description | Constraints | Default Value | Status |
| :------------- | :----- | :-------------------- | :----------------------------------------------------------- | :------------ | :------------: |
| CompanyName | string | Company name | - | SomniaRooms | :construction: |
| CompanyWebsite | string | Company website URL | Valid URL to company website. | somnia.dev | :construction: |
| CompanyLogo | string | Company Logo URL | Valid URL to company logo. Somnia Backend system can be used | "" | :construction: |
| CompanyAddress | string | Company Address | - | "" | :construction: |
| CompanyEmail | string | Company email address | - | "" | :construction: |
| CompanyPhone | string | Company phone number | - | "" | :construction: |
## Front-end Config
This variable set the behaviour of front-end app.
| Variable | Type | Description | Constraints | Default Value | Status |
| :-------------------------- | :------ | :------------------------------------------------- | :---------------------------------------------- | :------------ | :------------: |
| NightAmountForContactDialog | int | Set the amount of nights to show a contact dialog. | See [details](#var-nightamountforcontactdialog) | 10 | :construction: |
| GuestNumberForContactDialog | int | Set the guest number to show a contact dialog. | See [details](#var-guestnumberforcontactdialog) | 6 | :construction: |
| ContactDialogShowBehaviour | options | Set how the contact dialog is shown | See [details](#var-contactdialogshowbehaviour) | show | :construction: |
| ShowNonAvailableRooms | bool | Show rooms without availability on search | See [details](#var-shownonavailablerooms) | true | :construction: |
| SomniaAttributionNotice | options | How SomniaRooms notice is shown. | See [details](#var-somniaattributionnotice) | full | :construction: |
| PhotoAspectRatio | string | Set the aspect ratio for photos | See [details](#var-photoaspectratio) | 4:3 | :construction: |
### Var `NightAmountForContactDialog`
Set the amount of nights to show a contact dialog. This dialog contains company contact information and may be used to get a custom price for long stays.
The contact dialog will be shown when night amount is equal or greater than configured amount.
**Options:**
| Option | Value | Usage | Status |
| :------------ | :---- | :-------------------------------------------------------- | :----: |
| Disabled | -1 | Disable this funcionality. Contact dialog won't be shown. | :x: |
| Always | 0 | Contact dialog is always shown. | :x: |
| Custom Amount | - | Set the minimun number of nights to show the dialog. | :x: |
### Var `GuestNumberForContactDialog`
Set the guest number to show a contact dialog. This dialog contains company contact information and may be used to get a custom price for groups.
The contact dialog will be shown when guest number is equal or greater than configured amount.
**Options:**
| Option | Value | Usage | Status |
| :------------ | :---- | :-------------------------------------------------------- | :----: |
| Disabled | -1 | Disable this funcionality. Contact dialog won't be shown. | :x: |
| Always | 0 | Contact dialog is always shown. | :x: |
| Custom Amount | - | Set the minimun guest number to show the dialog. | :x: |
### Var `ContactDialogShowBehaviour`
How the contact dialog is shown to the user (when conditions are meet).
**Options:**
| Option | Value | Usage | Status |
| :----- | :---- | :------------------------------- | :----: |
| Show | SHOW | Show dialog on screen | :x: |
| Alert | ALERT | Show an alert to open the dialog | :x: |
### Var `ShowNonAvailableRooms`
Show rooms without availability on search.
Non-available rooms will be shown at the end of the list.
**Options:**
| Option | Value | Usage | Status |
| :----- | :---- | :-------------------------------------------------------- | :----: |
| Show | true | All rooms will appear on search. Even non-available ones. | :x: |
| Hide | false | Only show available rooms. | :x: |
### Var `SomniaAttributionNotice`
Configure how SomniaRooms attribution note is display.
**Options:**
| Option | Value | Usage | Status |
| :----- | :---- | :------------------------------------ | :----: |
| Full | FULL | Show the full message. Thanks :heart: | :x: |
| Small | SMALL | Show a small notice :+1: | :x: |
| Hide | HIDE | SomniaRooms notice won't be shown. | :x: |
### Var `PhotoAspectRatio`
Aspect ratio for front-end photos.
This setting is currently used for room photos. You may set any valid aspect-ratio you want.
It's recommended that all photos have the same aspect-ratio to avoid scaling.
## Price Management
How SomniaRooms API services calculates prices for bookings.
| Variable | Type | Description | Constraints | Default Value | Status |
| :------------------ | :----- | :---------------------------------------------------- | :-------------------------------------- | :------------ | :----: |
| TotalPriceMethod | option | How total price is calculated for a stay | See [details](#var-totalpricemethod) | SUM | :x: |
| FareSelectionMethod | option | What fare to select when multiple fares are available | See [details](#var-fareselectionmethod) | HIGHER | :x: |
### Var `TotalPriceMethod`.
Establish the method used to calculate the total price for a stay.
The total price for a stay will be calculated from the price of the days that make the stay. A operation must be selected to get total price from daily prices.
**Options:**
| Option | Value | Usage | Status |
| :------------ | :------ | :----------------------------------------------------------------- | :----: |
| Lower Price | LOWER | The lower price will be apply for each day. | :x: |
| High Price | HIGHER | The highest price will be apply for each day. | :x: |
| Total Sum | SUM | Total price will be the sum of the price for each day. | :x: |
| Average Price | AVERAGE | The average for all days will be calculated and apply to each day. | :x: |
### Var `FareSelectionMethod`
How to select the fare for a room when there are multiple fares applicable.
A faretable may contain multiple fares for a room and guest number given some conditions. If multiple conditions match for a date and therefore there are more than 1 applicable fare, one must be used. This method sets what to do in those cases.
Note: RoomPrices have more priority than fares. So if a RoomPrice is available, it will always be the prefered option.
**Options:**
| Option | Value | Usage | Status |
| :------------ | :------ | :---------------------------------------------------------------- | :----: |
| Lower Price | LOWER | Lower price available will be used. | :x: |
| High Price | HIGHER | Higher available price will be used. | :x: |
| Average price | AVERAGE | Selected price will be the aritmetic average of available prices. | :x: |
## API Config.
Configuration for some API endpoints.
| Variable | Type | Description | Constraints | Default Value | Status |
| :-------------------- | :--- | :----------------------------------- | :---------------------------------------- | :------------ | :------------: |
| MaxAllowedNightAmount | int | Max night amount that can be booked. | See [details](#var-maxallowednightamount) | 20 | :construction: |
| MaxAllowedGuestNumber | int | Max guest number that can be booked. | See [details](#var-maxallowedguestnumber) | 20 | :construction: |
### **Price Management** ### Var `MaxAllowedNightAmount`
Configure how backend services calculates prices for bookings. Max number of nights that can be booked using the app. For longer stays the company should be contacted.
- SectionName -> price_mng **Options:**
- Availability -> internal | Option | Value | Usage | Status |
- Nº variables -> X | :------------ | :---- | :---------------------------------------------------------- | :----: |
| Disabled | 0 | Disable this funcionality. No limit on booking will be set. | :x: |
**Variable list:** | Custom Amount | - | Set the max number of nights allowed to book. | :x: |
WIP
#### FinalPriceCalculation.
Establece cómo se deben tratar los precios de cada día para generar el precio final de la estancia.
Algunos algoritmos para estos serán:
- Precio más alto.
- Precio más bajo.
- Suma de precios. --> Por defecto.
- Media de precios.
#### MultipleRoomFareValid
Estrategia de selección de tarifa en caso de que haya varias válidas (mientras no exista un sistema de prioridad de tarifas...)
Posibles algoritmos:
- Precio más alto.
- Precio más bajo.
- Media.
### Var `MaxAllowedGuestNumber`
Max guest number that can be booked using the app. For bigger groups the company should be contacted.
**Options:**
| Option | Value | Usage | Status |
| :------------ | :---- | :---------------------------------------------------------- | :----: |
| Disabled | 0 | Disable this funcionality. No limit on booking will be set. | :x: |
| Custom Amount | - | Set the max guest number allowed to book. | :x: |
+8
View File
@@ -1,5 +1,13 @@
<!-- omit in toc -->
# SomniaRooms - How to deploy. # SomniaRooms - How to deploy.
- [Docker compose examples.](#docker-compose-examples)
- [Deploy back-end and front-end on different servers (different ip address).](#deploy-back-end-and-front-end-on-different-servers-different-ip-address)
- [Deploy on the same server.](#deploy-on-the-same-server)
- [What's next?](#whats-next)
---
SomniaRooms is designed to be easy to deploy and use. SomniaRooms is designed to be easy to deploy and use.
Production images for Docker Containers are available from Gitea Registry. Production images for Docker Containers are available from Gitea Registry.
+10
View File
@@ -0,0 +1,10 @@
[
{
"name": "Double Bed",
"iconUrl": "icon-bed-double"
},
{
"name": "Single Bed",
"iconUrl": "icon-bed-single"
}
]
+41
View File
@@ -0,0 +1,41 @@
[
{
"name": "Double room (double bed)",
"description": "Room with double bed and shared bathroom",
"maxGuestNum": 2,
"orderHomePage": null,
"roomCategoryPhotos": [
{
"path": "https://images.unsplash.com/photo-1559841644-08984562005a",
"altText": "Room",
"photoOrder": 1
}
]
},
{
"name": "Double w/ bath (double bed)",
"description": "Room with double bed and private bathroom",
"maxGuestNum": 2,
"orderHomePage": null,
"roomCategoryPhotos": [
{
"path": "https://images.unsplash.com/photo-1559841644-08984562005a",
"altText": "Room",
"photoOrder": 1
}
]
},
{
"name": "Double w/ bath (single bed)",
"description": "Room with 2 single beds and private bathroom",
"maxGuestNum": 2,
"orderHomePage": null,
"roomCategoryPhotos": [
{
"path": "https://images.unsplash.com/photo-1559841644-08984562005a",
"altText": "Room",
"photoOrder": 1
}
]
}
]
+12
View File
@@ -0,0 +1,12 @@
[
{
"amount": 1,
"roomCategory": 0,
"bedType": 0
},
{
"amount": 1,
"roomCategory": 2,
"bedType": 1
}
]
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace App\Consts;
use App\Entity\ConfigVariable;
use App\Enum\ConfigVariableSectionEnum;
//Variable List Constants
// --> This constants contains all defined Config Variables. For each one a array is stored as [varKey => [varSection, varDefaultValue]].
class ConfigVariableConsts
{
public const CONFIG_COMPANY_NAME = ["CompanyName" => [ConfigVariableSectionEnum::Company, "SomniaRooms"]];
public const CONFIG_COMPANY_WEBSITE = ["CompanyWebsite" => [ConfigVariableSectionEnum::Company, "somnia.dev"]];
public const CONFIG_COMPANY_LOGO = ["CompanyLogo" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_COMPANY_ADDRESS = ["CompanyAddress" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_COMPANY_EMAIL = ["CompanyEmail" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_COMPANY_PHONE = ["CompanyPhone" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_FRONTEND_NIGHTSFORCONTACT = ["NightAmountForContactDialog" => [ConfigVariableSectionEnum::Frontend, "10"]];
public const CONFIG_FRONTEND_GUESTSFORCONTACT = ["GuestNumberForContactDialog" => [ConfigVariableSectionEnum::Frontend, "6"]];
public const CONFIG_FRONTEND_CONTACTDIALOGBEHAVIOUR = ["ContactDialogShowBehaviour" => [ConfigVariableSectionEnum::Frontend, "show"]];
public const CONFIG_FRONTEND_SHOWNONAVAILABLEROOMS = ["ShowNonAvailableRooms" => [ConfigVariableSectionEnum::Frontend, true]];
public const CONFIG_FRONTEND_SOMNIAATTRIBUTION = ["SomniaAttributionNotice" => [ConfigVariableSectionEnum::Frontend, "full"]];
public const CONFIG_FRONTEND_PHOTOASPECTRATIO = ["PhotoAspectRatio" => [ConfigVariableSectionEnum::Frontend, "4:3"]];
public const CONFIG_PRICES_PRICEMETHOD = ["TotalPriceMethod" => [ConfigVariableSectionEnum::API_PRICES, "SUM"]];
public const CONFIG_PRICES_FAREMETHOD = ["FareSelectionMethod" => [ConfigVariableSectionEnum::API_PRICES, "HIGHER"]];
public const CONFIG_API_NAXNIGHTS = ["MaxAllowedNightAmount" => [ConfigVariableSectionEnum::API_CONFIG, "20"]];
public const CONFIG_API_NAXGUESTS = ["MaxAllowedGuestNumber" => [ConfigVariableSectionEnum::API_CONFIG, "20"]];
public const CONFIG_INTERNAL_SETUP = ["SETUP_STATUS" => [ConfigVariableSectionEnum::Internal, "1"]];
public static function getKeyFromConstant(array $constant): string
{
return array_key_first($constant);
}
public static function GetAllDefinedConfigVariablesList(): array
{
return array_merge(
ConfigVariableConsts::CONFIG_COMPANY_NAME,
ConfigVariableConsts::CONFIG_COMPANY_WEBSITE,
ConfigVariableConsts::CONFIG_COMPANY_LOGO,
ConfigVariableConsts::CONFIG_COMPANY_ADDRESS,
ConfigVariableConsts::CONFIG_COMPANY_EMAIL,
ConfigVariableConsts::CONFIG_COMPANY_PHONE,
ConfigVariableConsts::CONFIG_FRONTEND_NIGHTSFORCONTACT,
ConfigVariableConsts::CONFIG_FRONTEND_GUESTSFORCONTACT,
ConfigVariableConsts::CONFIG_FRONTEND_CONTACTDIALOGBEHAVIOUR,
ConfigVariableConsts::CONFIG_FRONTEND_SHOWNONAVAILABLEROOMS,
ConfigVariableConsts::CONFIG_FRONTEND_SOMNIAATTRIBUTION,
ConfigVariableConsts::CONFIG_FRONTEND_PHOTOASPECTRATIO,
ConfigVariableConsts::CONFIG_PRICES_PRICEMETHOD,
ConfigVariableConsts::CONFIG_PRICES_FAREMETHOD,
ConfigVariableConsts::CONFIG_API_NAXNIGHTS,
ConfigVariableConsts::CONFIG_API_NAXGUESTS,
ConfigVariableConsts::CONFIG_INTERNAL_SETUP,
);
}
}
@@ -3,7 +3,8 @@
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Entity\ConfigVariable; use App\Entity\ConfigVariable;
use App\Enum\ConfigSectionType; use App\Enum\ConfigVariableSectionEnum;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
@@ -21,10 +22,15 @@ class ConfigVariableCrudController extends AbstractCrudController
return $crud; return $crud;
} }
public function configureActions(Actions $actions): Actions
{
return $actions->disable("new", "delete");;
}
public function configureFields(string $pageName): iterable public function configureFields(string $pageName): iterable
{ {
yield ChoiceField::new ('section')->setChoices(ConfigSectionType::cases()); yield ChoiceField::new('section')->setChoices(ConfigVariableSectionEnum::cases());
yield TextField::new ('key'); yield TextField::new('key');
yield TextField::new ('value'); yield TextField::new('value');
} }
} }
+6 -5
View File
@@ -2,6 +2,7 @@
namespace App\Controller; namespace App\Controller;
use App\Consts\ConfigVariableConsts;
use App\DTO\AppSetupConfig; use App\DTO\AppSetupConfig;
use App\DTO\ExampleDataConfig; use App\DTO\ExampleDataConfig;
use App\Entity\ConfigVariable; use App\Entity\ConfigVariable;
@@ -9,9 +10,9 @@ use App\Form\AppSetupFormType;
use App\Form\ExampleDataFormType; use App\Form\ExampleDataFormType;
use App\Repository\RoomCategoryRepository; use App\Repository\RoomCategoryRepository;
use App\Service\AppSetupService; use App\Service\AppSetupService;
use App\Service\ConfigVariableService;
use App\Service\ExampleDataService; use App\Service\ExampleDataService;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Monolog\Logger;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -25,13 +26,13 @@ class AppSetupController extends AbstractController
LoggerInterface $logger, LoggerInterface $logger,
AppSetupService $setupService, AppSetupService $setupService,
Request $request, Request $request,
EntityManagerInterface $entityManager ConfigVariableService $configVars
): Response { ): Response {
$configRepo = $entityManager->getRepository(ConfigVariable::class); $setupVar_key = ConfigVariableConsts::getKeyFromConstant(ConfigVariableConsts::CONFIG_INTERNAL_SETUP);
$setupStatus = $configRepo->findBy(["key" => "SETUP_STATUS"]); $setupStatus = $configVars->getVariable($setupVar_key);
if ($setupStatus != null && $setupStatus[0] != null && $setupStatus[0]->getValue() == 1) { if ($setupStatus != null && $setupStatus->getValue()) {
$logger->warning("SomniaRooms was already setup"); $logger->warning("SomniaRooms was already setup");
return $this->GoToResultPage( return $this->GoToResultPage(
+6 -6
View File
@@ -3,22 +3,22 @@
namespace App\Entity; namespace App\Entity;
use App\Repository\ConfigVariableRepository; use App\Repository\ConfigVariableRepository;
use App\Enum\ConfigSectionType; use App\Enum\ConfigVariableSectionEnum;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ConfigVariableRepository::class)] #[ORM\Entity(repositoryClass: ConfigVariableRepository::class)]
class ConfigVariable class ConfigVariable
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue("NONE")] #[ORM\GeneratedValue(strategy: "NONE")]
#[ORM\Column(length: 32, unique: true, nullable: false)] #[ORM\Column(length: 32, unique: true, nullable: false)]
private string $key = ""; private string $key = "";
#[ORM\Column(length: 255, nullable: false)] #[ORM\Column(length: 255, nullable: false)]
private ?string $value = null; private ?string $value = null;
#[ORM\Column(type: 'string', enumType: ConfigSectionType::class, nullable: false)] #[ORM\Column(type: 'string', enumType: ConfigVariableSectionEnum::class, nullable: false)]
private ConfigSectionType $section = ConfigSectionType::General; private ConfigVariableSectionEnum $section = ConfigVariableSectionEnum::Internal;
public function getKey(): string public function getKey(): string
@@ -45,12 +45,12 @@ class ConfigVariable
return $this; return $this;
} }
public function getSection(): ConfigSectionType public function getSection(): ConfigVariableSectionEnum
{ {
return $this->section; return $this->section;
} }
public function setSection(ConfigSectionType $section): static public function setSection(ConfigVariableSectionEnum $section): static
{ {
$this->section = $section; $this->section = $section;
-12
View File
@@ -1,12 +0,0 @@
<?php
namespace App\Enum;
enum ConfigSectionType: string {
case General = 'GENERAL';
case Internal = 'INTERNAL';
case Company = 'COMPANY';
case Backend = 'BACK';
case Frontend = 'FRONT';
case Test = 'TEST';
}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace App\Enum;
enum ConfigVariableSectionEnum: string
{
case Company = 'COMPANY';
case Frontend = 'FRONTEND_CONFIG';
case API_PRICES = 'API_PRICES';
case API_CONFIG = 'API_CONFIG';
case Internal = 'INTERNAL';
}
+28 -3
View File
@@ -5,6 +5,7 @@ namespace App\Repository;
use App\Entity\ConfigVariable; use App\Entity\ConfigVariable;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Ds\Set;
/** /**
* @extends ServiceEntityRepository<ConfigVariable> * @extends ServiceEntityRepository<ConfigVariable>
@@ -51,14 +52,24 @@ class ConfigVariableRepository extends ServiceEntityRepository
* @param string Variable Key * @param string Variable Key
* @return ConfigVariable|null Config Variable. * @return ConfigVariable|null Config Variable.
*/ */
public function findVariableByKey($varKey) public function findVariableByKey($varKey): ConfigVariable|null
{ {
return $this->createQueryBuilder('c') $rs = $this->createQueryBuilder('c')
->select('c.key, c.value, c.section') ->select('c.key, c.value, c.section')
->andWhere('c.key = :val') ->andWhere('c.key = :val')
->setParameter('val', $varKey) ->setParameter('val', $varKey)
->setMaxResults(1)
->getQuery() ->getQuery()
->getOneOrNullResult(); ->getResult();
// FIXME!!!
if (!$rs) {
return null;
}
$var = new ConfigVariable();
$var->setKey($rs[0]["key"]);
$var->setValue($rs[0]["value"]);
$var->setSection($rs[0]["section"]);
return $var;
} }
/** /**
@@ -73,4 +84,18 @@ class ConfigVariableRepository extends ServiceEntityRepository
->getQuery() ->getQuery()
->getResult(); ->getResult();
} }
/**
* Get all DB config variables names on a Set.
* @return Set Returns a Set with all variable names.
*/
public function getVariableSet(): Set
{
$varList = $this->findAll();
$keyList = array_map(function ($var) {
return $var->getKey();
}, $varList);
return new Set($keyList);
}
} }
+14 -12
View File
@@ -2,10 +2,11 @@
namespace App\Service; namespace App\Service;
use App\Consts\ConfigVariableConsts;
use App\DTO\AppSetupConfig; use App\DTO\AppSetupConfig;
use App\Entity\ConfigVariable; use App\Entity\ConfigVariable;
use App\Entity\User; use App\Entity\User;
use App\Enum\ConfigSectionType; use App\Enum\ConfigVariableSectionEnum;
use App\Enum\UserRoleEnum; use App\Enum\UserRoleEnum;
use App\Repository\ConfigVariableRepository; use App\Repository\ConfigVariableRepository;
@@ -23,7 +24,7 @@ class AppSetupService
public function __construct( public function __construct(
private LoggerInterface $logger, private LoggerInterface $logger,
private EntityManagerInterface $entityManager, private EntityManagerInterface $entityManager,
private ConfigVariableRepository $repoVariables, private ConfigVariableService $configVars,
private UserPasswordHasherInterface $passwordHasher, private UserPasswordHasherInterface $passwordHasher,
private Security $security private Security $security
) {} ) {}
@@ -37,15 +38,18 @@ class AppSetupService
//Create system variables. //Create system variables.
$this->logger->info("SomniaRooms Setup - Creating system variables..."); $this->logger->info("SomniaRooms Setup - Creating system variables...");
$var_setup = $this->repoVariables->find("SETUP_STATUS"); //Not neccesary anymore. CONFIG_INTERNAL_SETUP will be initialized with all vars.
if (!$var_setup) { // $var_setup_key = ConfigVariableConsts::getKeyFromConstant(ConfigVariableConsts::CONFIG_INTERNAL_SETUP);
$var_setup = new ConfigVariable(); // $var_setup = $this->configVars->getVariable($var_setup_key);
$var_setup->setKey("SETUP_STATUS"); // if (!$var_setup) {
} // $var_setup = new ConfigVariable();
$var_setup->setValue("1"); // $var_setup->setKey($var_setup_key);
$var_setup->setSection(ConfigSectionType::Internal); // $this->entityManager->persist($var_setup);
$this->entityManager->persist($var_setup); // }
// $var_setup->setValue("1");
// $var_setup->setSection(ConfigVariableSectionEnum::Internal);
$this->configVars->InitVariablesOnSetup();
//Create user Admin. //Create user Admin.
$this->logger->info("SomniaRooms Setup - Adding super-admin user..."); $this->logger->info("SomniaRooms Setup - Adding super-admin user...");
@@ -53,8 +57,6 @@ class AppSetupService
$user->setRoles([UserRoleEnum::ROLE_USER->value, UserRoleEnum::ROLE_ADMIN->value, UserRoleEnum::ROLE_SUPERADMIN->value]); $user->setRoles([UserRoleEnum::ROLE_USER->value, UserRoleEnum::ROLE_ADMIN->value, UserRoleEnum::ROLE_SUPERADMIN->value]);
$plaintextPassword = $configData->getAdminPass(); $plaintextPassword = $configData->getAdminPass();
// hash the password (based on the security.yaml config for the $user class) // hash the password (based on the security.yaml config for the $user class)
$hashedPassword = $this->passwordHasher->hashPassword( $hashedPassword = $this->passwordHasher->hashPassword(
$user, $user,
+36 -13
View File
@@ -2,10 +2,14 @@
namespace App\Service; namespace App\Service;
use App\Consts\ConfigVariableConsts;
use App\Entity\ConfigVariable; use App\Entity\ConfigVariable;
use App\Enum\ConfigVariableSectionEnum;
use App\Repository\ConfigVariableRepository; use App\Repository\ConfigVariableRepository;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
/** /**
* Service to manage config variables. * Service to manage config variables.
@@ -14,33 +18,52 @@ class ConfigVariableService
{ {
public function __construct( public function __construct(
private ConfigVariableRepository $repoVariables private EntityManagerInterface $entityManager,
) {} private ConfigVariableRepository $repoVariables,
private LoggerInterface $logger,
) {}
public function getVariable(string $key) { public function getVariable(string $key): ConfigVariable|null
$var = $this->repoVariables->findVariableByKey($key); {
return $this->repoVariables->findVariableByKey($key);
} }
/** /**
* Create and init all variables. * Create and init all variables.
* To be used during Setup process. * To be used during Setup process.
*/ */
public function InitVariablesOnSetup(): void { public function InitVariablesOnSetup(): void
{
//Get a list of variables to check if those already exists.
//$this->entityManager->beginTransaction();
$varListDb = $this->repoVariables->getVariableSet();
$varListPHP = ConfigVariableConsts::GetAllDefinedConfigVariablesList();
$entityManager = $this->repoVariables->getEntityManager(); foreach ($varListPHP as $varKey => $varValue) {
$entityManager->beginTransaction(); if ($varListDb->contains($varKey)) {
$this->logger->info("Var $varKey already created. Skipped!", [$varKey, $varValue]);
continue;
}
$this->AddConfigVariable($varKey, $varValue[1], $varValue[0]);
}
try { try {
} catch (\Throwable $th) {
} $this->entityManager->rollback();
catch (\Throwable $th) {
$entityManager->rollback();
throw $th; throw $th;
} }
} }
private function AddConfigVariable(string $key, string $value, string $section): void { public function AddConfigVariable(string $key, string $value, ConfigVariableSectionEnum $section): void
{
$var = new ConfigVariable();
$var->setKey($key);
$var->setValue($value);
$var->setSection($section);
$this->entityManager->persist($var);
$this->logger->info("Variable added to DB", ["key" => $key, "value" => $value, "section" => $section]);
} }
} }
+124 -5
View File
@@ -4,36 +4,67 @@ namespace App\Service;
use App\DTO\AppSetupConfig; use App\DTO\AppSetupConfig;
use App\DTO\ExampleDataConfig; use App\DTO\ExampleDataConfig;
use App\Entity\BedType;
use App\Entity\ConfigVariable; use App\Entity\ConfigVariable;
use App\Entity\FareTable; use App\Entity\FareTable;
use App\Entity\RoomCategory; use App\Entity\RoomCategory;
use App\Entity\RoomCategoryBedType;
use App\Entity\RoomFare; use App\Entity\RoomFare;
use App\Entity\User; use App\Entity\User;
use App\Enum\ConfigSectionType;
use App\Enum\UserRoleEnum; use App\Enum\UserRoleEnum;
use App\Repository\ConfigVariableRepository; use App\Repository\ConfigVariableRepository;
use App\Repository\FareTableRepository; use App\Repository\FareTableRepository;
use App\Repository\RoomAvailabilityRepository; use App\Repository\RoomAvailabilityRepository;
use App\Repository\RoomCategoryRepository; use App\Repository\RoomCategoryRepository;
use App\Repository\RoomFareRepository; use App\Repository\RoomFareRepository;
use App\Serializer\Normalizer\EntityByIdDenormalizer;
use DateTime; use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use stdClass;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
/** /**
* Service for setup proccess. * Service for setup proccess.
*/ */
class ExampleDataService class ExampleDataService
{ {
private Serializer $sampleSerializer;
private FileLocator $fileLocator;
public function __construct( public function __construct(
private string $sampleDirectory,
private LoggerInterface $logger, private LoggerInterface $logger,
private EntityManagerInterface $entityManager, private EntityManagerInterface $entityManager,
private RoomCategoryRepository $roomCategoryRepository, private RoomCategoryRepository $roomCategoryRepository,
private RoomAvailabilityRepository $roomAvailabilityRepository, private RoomAvailabilityRepository $roomAvailabilityRepository,
private FareTableRepository $fareTableRepository, private FareTableRepository $fareTableRepository,
private RoomFareRepository $roomFareRepository, private RoomFareRepository $roomFareRepository
) {} ) {
$this->sampleSerializer = new Serializer(
array(
new ObjectNormalizer(
null,
null,
null,
new PhpDocExtractor()
),
new ArrayDenormalizer(),
),
array('json' => new JsonEncoder())
);
$this->fileLocator = new FileLocator($this->sampleDirectory);
}
public function AddExampleData(ExampleDataConfig $config): string public function AddExampleData(ExampleDataConfig $config): string
{ {
@@ -65,12 +96,53 @@ class ExampleDataService
return "OK"; return "OK";
} }
private function GenerateRoomCategories(): bool public function GenerateRoomCategories(): bool
{ {
//TODOME. //Add Example BedTypes.
$bedTypeList = $this->DeserializeSampleFile("SampleBedTypes.json", BedType::class . '[]');
$this->PersistEntityArray($bedTypeList);
//Add Example RoomCategories
$roomCategoryList = $this->DeserializeSampleFile("SampleRoomCategories.json", RoomCategory::class . '[]');
$this->PersistEntityArray($roomCategoryList);
//Add Example RoomCategoryBedTypes
$sampleRcBtData = $this->GetFileData("SampleRoomCategoryBedTypes.json");
$sampleRcBtContent = json_decode($sampleRcBtData);
foreach ($sampleRcBtContent as $contentElement) {
$bedType = null;
if (count($bedTypeList) > $contentElement->bedType) {
$bedType = $bedTypeList[$contentElement->bedType];
}
$roomCategory = null;
if (count($roomCategoryList) > $contentElement->roomCategory) {
$roomCategory = $roomCategoryList[$contentElement->roomCategory];
}
if (!$roomCategory || !$bedType) {
$this->logger->error(
"Invalid RoomCategoryBedType: RoomCategory or BedType not found!",
["json" => $contentElement, "bedtype" => $bedType, "roomcategory" => $roomCategory]
);
continue;
}
$item = new RoomCategoryBedType();
$item->setAmount($contentElement->amount);
$item->setBedType($bedType);
$item->setRoomCategory($roomCategory);
$this->entityManager->persist($item);
}
$this->entityManager->flush();
return true; return true;
} }
#region Generate Ramdom Data
private function GenerateAvailability(ExampleDataConfig $config): bool private function GenerateAvailability(ExampleDataConfig $config): bool
{ {
$roomCategoryList = $this->roomCategoryRepository->findAll(); $roomCategoryList = $this->roomCategoryRepository->findAll();
@@ -126,4 +198,51 @@ class ExampleDataService
return true; return true;
} }
#endregion
#region Load Sample Data
//TODO
#endregion
#region Utils
private function DeserializeSampleFile(string $fileName, string $class)
{
$data = $this->GetFileData($fileName);
if (!$data) return;
$itemList = $this->sampleSerializer->deserialize($data, $class, "json");
return $itemList;
}
private function GetFileData(string $fileName): string|null
{
$file = $this->fileLocator->locate($fileName, null);
$data = file_get_contents($file);
if (!$data) {
$this->logger->error(
"ExampleDataService - DeserializeSampleFile - Unable to get file contents.",
["file" => $fileName, "fileData" => $data]
);
return null;
}
return $data;
}
private function PersistEntityArray(array $itemList): void
{
foreach ($itemList as $item) {
try {
$this->entityManager->persist($item);
} catch (\Throwable $th) {
$this->logger->error("Item can't be persisted", ["item" => $item, "exception" => $th]);
}
}
}
#endregion
} }