Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7dbf5a1c3 |
@@ -27,7 +27,6 @@ public/bundles/
|
|||||||
tests/
|
tests/
|
||||||
var/
|
var/
|
||||||
vendor/
|
vendor/
|
||||||
node_modules/
|
|
||||||
.editorconfig
|
.editorconfig
|
||||||
.env.*.local
|
.env.*.local
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
+6
-8
@@ -21,16 +21,13 @@ indent_size = 2
|
|||||||
[*.json]
|
[*.json]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[{compose.yaml,compose.*.yaml}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.sh]
|
[*.sh]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
[*.xml.dist]
|
[*.xml{,.dist}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
@@ -43,18 +40,19 @@ indent_size = 2
|
|||||||
[.gitmodules]
|
[.gitmodules]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
[.php_cs.dist]
|
[.php_cs{,.dist}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
[composer.json]
|
[composer.json]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
[{compose,docker-compose}.*.{yaml,yml}]
|
[{,docker-}compose{,.*}.{yaml,yml}]
|
||||||
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.*Dockerfile]
|
[{,*.*}Dockerfile]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
[*.*Caddyfile]
|
[{,*.*}Caddyfile]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|||||||
@@ -16,25 +16,19 @@
|
|||||||
|
|
||||||
###> symfony/framework-bundle ###
|
###> symfony/framework-bundle ###
|
||||||
APP_ENV=dev
|
APP_ENV=dev
|
||||||
APP_SECRET=
|
APP_SECRET=d130654a5d6a8ab5945b20b22dfeee74
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||||
#
|
#
|
||||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
|
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||||
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||||
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||||
DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=16&charset=utf8"
|
DATABASE_URL="postgresql://app:[email protected]:54195/app?serverVersion=16&charset=utf8"
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
###> nelmio/cors-bundle ###
|
###> nelmio/cors-bundle ###
|
||||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||||
###< nelmio/cors-bundle ###
|
###< nelmio/cors-bundle ###
|
||||||
|
|
||||||
###> symfony/routing ###
|
|
||||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
|
||||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
|
||||||
DEFAULT_URI=http://localhost
|
|
||||||
###< symfony/routing ###
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
###> symfony/framework-bundle ###
|
|
||||||
APP_SECRET=344d36e897253340ff9de6cfacce2da9
|
|
||||||
###< symfony/framework-bundle ###
|
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{vars.REGISTRY_INSTANCE}}/somnia/somniarooms:devbuild
|
tags: ${{vars.REGISTRY_INSTANCE}}/daniel-garmig/somniarooms:devbuild
|
||||||
target: frankenphp_prod
|
target: frankenphp_prod
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
secrets: |
|
secrets: |
|
||||||
|
|||||||
-14
@@ -8,17 +8,3 @@
|
|||||||
/var/
|
/var/
|
||||||
/vendor/
|
/vendor/
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
# Draw.io backup file.
|
|
||||||
*bkp
|
|
||||||
|
|
||||||
# Bolt db files
|
|
||||||
[Bb]olt.db
|
|
||||||
|
|
||||||
# JS files
|
|
||||||
/node_modules/
|
|
||||||
|
|
||||||
###> symfony/asset-mapper ###
|
|
||||||
/public/assets/
|
|
||||||
/assets/vendor/
|
|
||||||
###< symfony/asset-mapper ###
|
|
||||||
|
|||||||
Vendored
-5
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"php.suggest.basic": false,
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnSaveMode": "modificationsIfAvailable"
|
|
||||||
}
|
|
||||||
+18
-26
@@ -13,8 +13,6 @@ FROM frankenphp_upstream AS frankenphp_base
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
VOLUME /app/var/
|
|
||||||
|
|
||||||
# persistent / runtime deps
|
# persistent / runtime deps
|
||||||
# hadolint ignore=DL3008
|
# hadolint ignore=DL3008
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
@@ -26,11 +24,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
|
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
install-php-extensions \
|
install-php-extensions \
|
||||||
@composer \
|
@composer \
|
||||||
apcu \
|
apcu \
|
||||||
intl \
|
intl \
|
||||||
opcache \
|
opcache \
|
||||||
zip \
|
zip \
|
||||||
;
|
;
|
||||||
|
|
||||||
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
|
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
|
||||||
@@ -42,41 +40,42 @@ RUN install-php-extensions pdo_pgsql
|
|||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
###< recipes ###
|
###< recipes ###
|
||||||
|
|
||||||
COPY --link frankenphp/conf.d/10-app.ini $PHP_INI_DIR/app.conf.d/
|
COPY --link frankenphp/conf.d/app.ini $PHP_INI_DIR/conf.d/
|
||||||
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
|
COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint
|
||||||
COPY --link frankenphp/Caddyfile /etc/frankenphp/Caddyfile
|
COPY --link frankenphp/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
|
||||||
ENTRYPOINT ["docker-entrypoint"]
|
ENTRYPOINT ["docker-entrypoint"]
|
||||||
|
|
||||||
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
|
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
|
||||||
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ]
|
CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ]
|
||||||
|
|
||||||
# Dev FrankenPHP image
|
# Dev FrankenPHP image
|
||||||
FROM frankenphp_base AS frankenphp_dev
|
FROM frankenphp_base AS frankenphp_dev
|
||||||
|
|
||||||
ENV APP_ENV=dev
|
ENV APP_ENV=dev XDEBUG_MODE=off
|
||||||
ENV XDEBUG_MODE=off
|
VOLUME /app/var/
|
||||||
ENV FRANKENPHP_WORKER_CONFIG=watch
|
|
||||||
|
|
||||||
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
|
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
|
||||||
|
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
install-php-extensions \
|
install-php-extensions \
|
||||||
xdebug \
|
xdebug \
|
||||||
;
|
;
|
||||||
|
|
||||||
COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/
|
COPY --link frankenphp/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/
|
||||||
|
|
||||||
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--watch" ]
|
CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ]
|
||||||
|
|
||||||
# Prod FrankenPHP image
|
# Prod FrankenPHP image
|
||||||
FROM frankenphp_base AS frankenphp_prod
|
FROM frankenphp_base AS frankenphp_prod
|
||||||
|
|
||||||
ENV APP_ENV=prod
|
ENV APP_ENV=prod
|
||||||
|
ENV FRANKENPHP_CONFIG="import worker.Caddyfile"
|
||||||
|
|
||||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||||
|
|
||||||
COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/
|
COPY --link frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/
|
||||||
|
COPY --link frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile
|
||||||
|
|
||||||
# prevent the reinstallation of vendors at every changes in the source code
|
# prevent the reinstallation of vendors at every changes in the source code
|
||||||
COPY --link composer.* symfony.* ./
|
COPY --link composer.* symfony.* ./
|
||||||
@@ -85,18 +84,11 @@ RUN set -eux; \
|
|||||||
|
|
||||||
# copy sources
|
# copy sources
|
||||||
COPY --link . ./
|
COPY --link . ./
|
||||||
|
RUN rm -Rf frankenphp/
|
||||||
|
|
||||||
# Remove development data.
|
|
||||||
RUN rm -Rf frankenphp/ \
|
|
||||||
rm -Rf .gitea/ \
|
|
||||||
rm -Rf .vscode/ \
|
|
||||||
rm -Rf public/images
|
|
||||||
|
|
||||||
# Setup application.
|
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
mkdir -p var/cache var/log; \
|
mkdir -p var/cache var/log; \
|
||||||
composer dump-autoload --classmap-authoritative --no-dev; \
|
composer dump-autoload --classmap-authoritative --no-dev; \
|
||||||
composer dump-env prod; \
|
composer dump-env prod; \
|
||||||
composer run-script --no-dev post-install-cmd; \
|
composer run-script --no-dev post-install-cmd; \
|
||||||
chmod +x bin/console; sync; \
|
chmod +x bin/console; sync;
|
||||||
php bin/console asset-map:compile
|
|
||||||
|
|||||||
@@ -1,661 +1,19 @@
|
|||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
Copyright (c) Fabien Potencier
|
||||||
Version 3, 19 November 2007
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
in the Software without restriction, including without limitation the rights
|
||||||
of this license document, but changing it is not allowed.
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
Preamble
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
The GNU Affero General Public License is a free, copyleft license for
|
The above copyright notice and this permission notice shall be included in all
|
||||||
software and other kinds of works, specifically designed to ensure
|
copies or substantial portions of the Software.
|
||||||
cooperation with the community in the case of network server software.
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
The licenses for most software and other practical works are designed
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
to take away your freedom to share and change the works. By contrast,
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
our General Public Licenses are intended to guarantee your freedom to
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
share and change all versions of a program--to make sure it remains free
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
software for all its users.
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|
||||||
SomniaRooms - A room booking platform.
|
|
||||||
Copyright (C) 2024 - Daniel García
|
|
||||||
|
|
||||||
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 <https://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
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
||||||
|
|||||||
@@ -6,111 +6,20 @@ Powered by:
|
|||||||
- PHP Symfony.
|
- PHP Symfony.
|
||||||
- Easyadmin.
|
- Easyadmin.
|
||||||
|
|
||||||
## Get started:
|
## Commands:
|
||||||
|
|
||||||
### Dev commands
|
|
||||||
|
|
||||||
Build containers:
|
Build containers:
|
||||||
```bash
|
```
|
||||||
docker compose build --no-cache
|
docker compose build --no-cache
|
||||||
```
|
```
|
||||||
|
|
||||||
Start Containers:
|
Start Containers:
|
||||||
```bash
|
```
|
||||||
# Run SomniaRooms
|
|
||||||
docker compose up --pull always -d --wait
|
docker compose up --pull always -d --wait
|
||||||
docker compose --env-file .\.env.local up -d --wait
|
docker compose --env-file .\.env.local up -d --wait
|
||||||
# Run SomniaRooms with debug.
|
|
||||||
docker compose --env-file .\.env.dev.local up -d --wait
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Stop containers
|
Stop containers
|
||||||
```bash
|
```
|
||||||
docker compose down --remove-orphans
|
docker compose down --remove-orphans
|
||||||
```
|
```
|
||||||
|
|
||||||
To use xdebug in VSCode you may need to configure some environment vars. A .env file (`.env.local`) can be used as:
|
|
||||||
```shell
|
|
||||||
XDEBUG_MODE=develop,debug
|
|
||||||
XDEBUG_DISCOVER_CLIENT_HOST=1
|
|
||||||
XDEBUG_IDEKEY=docker
|
|
||||||
XDEBUG_LOG=/dev/stdout
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Production commands:
|
|
||||||
|
|
||||||
When deploying to a production environment a production build should be used.
|
|
||||||
Then, deployment can be done as containers or using docker-compose.
|
|
||||||
|
|
||||||
**Build Prod image**
|
|
||||||
|
|
||||||
To build a production image:
|
|
||||||
```
|
|
||||||
docker build -t somniarooms:prod --target frankenphp_prod .
|
|
||||||
```
|
|
||||||
|
|
||||||
**Deploy prod image**
|
|
||||||
|
|
||||||
You may use a env file to configure the SomniaRoom container (`.env.prod.local`):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
SERVER_NAME=localhost
|
|
||||||
DATABASE_URL=postgresql://app:!ChangeMe!@somniadb:5432/somniarooms?serverVersion=16&charset=utf8
|
|
||||||
APP_SECRET=MYSECRET
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY=MYCADDY_MERCURE_JWT_SECRET
|
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY=MYCADDY_MERCURE_JWT_SECRET
|
|
||||||
FRANKENPHP_CONFIG="" #-> Avoid using FrankenPHP workers, as don't play well with Easyadmin.
|
|
||||||
```
|
|
||||||
|
|
||||||
You need a database for using SomniaRooms.
|
|
||||||
To run one on a container you can use this that match this example config:
|
|
||||||
```bash
|
|
||||||
docker run -d --name somniarooms-db --hostname somniadb -p 5432:5432 --env=POSTGRES_DB=somniarooms --env=POSTGRES_PASSWORD=!ChangeMe! --env=POSTGRES_USER=app --env=PGDATA=/var/lib/postgresql/data --volume=somniarooms_db:/var/lib/postgresql/data --network somniarooms-network postgres:16-alpine
|
|
||||||
```
|
|
||||||
|
|
||||||
Then SomniaRooms can be deployed on a container (change --env-file value to the created file):
|
|
||||||
```bash
|
|
||||||
docker run -d -it -p 80:80 -p 443:443 --name somniarooms --env-file ./.env.prod.local --network somniarooms-network somniarooms:prod
|
|
||||||
```
|
|
||||||
|
|
||||||
When deploying to prod is recomended to mount some directories as a docker volume or hostpath. This makes sure data is preserved after deleting the container.
|
|
||||||
- Image directory (`/app/public/images`)
|
|
||||||
- Logs (`/somnia_logs`)
|
|
||||||
|
|
||||||
If you are deploying your DB as a container, check the database docs and make sure database data won't be lost when updating or deleting the container. This example configuration store postgress data on a unnamed docket volume.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d -it -p 80:80 -p 443:443 --name somniarooms --env-file ./.env.prod.local --volume=somniarooms_images:/app/public/images:rw --network somniarooms-network somniarooms:prod
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want your app and DB to comunicate using it's hostname (like on this example) you should create a docker network.
|
|
||||||
```bash
|
|
||||||
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!
|
|
||||||
|
|
||||||
Go to the app address (`https://localhost`) and it will show Symfony welcome page (or a error when running on prod).
|
|
||||||
|
|
||||||
Go to addres `https://localhost/setup` to inicialize app users and data.
|
|
||||||
|
|
||||||
During setup you must configure the admin account.
|
|
||||||
If you check "Add example data" you will be redirected to example data setup.
|
|
||||||
Example data can be added anytime at `https://localhost/debug/exampleData`. There is needed to use a Superadmin account.
|
|
||||||
|
|
||||||
Then, you the admin panel is at `https://localhost/admin`
|
|
||||||
|
|
||||||
## Logs.
|
|
||||||
|
|
||||||
By default logs are saved to `/app/var/log` on an unamed container volume.
|
|
||||||
For development, `/app/var/log` directory is mounted to `./var/log` on the local filesystem.
|
|
||||||
|
|
||||||
This config can be overriden setting a different mount for `/app/var/log` when creating the containers.
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
* Welcome to your app's main JavaScript file!
|
|
||||||
*
|
|
||||||
* This file will be included onto the page via the importmap() Twig function,
|
|
||||||
* which should already be in your base.html.twig.
|
|
||||||
*/
|
|
||||||
import './styles/app.css';
|
|
||||||
|
|
||||||
console.log('SomniaRooms Admin Panel - Assets loaded! 🎉');
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 257"><circle cx="128" cy="128.827" r="128" fill="#1a171b"/><path fill="#fff" d="M183.706 48.124c-12.986.453-24.32 7.61-32.757 17.51c-9.342 10.855-15.557 23.73-20.035 36.872c-8.01-6.565-14.19-15.064-27.041-18.77c-9.933-2.852-20.366-1.674-29.96 5.474c-4.545 3.395-7.676 8.527-9.165 13.351c-3.855 12.537 4.053 23.694 7.645 27.7l7.853 8.416c1.619 1.65 5.518 5.955 3.612 12.127c-2.06 6.71-10.15 11.055-18.448 8.495c-3.706-1.13-9.03-3.891-7.838-7.779c.493-1.59 1.631-2.78 2.241-4.155c.56-1.181.827-2.067.997-2.587c1.516-4.95-.555-11.39-5.857-13.025c-4.946-1.516-10.007-.315-11.969 6.054c-2.225 7.235 1.237 20.366 19.783 26.084c21.729 6.676 40.11-5.155 42.717-20.586c1.642-9.665-2.722-16.845-10.717-26.08l-6.514-7.204c-3.946-3.942-5.301-10.661-1.217-15.825c3.446-4.356 8.354-6.215 16.392-4.029c11.733 3.186 16.963 11.327 25.69 17.893c-3.603 11.819-5.958 23.682-8.09 34.32l-1.299 7.931c-6.238 32.721-11 50.688-23.375 61.003c-2.493 1.773-6.057 4.427-11.429 4.612c-2.816.087-3.726-1.85-3.765-2.694c-.067-1.977 1.599-2.883 2.706-3.773c1.654-.902 4.155-2.398 3.985-7.191c-.18-5.664-4.872-10.575-11.654-10.35c-5.08.173-12.823 4.954-12.532 13.705c.303 9.039 8.728 15.813 21.43 15.384c6.79-.233 21.952-2.997 36.895-20.76c17.392-20.362 22.256-43.705 25.915-60.79l4.084-22.556c2.269.272 4.695.453 7.334.516c21.661.457 32.496-10.763 32.657-18.924c.107-4.939-3.241-9.799-7.928-9.689c-3.355.095-7.57 2.328-8.582 6.968c-.988 4.552 6.893 8.66.733 12.65c-4.376 2.832-12.221 4.828-23.269 3.206l2.009-11.103c4.1-21.055 9.157-46.954 28.341-47.584c1.398-.071 6.514.063 6.633 3.446c.035 1.13-.245 1.418-1.568 4.005c-1.347 2.017-1.855 3.734-1.792 5.707c.185 5.376 4.273 8.909 10.185 8.696c7.916-.256 10.193-7.963 10.063-11.921c-.32-9.3-10.122-15.175-23.1-14.75"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 96 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bed"><path d="M2 4v16"/><path d="M2 8h18a2 2 0 0 1 2 2v10"/><path d="M2 17h20"/><path d="M6 8v9"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 306 B |
@@ -1,48 +0,0 @@
|
|||||||
:root {
|
|
||||||
--main-border-radius: 5pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.container {
|
|
||||||
margin: 1em auto;
|
|
||||||
max-width: 800px;
|
|
||||||
width: 75%;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: var(--main-border-radius);
|
|
||||||
border: 1px solid #dad9d9;
|
|
||||||
box-shadow: 2px 2px 4px #dad9d9;
|
|
||||||
background-color: #F5F5F5;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-section {
|
|
||||||
position: relative;
|
|
||||||
margin-block: 10px;
|
|
||||||
padding-block: 8px;
|
|
||||||
margin-inline: auto;
|
|
||||||
width: 80%;
|
|
||||||
|
|
||||||
border: 1px solid #dad9d9;
|
|
||||||
border-radius: var(--main-border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg-container {
|
|
||||||
max-width: 70%;
|
|
||||||
margin-inline: auto;
|
|
||||||
|
|
||||||
padding-block: 5px;
|
|
||||||
padding-inline: 8px;
|
|
||||||
|
|
||||||
border-radius: var(--main-border-radius);
|
|
||||||
border: 1px solid #dad9d9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg-error {
|
|
||||||
background-color: rgba(164, 0, 0, 0.5);
|
|
||||||
border-color: rgba(87, 0, 0, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg-warn {
|
|
||||||
background-color: rgba(255, 234, 6, 0.5);
|
|
||||||
border-color: rgba(204, 187, 0, 0.75);
|
|
||||||
}
|
|
||||||
+3
-12
@@ -6,27 +6,18 @@ services:
|
|||||||
target: frankenphp_dev
|
target: frankenphp_dev
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app
|
- ./:/app
|
||||||
- ./frankenphp/Caddyfile:/etc/frankenphp/Caddyfile:ro
|
- ./frankenphp/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
- ./frankenphp/conf.d/20-app.dev.ini:/usr/local/etc/php/app.conf.d/20-app.dev.ini:ro
|
- ./frankenphp/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini:ro
|
||||||
# If you develop on Mac or Windows you can remove the vendor/ directory
|
# If you develop on Mac or Windows you can remove the vendor/ directory
|
||||||
# from the bind-mount for better performance by enabling the next line:
|
# from the bind-mount for better performance by enabling the next line:
|
||||||
- /app/vendor
|
- /app/vendor
|
||||||
# App logs can be bind-mount to host folder.
|
|
||||||
- ./var/log:/somnia_logs
|
|
||||||
environment:
|
environment:
|
||||||
FRANKENPHP_WORKER_CONFIG: watch
|
|
||||||
FRANKENPHP_USEWORKER: none # <-- Disable FrankenPHP Workers for development as don't reload on code changes.
|
|
||||||
MERCURE_EXTRA_DIRECTIVES: demo
|
MERCURE_EXTRA_DIRECTIVES: demo
|
||||||
# See https://xdebug.org/docs/all_settings#mode
|
# See https://xdebug.org/docs/all_settings#mode
|
||||||
XDEBUG_MODE: "${XDEBUG_MODE:-off}"
|
XDEBUG_MODE: "${XDEBUG_MODE:-off}"
|
||||||
XDEBUG_CONFIG: "client_host=${XDEBUG_CLIENT_HOST:-host.containers.internal} discover_client_host=${XDEBUG_DISCOVER_CLIENT_HOST:-off}"
|
|
||||||
APP_ENV: "${APP_ENV:-dev}"
|
|
||||||
# Change APP log path.
|
|
||||||
# SOMNIA_LOGSDIR: "/somnia_logs"
|
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
# Ensure that host.docker.internal is correctly defined on Linux
|
# Ensure that host.docker.internal is correctly defined on Linux
|
||||||
- host.docker.internal:host-gateway
|
- host.docker.internal:host-gateway
|
||||||
- host.containers.internal:host-gateway
|
|
||||||
tty: true
|
tty: true
|
||||||
|
|
||||||
###> symfony/mercure-bundle ###
|
###> symfony/mercure-bundle ###
|
||||||
@@ -35,5 +26,5 @@ services:
|
|||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
database:
|
database:
|
||||||
ports:
|
ports:
|
||||||
- "5432"
|
- 54195:5432
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|||||||
+5
-9
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
php:
|
php:
|
||||||
image: somniarooms
|
image: ${IMAGES_PREFIX:-}app-php
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
|
SERVER_NAME: ${SERVER_NAME:-localhost}, php:80
|
||||||
@@ -9,19 +9,17 @@ services:
|
|||||||
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
|
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
|
||||||
TRUSTED_HOSTS: ^${SERVER_NAME:-example\.com|localhost}|php$$
|
TRUSTED_HOSTS: ^${SERVER_NAME:-example\.com|localhost}|php$$
|
||||||
# Run "composer require symfony/orm-pack" to install and configure Doctrine ORM
|
# Run "composer require symfony/orm-pack" to install and configure Doctrine ORM
|
||||||
DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-16}&charset=${POSTGRES_CHARSET:-utf8}
|
DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}&charset=${POSTGRES_CHARSET:-utf8}
|
||||||
# Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration
|
# Run "composer require symfony/mercure-bundle" to install and configure the Mercure integration
|
||||||
MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
|
MERCURE_URL: ${CADDY_MERCURE_URL:-http://php/.well-known/mercure}
|
||||||
MERCURE_PUBLIC_URL: https://${SERVER_NAME:-localhost}/.well-known/mercure
|
MERCURE_PUBLIC_URL: https://${SERVER_NAME:-localhost}/.well-known/mercure
|
||||||
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
|
MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
|
||||||
FRANKENPHP_USEWORKER: worker # <-- Disable FrankenPHP Workers as don't work well with current EasyAdmin version. This is fixed on newer versions.
|
|
||||||
# The two next lines can be removed after initial installation
|
# The two next lines can be removed after initial installation
|
||||||
SYMFONY_VERSION: ${SYMFONY_VERSION:-}
|
SYMFONY_VERSION: ${SYMFONY_VERSION:-}
|
||||||
STABILITY: ${STABILITY:-stable}
|
STABILITY: ${STABILITY:-stable}
|
||||||
volumes:
|
volumes:
|
||||||
- caddy_data:/data
|
- caddy_data:/data
|
||||||
- caddy_config:/config
|
- caddy_config:/config
|
||||||
- app_logs:/somnia_logs # <-- Volume for App logs.
|
|
||||||
ports:
|
ports:
|
||||||
# HTTP
|
# HTTP
|
||||||
- target: 80
|
- target: 80
|
||||||
@@ -43,19 +41,18 @@ services:
|
|||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
database:
|
database:
|
||||||
image: postgres:${POSTGRES_VERSION:-16}-alpine
|
image: postgres:${POSTGRES_VERSION:-16}-alpine
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-app}
|
POSTGRES_DB: ${POSTGRES_DB:-app}
|
||||||
# You should definitely change the password in production
|
# You should definitely change the password in production
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
|
||||||
POSTGRES_USER: ${POSTGRES_USER:-app}
|
POSTGRES_USER: ${POSTGRES_USER:-app}
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
|
test: ["CMD", "pg_isready"]
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 60s
|
start_period: 60s
|
||||||
volumes:
|
volumes:
|
||||||
- app_database:/var/lib/postgresql/data:rw
|
- database_data:/var/lib/postgresql/data:rw
|
||||||
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
|
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
|
||||||
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
@@ -63,10 +60,9 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
caddy_data:
|
caddy_data:
|
||||||
caddy_config:
|
caddy_config:
|
||||||
app_logs:
|
|
||||||
###> symfony/mercure-bundle ###
|
###> symfony/mercure-bundle ###
|
||||||
###< symfony/mercure-bundle ###
|
###< symfony/mercure-bundle ###
|
||||||
|
|
||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
app_database:
|
database_data:
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|||||||
+20
-35
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "dgm/somnia-rooms",
|
"name": "dgm/somnia-rooms",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "AGPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"description": "A FOSS Hotel room booking system.",
|
"description": "A FOSS Hotel room booking system.",
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
@@ -9,40 +9,26 @@
|
|||||||
"php": ">=8.3.6",
|
"php": ">=8.3.6",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"doctrine/dbal": "^4.2",
|
"doctrine/dbal": "^3",
|
||||||
"doctrine/doctrine-bundle": "^2.14",
|
"doctrine/doctrine-bundle": "^2.12",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.3",
|
"doctrine/doctrine-migrations-bundle": "^3.3",
|
||||||
"doctrine/orm": "^3.3",
|
"doctrine/orm": "^3.1",
|
||||||
"easycorp/easyadmin-bundle": "^4.25",
|
"easycorp/easyadmin-bundle": "^4.10",
|
||||||
"nelmio/api-doc-bundle": "^5.0",
|
|
||||||
"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": "^2.0",
|
"phpstan/phpdoc-parser": "^1.28",
|
||||||
"runtime/frankenphp-symfony": "^0.2.0",
|
"runtime/frankenphp-symfony": "^0.2.0",
|
||||||
"symfony/asset": "7.3.*",
|
"symfony/console": "7.0.*",
|
||||||
"symfony/asset-mapper": "7.3.*",
|
"symfony/dotenv": "7.0.*",
|
||||||
"symfony/console": "7.3.*",
|
|
||||||
"symfony/dotenv": "7.3.*",
|
|
||||||
"symfony/flex": "^2",
|
"symfony/flex": "^2",
|
||||||
"symfony/form": "7.3.*",
|
"symfony/form": "7.0.*",
|
||||||
"symfony/framework-bundle": "7.3.*",
|
"symfony/framework-bundle": "7.0.*",
|
||||||
"symfony/http-client": "7.3.*",
|
"symfony/property-access": "7.0.*",
|
||||||
"symfony/mime": "7.3.*",
|
"symfony/property-info": "7.0.*",
|
||||||
"symfony/monolog-bundle": "^3.10",
|
"symfony/runtime": "7.0.*",
|
||||||
"symfony/property-access": "7.3.*",
|
"symfony/security-bundle": "7.0.*",
|
||||||
"symfony/property-info": "7.3.*",
|
"symfony/serializer": "7.0.*",
|
||||||
"symfony/runtime": "7.3.*",
|
"symfony/yaml": "7.0.*"
|
||||||
"symfony/security-bundle": "7.3.*",
|
|
||||||
"symfony/serializer": "7.3.*",
|
|
||||||
"symfony/twig-bundle": "7.3.*",
|
|
||||||
"symfony/ux-icons": "^2.25",
|
|
||||||
"symfony/ux-twig-component": "^2.25",
|
|
||||||
"symfony/validator": "7.3.*",
|
|
||||||
"symfony/var-exporter": "7.3.*",
|
|
||||||
"symfony/yaml": "7.3.*",
|
|
||||||
"twig/extra-bundle": "^3.0",
|
|
||||||
"twig/twig": "^3.0"
|
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
@@ -75,8 +61,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"auto-scripts": {
|
"auto-scripts": {
|
||||||
"cache:clear": "symfony-cmd",
|
"cache:clear": "symfony-cmd",
|
||||||
"assets:install %PUBLIC_DIR%": "symfony-cmd",
|
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||||
"importmap:install": "symfony-cmd"
|
|
||||||
},
|
},
|
||||||
"post-install-cmd": [
|
"post-install-cmd": [
|
||||||
"@auto-scripts"
|
"@auto-scripts"
|
||||||
@@ -91,13 +76,13 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "7.3.*",
|
"require": "7.0.*",
|
||||||
"docker": true
|
"docker": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/maker-bundle": "^1.58",
|
"symfony/maker-bundle": "^1.58",
|
||||||
"symfony/stopwatch": "7.3.*",
|
"symfony/stopwatch": "7.0.*",
|
||||||
"symfony/web-profiler-bundle": "7.3.*"
|
"symfony/web-profiler-bundle": "7.0.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+815
-2471
File diff suppressed because it is too large
Load Diff
@@ -10,9 +10,4 @@ return [
|
|||||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||||
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
|
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
|
||||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
|
||||||
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
|
|
||||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
|
||||||
Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true],
|
|
||||||
Symfony\UX\Icons\UXIconsBundle::class => ['all' => true],
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
framework:
|
|
||||||
asset_mapper:
|
|
||||||
# The paths to make available to the asset mapper.
|
|
||||||
paths:
|
|
||||||
- assets/
|
|
||||||
missing_import_mode: strict
|
|
||||||
|
|
||||||
when@prod:
|
|
||||||
framework:
|
|
||||||
asset_mapper:
|
|
||||||
missing_import_mode: warn
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Enable stateless CSRF protection for forms and logins/logouts
|
|
||||||
framework:
|
|
||||||
form:
|
|
||||||
csrf_protection:
|
|
||||||
token_id: submit
|
|
||||||
|
|
||||||
csrf_protection:
|
|
||||||
stateless_token_ids:
|
|
||||||
- submit
|
|
||||||
- authenticate
|
|
||||||
- logout
|
|
||||||
@@ -14,8 +14,6 @@ doctrine:
|
|||||||
report_fields_where_declared: true
|
report_fields_where_declared: true
|
||||||
validate_xml_mapping: true
|
validate_xml_mapping: true
|
||||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
identity_generation_preferences:
|
|
||||||
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
|
||||||
auto_mapping: true
|
auto_mapping: true
|
||||||
mappings:
|
mappings:
|
||||||
App:
|
App:
|
||||||
@@ -25,7 +23,7 @@ doctrine:
|
|||||||
prefix: 'App\Entity'
|
prefix: 'App\Entity'
|
||||||
alias: App
|
alias: App
|
||||||
controller_resolver:
|
controller_resolver:
|
||||||
auto_mapping: false
|
auto_mapping: true
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
doctrine:
|
doctrine:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||||
framework:
|
framework:
|
||||||
secret: '%env(APP_SECRET)%'
|
secret: '%env(APP_SECRET)%'
|
||||||
|
#csrf_protection: true
|
||||||
|
|
||||||
# Note that the session will be started ONLY if you read or write from it.
|
# Note that the session will be started ONLY if you read or write from it.
|
||||||
session: true
|
session: true
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
monolog:
|
|
||||||
channels:
|
|
||||||
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
|
||||||
|
|
||||||
when@dev:
|
|
||||||
monolog:
|
|
||||||
handlers:
|
|
||||||
main:
|
|
||||||
type: rotating_file
|
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
|
||||||
level: info
|
|
||||||
channels: ["!event", "!doctrine"]
|
|
||||||
max_files: 0
|
|
||||||
doctrine:
|
|
||||||
type: rotating_file
|
|
||||||
path: "%kernel.logs_dir%/db_%kernel.environment%.log"
|
|
||||||
level: info
|
|
||||||
channels: ["doctrine", "request"]
|
|
||||||
max_files: 10
|
|
||||||
# uncomment to get logging in your browser
|
|
||||||
# you may have to allow bigger header sizes in your Web server configuration
|
|
||||||
#firephp:
|
|
||||||
# type: firephp
|
|
||||||
# level: info
|
|
||||||
#chromephp:
|
|
||||||
# type: chromephp
|
|
||||||
# level: info
|
|
||||||
console:
|
|
||||||
type: console
|
|
||||||
process_psr_3_messages: false
|
|
||||||
channels: ["!event", "!doctrine", "!console"]
|
|
||||||
|
|
||||||
when@test:
|
|
||||||
monolog:
|
|
||||||
handlers:
|
|
||||||
main:
|
|
||||||
type: fingers_crossed
|
|
||||||
action_level: error
|
|
||||||
handler: nested
|
|
||||||
excluded_http_codes: [404, 405]
|
|
||||||
channels: ["!event"]
|
|
||||||
nested:
|
|
||||||
type: stream
|
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
|
||||||
level: debug
|
|
||||||
|
|
||||||
when@prod:
|
|
||||||
monolog:
|
|
||||||
handlers:
|
|
||||||
main:
|
|
||||||
type: fingers_crossed
|
|
||||||
action_level: error
|
|
||||||
handler: nested
|
|
||||||
excluded_http_codes: [404, 405]
|
|
||||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
|
||||||
nested:
|
|
||||||
type: stream
|
|
||||||
path: php://stderr
|
|
||||||
level: debug
|
|
||||||
formatter: monolog.formatter.json
|
|
||||||
console:
|
|
||||||
type: console
|
|
||||||
process_psr_3_messages: false
|
|
||||||
channels: ["!event", "!doctrine"]
|
|
||||||
deprecation:
|
|
||||||
type: stream
|
|
||||||
channels: [deprecation]
|
|
||||||
path: php://stderr
|
|
||||||
formatter: monolog.formatter.json
|
|
||||||
logfile:
|
|
||||||
type: rotating_file
|
|
||||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
|
||||||
level: info
|
|
||||||
channels: ["!event"]
|
|
||||||
max_files: 60
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
nelmio_api_doc:
|
|
||||||
type_info: true
|
|
||||||
documentation:
|
|
||||||
info:
|
|
||||||
title: SomniaRooms
|
|
||||||
description: A FOSS Hotel room booking system.
|
|
||||||
version: 1.0.0
|
|
||||||
areas: # to filter documented areas
|
|
||||||
path_patterns:
|
|
||||||
- ^/api(?!/doc$) # Accepts routes under /api except /api/doc
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
framework:
|
|
||||||
property_info:
|
|
||||||
with_constructor_extractor: true
|
|
||||||
@@ -2,7 +2,7 @@ framework:
|
|||||||
router:
|
router:
|
||||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||||
default_uri: '%env(DEFAULT_URI)%'
|
#default_uri: http://localhost
|
||||||
|
|
||||||
when@prod:
|
when@prod:
|
||||||
framework:
|
framework:
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ security:
|
|||||||
# Note: Only the *first* access control that matches will be used
|
# Note: Only the *first* access control that matches will be used
|
||||||
access_control:
|
access_control:
|
||||||
- { path: ^/admin, roles: ROLE_ADMIN }
|
- { path: ^/admin, roles: ROLE_ADMIN }
|
||||||
- { path: ^/debug, roles: ROLE_SUPERADMIN }
|
|
||||||
# - { path: ^/profile, roles: ROLE_USER }
|
# - { path: ^/profile, roles: ROLE_USER }
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
|
|||||||
@@ -2,4 +2,6 @@ framework:
|
|||||||
default_locale: en
|
default_locale: en
|
||||||
translator:
|
translator:
|
||||||
default_path: '%kernel.project_dir%/translations'
|
default_path: '%kernel.project_dir%/translations'
|
||||||
|
fallbacks:
|
||||||
|
- en
|
||||||
providers:
|
providers:
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
twig_component:
|
|
||||||
anonymous_template_directory: 'components/'
|
|
||||||
defaults:
|
|
||||||
# Namespace & directory for components
|
|
||||||
App\Twig\Components\: 'components/'
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
ux_icons:
|
|
||||||
# The local directory where icons are stored
|
|
||||||
icon_dir: "%kernel.project_dir%/assets/icons"
|
|
||||||
|
|
||||||
# Default attributes to add to all icons
|
|
||||||
default_icon_attributes:
|
|
||||||
width: "1.25em"
|
|
||||||
|
|
||||||
# Icon aliases (alias => icon name)
|
|
||||||
aliases:
|
|
||||||
"tabler:save": "tabler:device-floppy"
|
|
||||||
|
|
||||||
# Configuration for the "on demand" icons powered by Iconify.design
|
|
||||||
iconify:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
# Whether to use the "on demand" icons powered by Iconify.design
|
|
||||||
on_demand: true
|
|
||||||
|
|
||||||
# The endpoint for the Iconify API
|
|
||||||
endpoint: "https://api.iconify.design"
|
|
||||||
|
|
||||||
# Whether to ignore errors when an icon is not found
|
|
||||||
ignore_not_found: true
|
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
when@dev:
|
when@dev:
|
||||||
web_profiler:
|
web_profiler:
|
||||||
toolbar: true
|
toolbar: true
|
||||||
|
intercept_redirects: false
|
||||||
|
|
||||||
framework:
|
framework:
|
||||||
profiler:
|
profiler:
|
||||||
|
only_exceptions: false
|
||||||
collect_serializer_data: true
|
collect_serializer_data: true
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
|
web_profiler:
|
||||||
|
toolbar: false
|
||||||
|
intercept_redirects: false
|
||||||
|
|
||||||
framework:
|
framework:
|
||||||
profiler:
|
profiler: { collect: false }
|
||||||
collect: false
|
|
||||||
collect_serializer_data: true
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
# config/routes/easyadmin.yaml
|
|
||||||
easyadmin:
|
|
||||||
resource: .
|
|
||||||
type: easyadmin.routes
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
when@dev:
|
when@dev:
|
||||||
_errors:
|
_errors:
|
||||||
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
|
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||||
prefix: /_error
|
prefix: /_error
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
# Expose your documentation as JSON swagger compliant
|
|
||||||
app.swagger:
|
|
||||||
path: /api/doc.json
|
|
||||||
methods: GET
|
|
||||||
defaults: { _controller: nelmio_api_doc.controller.swagger }
|
|
||||||
|
|
||||||
app.swagger_ui:
|
|
||||||
path: /api/swagger
|
|
||||||
methods: GET
|
|
||||||
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
when@dev:
|
when@dev:
|
||||||
web_profiler_wdt:
|
web_profiler_wdt:
|
||||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
|
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||||
prefix: /_wdt
|
prefix: /_wdt
|
||||||
|
|
||||||
web_profiler_profiler:
|
web_profiler_profiler:
|
||||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
|
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||||
prefix: /_profiler
|
prefix: /_profiler
|
||||||
|
|||||||
@@ -4,11 +4,6 @@
|
|||||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
parameters:
|
parameters:
|
||||||
# Allow custom path for log files.
|
|
||||||
env(SOMNIA_LOGSDIR): '/somnia_logs'
|
|
||||||
kernel.logs_dir: '%env(SOMNIA_LOGSDIR)%'
|
|
||||||
# Set default value for APP_SECRET.
|
|
||||||
env(APP_SECRET): 'ChangeMySecret'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
@@ -25,13 +20,5 @@ services:
|
|||||||
- '../src/Entity/'
|
- '../src/Entity/'
|
||||||
- '../src/Kernel.php'
|
- '../src/Kernel.php'
|
||||||
|
|
||||||
App\Service\FileUploaderService:
|
|
||||||
arguments:
|
|
||||||
$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
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+36
-171
@@ -1,189 +1,54 @@
|
|||||||
<!-- 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 `NightCountForContactDialog`](#var-nightcountforcontactdialog)
|
|
||||||
- [Var `GuestNumberForContactDialog`](#var-guestnumberforcontactdialog)
|
|
||||||
- [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`_).
|
||||||
|
|
||||||
## Config Sections
|
## List of sections:
|
||||||
|
|
||||||
| Section | SectionName | Description | Availability |
|
WIP
|
||||||
| :------------------------------------------ | :-------------- | :--------------------------------------------- | :----------: |
|
|
||||||
| [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 | Public |
|
|
||||||
|
|
||||||
|
TODO -> Add default table with basic information to all sections.
|
||||||
|
|
||||||
### Config implementation status
|
### **Company**
|
||||||
|
Variables in this section contains information about the Hotel.
|
||||||
|
|
||||||
Not all variables are fully implemented, some are experimental or have options that are.
|
- SectionName -> company
|
||||||
Current status for a variable and it's options:
|
- Availability -> public
|
||||||
- New/Experimental: :test_tube:
|
- Nº variables -> X
|
||||||
- Fully available: :heavy_check_mark:
|
|
||||||
- Partially available: :construction:
|
|
||||||
- Not yet: :x:
|
|
||||||
|
|
||||||
|
**Variable list:**
|
||||||
|
|
||||||
## Company Information
|
- Name - Name of hotel.
|
||||||
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 | :heavy_check_mark: |
|
|
||||||
| CompanyWebsite | string | Company website URL | Valid URL to company website. | somnia.dev | :heavy_check_mark: |
|
|
||||||
| CompanyContactFormUrl | string | Company Contact form URL | A empty string or a URL to a page with a contact form. | "" | :heavy_check_mark: |
|
|
||||||
| CompanyLogo | string | Company Logo URL | Valid URL to company logo. Somnia Backend system can be used | "" | :heavy_check_mark: |
|
|
||||||
| CompanyLogoSize | string | Company Logo Size | A valid CSS size for the company logo. e.g. "96px" | "" | :heavy_check_mark: |
|
|
||||||
| CompanyAddress | string | Company Address | - | "" | :heavy_check_mark: |
|
|
||||||
| CompanyEmail | string | Company email address | - | "" | :heavy_check_mark: |
|
|
||||||
| CompanyPhone | string | Company phone number | - | "" | :heavy_check_mark: |
|
|
||||||
|
|
||||||
|
|
||||||
## Front-end Config
|
|
||||||
This variable set the behaviour of front-end app.
|
|
||||||
|
|
||||||
| Variable | Type | Description | Constraints | Default Value | Status |
|
|
||||||
| :-------------------------- | :------ | :------------------------------------------------- | :---------------------------------------------- | :------------ | :------------: |
|
|
||||||
| NightCountForContactDialog | int | Set the amount of nights to show a contact dialog. | See [details](#var-NightCountForContactDialog) | 10 | :test_tube: |
|
|
||||||
| GuestNumberForContactDialog | int | Set the guest number to show a contact dialog. | See [details](#var-guestnumberforcontactdialog) | 6 | :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 `NightCountForContactDialog`
|
|
||||||
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. | :test_tube: |
|
|
||||||
| Always | 0 | Contact dialog is always shown. | :test_tube: |
|
|
||||||
| Custom Amount | - | Set the minimun number of nights to show the dialog. | :test_tube: |
|
|
||||||
|
|
||||||
|
|
||||||
### 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 `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: | :test_tube: |
|
|
||||||
| 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 | :test_tube: |
|
|
||||||
| FareSelectionMethod | option | What fare to select when multiple fares are available | See [details](#var-fareselectionmethod) | HIGHER | :test_tube: |
|
|
||||||
|
|
||||||
|
|
||||||
### 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. | :test_tube: |
|
|
||||||
| High Price | HIGHER | The highest price will be apply for each day. | :test_tube: |
|
|
||||||
| Total Sum | SUM | Total price will be the sum of the price for each day. | :test_tube: |
|
|
||||||
| Average Price | AVERAGE | The average for all days will be calculated and apply to each day. | :test_tube: |
|
|
||||||
|
|
||||||
|
|
||||||
### 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. | :test_tube: |
|
|
||||||
| High Price | HIGHER | Higher available price will be used. | :test_tube: |
|
|
||||||
| Average price | AVERAGE | Selected price will be the aritmetic average of available prices. | :test_tube: |
|
|
||||||
|
|
||||||
|
|
||||||
## 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 | :test_tube: |
|
|
||||||
| MaxAllowedGuestNumber | int | Max guest number that can be booked. | See [details](#var-maxallowedguestnumber) | 20 | :test_tube: |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Var `MaxAllowedNightAmount`
|
### **Price Management**
|
||||||
Max number of nights that can be booked using the app. For longer stays the company should be contacted.
|
Configure how backend services calculates prices for bookings.
|
||||||
|
|
||||||
**Options:**
|
- SectionName -> price_mng
|
||||||
| Option | Value | Usage | Status |
|
- Availability -> internal
|
||||||
| :------------ | :---- | :---------------------------------------------------------- | :---------: |
|
- Nº variables -> X
|
||||||
| Disabled | 0 | Disable this funcionality. No limit on booking will be set. | :test_tube: |
|
|
||||||
| Custom Amount | - | Set the max number of nights allowed to book. | :test_tube: |
|
**Variable list:**
|
||||||
|
|
||||||
|
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. | :test_tube: |
|
|
||||||
| Custom Amount | - | Set the max guest number allowed to book. | :test_tube: |
|
|
||||||
-317
@@ -1,317 +0,0 @@
|
|||||||
<!-- omit in toc -->
|
|
||||||
# SomniaRooms - How to deploy.
|
|
||||||
|
|
||||||
- [Environment Vars](#environment-vars)
|
|
||||||
- [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.
|
|
||||||
|
|
||||||
Production images for Docker Containers are available from Gitea Registry.
|
|
||||||
|
|
||||||
Documentation on how to deploy them as containers can be found at [Readme](../README.md).
|
|
||||||
|
|
||||||
## Environment Vars
|
|
||||||
|
|
||||||
Hare is a list of some "important" or useful environment vars to consider when deploying SomniaRooms
|
|
||||||
|
|
||||||
| Var | Required | Default Value | Description |
|
|
||||||
| :------------------------: | :----------------: | :--------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `SERVER_NAME` | :heavy_check_mark: | `-` | Your server hostname to be used by Caddy. Should include "http://". |
|
|
||||||
| `DATABASE_URL` | :heavy_check_mark: | `-` | Connection string for database. See: https://symfony.com/doc/current/doctrine.html#configuring-the-database |
|
|
||||||
| `APP_SECRET` | :heavy_check_mark: | `ChangeMySecret` | Symfony App secret. You really should change this. . You can use `openssl rand -base64 36` to generate random strings |
|
|
||||||
| `TRUSTED_PROXIES` | :heavy_check_mark: | `-` | Allowed proxies address. The proxy IP should be used. Must be set when used behind some proxy/reverse-proxy: traefik (see example below), nginx, .... |
|
|
||||||
| `TRUSTED_HOSTS` | :heavy_check_mark: | `-` | ¿? You may have to set this to your `SERVER_NAME`, but I don't remember how it worked. |
|
|
||||||
| `APP_ENV` | :x: | `dev` | Set current app environment. Set to `prod` from production use. |
|
|
||||||
| `FRANKENPHP_CONFIG` | :x: | `-` | Add extra config for FrankenPHP. See: https://frankenphp.dev/docs/config/ |
|
|
||||||
| `FRANKENPHP_WORKER_CONFIG` | :x: | `-` | Add extra config for FrankenPHP Workers. See: https://frankenphp.dev/docs/config/ |
|
|
||||||
| `FRANKENPHP_USEWORKER` | :x: | `-` | Enable/disable FrankenPHP Workers. Valid values: `none` (disabled) /`worker` (enabled) |
|
|
||||||
| `MERCURE_...` | :x: | `-` | Just copy default values from `compose.yaml`. SomniaRooms don't use Mercure right now and may get removed. |
|
|
||||||
| `SOMNIA_LOGSDIR` | :x: | `/somnia_logs` | Path for App logs. It's recomended to save them on a volume or host-path (even in production?) just in case, as not all errors may be print to the console. |
|
|
||||||
|
|
||||||
|
|
||||||
For running SomniaRooms behind a proxy, see: https://symfony.com/doc/current/deployment/proxies.html
|
|
||||||
|
|
||||||
|
|
||||||
## Docker compose examples.
|
|
||||||
|
|
||||||
Here are some deploy examples using docker compose:
|
|
||||||
|
|
||||||
SomniaRooms back-end and front-end can run on the same machine, but some config is needed to work.
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> **NOTE (31/05/2025)**: Some examples may be out-of-date. Use only as reference! Check ["dev" `compose.yaml`](../compose.yaml) and [Readme](../README.md) and apply relevant diffs.
|
|
||||||
|
|
||||||
### Deploy back-end and front-end on different servers (different ip address).
|
|
||||||
If your servers have different IP addresses everything is a little bit easier. You can set-up subdomains for each one.
|
|
||||||
|
|
||||||
Use the following docker compose files to deploy back-end and front-end on different servers.
|
|
||||||
|
|
||||||
Back-end:
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
somniarooms-back:
|
|
||||||
image: gitea.uberelectronnetwork.cc/somnia/somniarooms:devbuild
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
SERVER_NAME: https://api.somnia.dev # <-- Your hostname here!
|
|
||||||
DATABASE_URL: postgresql://somnia:ChangeMe!@database/somniarooms?serverVersion=16&charset=utf8
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
|
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
|
|
||||||
APP_SECRET: ChangeMySecret
|
|
||||||
FRANKENPHP_CONFIG: "" # <-- Dont touch this line unless you know what are doing.
|
|
||||||
volumes:
|
|
||||||
- caddy_data:/data
|
|
||||||
- caddy_config:/config
|
|
||||||
ports:
|
|
||||||
# HTTP
|
|
||||||
- target: 80
|
|
||||||
published: 80
|
|
||||||
protocol: tcp
|
|
||||||
# HTTPS
|
|
||||||
- target: 443
|
|
||||||
published: 443
|
|
||||||
protocol: tcp
|
|
||||||
# HTTP/3
|
|
||||||
- target: 443
|
|
||||||
published: 443
|
|
||||||
protocol: udp
|
|
||||||
depends_on:
|
|
||||||
- database
|
|
||||||
|
|
||||||
database:
|
|
||||||
image: postgres:16-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: somniarooms
|
|
||||||
# You should definitely change the password in production
|
|
||||||
POSTGRES_PASSWORD: ChangeMe!
|
|
||||||
POSTGRES_USER: somnia
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "pg_isready"]
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
start_period: 60s
|
|
||||||
volumes:
|
|
||||||
- database_data:/var/lib/postgresql/data:rw
|
|
||||||
# You may use a bind-mounted host directory instead, so that it is harder to acciden> # - ./docker/db/data:/var/lib/postgresql/data:rw
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
caddy_data:
|
|
||||||
caddy_config:
|
|
||||||
database_data:
|
|
||||||
```
|
|
||||||
|
|
||||||
Config details
|
|
||||||
- SERVER_NAME -> if set to https a SSL cert will be obtained. If you set http, SSL will be disabled. (Read more about this [here](https://caddyserver.com/docs/automatic-https))
|
|
||||||
|
|
||||||
|
|
||||||
Front-end:
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
somniarooms-front:
|
|
||||||
image: gitea.uberelectronnetwork.cc/somnia/somniaroomsapp:devbuild
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
# You should set your public IP/hostname to back-end.
|
|
||||||
SOMNIAROOMS_BACKEND_HOST: https://api.somnia.dev # <-- Your back-end hostname here!
|
|
||||||
SOMNIAROOMS_BACKEND_PORT: 443 # <-- Your back-end port here! (443 if using https)
|
|
||||||
ports:
|
|
||||||
# HTTP
|
|
||||||
- target: 80
|
|
||||||
published: 80
|
|
||||||
# HTTPS
|
|
||||||
- target: 443
|
|
||||||
published: 443
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Deploy on the same server.
|
|
||||||
Right now, it's quite difficult to set back-end app on a port different from 80/443. Back-end uses Caddy as webserver and automatically try to get SSL certs from let's encrypt. If back-end port is changed, back-end server address should be http to avoid failed TLS challenges.
|
|
||||||
|
|
||||||
My go-to option will be using some reverse-proxy to redirect requests to back or front by domain name.
|
|
||||||
|
|
||||||
This is an example of deployment using Traefik.
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
reverse-proxy:
|
|
||||||
# The official v3 Traefik docker image
|
|
||||||
image: traefik:v3.0
|
|
||||||
restart: unless-stopped
|
|
||||||
# Enables the web UI and tells Traefik to listen to docker
|
|
||||||
command:
|
|
||||||
- --api.insecure=true
|
|
||||||
- --providers.docker
|
|
||||||
- --entrypoints.web.address=:80
|
|
||||||
- --entrypoints.websecure.address=:443
|
|
||||||
- --entrypoints.web.http.redirections.entryPoint.to=websecure
|
|
||||||
- --entrypoints.web.http.redirections.entryPoint.scheme=https
|
|
||||||
- --certificatesresolvers.lets-encrypt.acme.tlschallenge=true
|
|
||||||
- --certificatesresolvers.lets-encrypt.acme.email=your_email # <-- Your email here!
|
|
||||||
- --certificatesresolvers.lets-encrypt.acme.storage=/letsencrypt/acme.json
|
|
||||||
ports:
|
|
||||||
# The HTTP port
|
|
||||||
- "80:80"
|
|
||||||
# The HTTPS port
|
|
||||||
- "443:443"
|
|
||||||
# The Web UI (enabled by --api.insecure=true)
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
# So that Traefik can listen to the Docker events
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
# acme.json should be created on host instance
|
|
||||||
- .certs/:/letsencrypt/
|
|
||||||
|
|
||||||
somniarooms-back:
|
|
||||||
image: gitea.uberelectronnetwork.cc/somnia/somniarooms:devbuild
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
SERVER_NAME: https://api.somnia.dev # <-- Your hostname here!
|
|
||||||
DATABASE_URL: postgresql://somnia:ChangeMe!@database/somniarooms?serverVersion=16&charset=utf8
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
|
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
|
|
||||||
APP_SECRET: ChangeMySecret
|
|
||||||
FRANKENPHP_CONFIG: "" # <-- Dont touch this line unless you know what are doing.
|
|
||||||
TRUSTED_HOSTS: api.somnia.dev
|
|
||||||
TRUSTED_PROXIES: reverse-proxy
|
|
||||||
volumes:
|
|
||||||
- caddy_data:/data
|
|
||||||
- caddy_config:/config
|
|
||||||
depends_on:
|
|
||||||
- database
|
|
||||||
labels:
|
|
||||||
# HTTPS YOUR APP
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.somniarooms-back.rule=Host(`api.somnia.dev`)" # <-- Your back-end hostname here!
|
|
||||||
- "traefik.http.routers.somniarooms-back.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.somniarooms-back.tls=true"
|
|
||||||
- "traefik.http.routers.somniarooms-back.tls.certresolver=lets-encrypt"
|
|
||||||
- "traefik.http.services.somniarooms-back.loadbalancer.server.port=80"
|
|
||||||
|
|
||||||
somniarooms-front:
|
|
||||||
image: gitea.uberelectronnetwork.cc/somnia/somniaroomsapp:devbuild
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
# You should set your public IP/hostname to back-end.
|
|
||||||
SOMNIAROOMS_BACKEND_HOST: https://api.somnia.dev # <-- Your back-end hostname here!
|
|
||||||
SOMNIAROOMS_BACKEND_PORT: 443 # <-- Your back-end port here! (443 if using https)
|
|
||||||
depends_on:
|
|
||||||
- somniarooms-back
|
|
||||||
labels:
|
|
||||||
- "traefik.http.routers.somniarooms-front.rule=Host(`app.somnia.dev`)" # <-- Your front-end hostname here!
|
|
||||||
- "traefik.http.routers.somniarooms-front.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.somniarooms-front.tls=true"
|
|
||||||
- "traefik.http.routers.somniarooms-front.tls.certresolver=lets-encrypt"
|
|
||||||
- "traefik.http.services.somniarooms-front.loadbalancer.server.port=80"
|
|
||||||
|
|
||||||
database:
|
|
||||||
image: postgres:16-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: somniarooms
|
|
||||||
# You should definitely change the password in production
|
|
||||||
POSTGRES_PASSWORD: ChangeMe!
|
|
||||||
POSTGRES_USER: somnia
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "pg_isready"]
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
start_period: 60s
|
|
||||||
volumes:
|
|
||||||
- database_data:/var/lib/postgresql/data:rw
|
|
||||||
# You may use a bind-mounted host directory instead, so that it is harder to acciden> # - ./docker/db/data:/var/lib/postgresql/data:rw
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
caddy_data:
|
|
||||||
caddy_config:
|
|
||||||
database_data:
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
If you dont care about SSL, you can set it up using HTTP only.
|
|
||||||
You may need to use different ports for back-end and front-end.
|
|
||||||
|
|
||||||
This setup uses the following config:
|
|
||||||
- back-end -> port 8001.
|
|
||||||
- front-end -> port 80.
|
|
||||||
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
somniarooms-back:
|
|
||||||
image: gitea.uberelectronnetwork.cc/somnia/somniarooms:devbuild
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
# Server name should be "HTTP" to disable auto certs.
|
|
||||||
# Set listening port as well if different from 80/443.
|
|
||||||
# If a server name is set, Caddy will only work for that name. You can set all you need comma separated.
|
|
||||||
# You can "wildcard" as well if that's your thing...
|
|
||||||
# SERVER_NAME: :8001
|
|
||||||
SERVER_NAME: http://api.somnia.dev:8001, http://IP:8001
|
|
||||||
DATABASE_URL: postgresql://somnia:ChangeMe!@database/somniarooms?serverVersion=16&charset=utf8
|
|
||||||
MERCURE_PUBLISHER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
|
|
||||||
MERCURE_SUBSCRIBER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
|
|
||||||
FRANKENPHP_CONFIG: ""
|
|
||||||
APP_SECRET: ChangeMySecret
|
|
||||||
TRUSTED_HOSTS: api.somnia.dev
|
|
||||||
volumes:
|
|
||||||
- caddy_data:/data
|
|
||||||
- caddy_config:/config
|
|
||||||
ports:
|
|
||||||
# HTTP
|
|
||||||
# Target port must be the same as SERVER_NAME. Caddy will use SERVER_NAME port instead of 80/443 if set.
|
|
||||||
- target: 8001
|
|
||||||
published: 8001
|
|
||||||
depends_on:
|
|
||||||
- database
|
|
||||||
|
|
||||||
somniarooms-front:
|
|
||||||
image: gitea.uberelectronnetwork.cc/somnia/somniaroomsapp:devbuild
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
# You should set your public IP/hostname to back-end.
|
|
||||||
# You may have problems with CORS... Make sure back and front have same domain or allow requests for those domains.
|
|
||||||
SOMNIAROOMS_BACKEND_HOST: http://api.somnia.dev
|
|
||||||
SOMNIAROOMS_BACKEND_PORT: 8001 # <-- Set back-end published port.
|
|
||||||
ports:
|
|
||||||
# HTTP
|
|
||||||
- target: 80
|
|
||||||
published: 80
|
|
||||||
# HTTPS.
|
|
||||||
# Uncomment if HTTPS is available. You should configure certs by yourself here.
|
|
||||||
#- target: 443
|
|
||||||
# published: 443
|
|
||||||
depends_on:
|
|
||||||
- somniarooms-back
|
|
||||||
|
|
||||||
database:
|
|
||||||
image: postgres:16-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: somniarooms
|
|
||||||
# You should definitely change the password in production. Remember to change DATABASE_URL as well.
|
|
||||||
POSTGRES_PASSWORD: ChangeMe!
|
|
||||||
POSTGRES_USER: somnia
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "pg_isready"]
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
start_period: 60s
|
|
||||||
volumes:
|
|
||||||
- database_data:/var/lib/postgresql/data:rw
|
|
||||||
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
|
|
||||||
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
caddy_data:
|
|
||||||
caddy_config:
|
|
||||||
database_data:
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## What's next?
|
|
||||||
|
|
||||||
Remember you can run your docker compose files using:
|
|
||||||
```
|
|
||||||
docker compose up -d --wait
|
|
||||||
```
|
|
||||||
+14
-32
@@ -1,44 +1,37 @@
|
|||||||
# Snipets "none"/"worker" can be used to enable-disable frankenphp workers.
|
|
||||||
(none) {
|
|
||||||
|
|
||||||
}
|
|
||||||
(worker) {
|
|
||||||
worker {
|
|
||||||
file ./public/index.php
|
|
||||||
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
|
|
||||||
{$FRANKENPHP_WORKER_CONFIG}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
skip_install_trust
|
|
||||||
|
|
||||||
{$CADDY_GLOBAL_OPTIONS}
|
{$CADDY_GLOBAL_OPTIONS}
|
||||||
|
|
||||||
frankenphp {
|
frankenphp {
|
||||||
{$FRANKENPHP_CONFIG}
|
{$FRANKENPHP_CONFIG}
|
||||||
|
|
||||||
import {$FRANKENPHP_USEWORKER:worker}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm
|
||||||
|
order mercure after encode
|
||||||
|
order vulcain after reverse_proxy
|
||||||
|
order php_server before file_server
|
||||||
}
|
}
|
||||||
|
|
||||||
{$CADDY_EXTRA_CONFIG}
|
{$CADDY_EXTRA_CONFIG}
|
||||||
|
|
||||||
{$SERVER_NAME:localhost} {
|
{$SERVER_NAME:localhost} {
|
||||||
log {
|
log {
|
||||||
{$CADDY_SERVER_LOG_OPTIONS}
|
|
||||||
# Redact the authorization query parameter that can be set by Mercure
|
# Redact the authorization query parameter that can be set by Mercure
|
||||||
format filter {
|
format filter {
|
||||||
request>uri query {
|
wrap console
|
||||||
replace authorization REDACTED
|
fields {
|
||||||
|
uri query {
|
||||||
|
replace authorization REDACTED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
root /app/public
|
root * /app/public
|
||||||
encode zstd br gzip
|
encode zstd br gzip
|
||||||
|
|
||||||
mercure {
|
mercure {
|
||||||
|
# Transport to use (default to Bolt)
|
||||||
|
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
|
||||||
# Publisher JWT key
|
# Publisher JWT key
|
||||||
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
|
publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
|
||||||
# Subscriber JWT key
|
# Subscriber JWT key
|
||||||
@@ -58,16 +51,5 @@
|
|||||||
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
|
# Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics
|
||||||
header ?Permissions-Policy "browsing-topics=()"
|
header ?Permissions-Policy "browsing-topics=()"
|
||||||
|
|
||||||
@phpRoute {
|
php_server
|
||||||
not path /.well-known/mercure*
|
|
||||||
not file {path}
|
|
||||||
}
|
|
||||||
rewrite @phpRoute index.php
|
|
||||||
|
|
||||||
@frontController path index.php
|
|
||||||
php @frontController
|
|
||||||
|
|
||||||
file_server {
|
|
||||||
hide *.php
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
; https://symfony.com/doc/current/performance.html#use-the-opcache-class-preloading
|
|
||||||
opcache.preload_user = root
|
|
||||||
opcache.preload = /app/config/preload.php
|
|
||||||
; https://symfony.com/doc/current/performance.html#don-t-check-php-files-timestamps
|
|
||||||
opcache.validate_timestamps = 0
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
opcache.preload_user = root
|
||||||
|
opcache.preload = /app/config/preload.php
|
||||||
@@ -17,7 +17,7 @@ if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
|||||||
composer config --json extra.symfony.docker 'true'
|
composer config --json extra.symfony.docker 'true'
|
||||||
|
|
||||||
if grep -q ^DATABASE_URL= .env; then
|
if grep -q ^DATABASE_URL= .env; then
|
||||||
echo 'To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build --wait'
|
echo "To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build -d --wait"
|
||||||
sleep infinity
|
sleep infinity
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -26,12 +26,8 @@ if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
|||||||
composer install --prefer-dist --no-progress --no-interaction
|
composer install --prefer-dist --no-progress --no-interaction
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Display information about the current project
|
|
||||||
# Or about an error in project initialization
|
|
||||||
php bin/console -V
|
|
||||||
|
|
||||||
if grep -q ^DATABASE_URL= .env; then
|
if grep -q ^DATABASE_URL= .env; then
|
||||||
echo 'Waiting for database to be ready...'
|
echo "Waiting for database to be ready..."
|
||||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
||||||
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
|
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
|
||||||
if [ $? -eq 255 ]; then
|
if [ $? -eq 255 ]; then
|
||||||
@@ -45,22 +41,20 @@ if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
|
if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
|
||||||
echo 'The database is not up or not reachable:'
|
echo "The database is not up or not reachable:"
|
||||||
echo "$DATABASE_ERROR"
|
echo "$DATABASE_ERROR"
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echo 'The database is now ready and reachable'
|
echo "The database is now ready and reachable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then
|
if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then
|
||||||
php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing
|
php bin/console doctrine:migrations:migrate --no-interaction
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
||||||
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var
|
||||||
|
|
||||||
echo 'PHP app ready!'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec docker-php-entrypoint "$@"
|
exec docker-php-entrypoint "$@"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
worker {
|
||||||
|
file ./public/index.php
|
||||||
|
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the importmap for this application.
|
|
||||||
*
|
|
||||||
* - "path" is a path inside the asset mapper system. Use the
|
|
||||||
* "debug:asset-map" command to see the full list of paths.
|
|
||||||
*
|
|
||||||
* - "entrypoint" (JavaScript only) set to true for any module that will
|
|
||||||
* be used as an "entrypoint" (and passed to the importmap() Twig function).
|
|
||||||
*
|
|
||||||
* The "importmap:require" command can be used to add new entries to this file.
|
|
||||||
*/
|
|
||||||
return [
|
|
||||||
'app' => [
|
|
||||||
'path' => './assets/app.js',
|
|
||||||
'entrypoint' => true,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240604185317 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP SEQUENCE config_variable_id_seq CASCADE');
|
|
||||||
$this->addSql('DROP INDEX uniq_718569538a90aba9');
|
|
||||||
$this->addSql('ALTER TABLE config_variable DROP CONSTRAINT config_variable_pkey');
|
|
||||||
$this->addSql('ALTER TABLE config_variable DROP id');
|
|
||||||
$this->addSql('ALTER TABLE config_variable ALTER section SET NOT NULL');
|
|
||||||
$this->addSql('ALTER TABLE config_variable ALTER section TYPE VARCHAR(255)');
|
|
||||||
$this->addSql('ALTER TABLE config_variable ADD PRIMARY KEY (key)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE SCHEMA public');
|
|
||||||
$this->addSql('CREATE SEQUENCE config_variable_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
|
||||||
$this->addSql('DROP INDEX config_variable_pkey');
|
|
||||||
$this->addSql('ALTER TABLE config_variable ADD id INT NOT NULL');
|
|
||||||
$this->addSql('ALTER TABLE config_variable ALTER section DROP NOT NULL');
|
|
||||||
$this->addSql('ALTER TABLE config_variable ALTER section TYPE VARCHAR(64)');
|
|
||||||
$this->addSql('CREATE UNIQUE INDEX uniq_718569538a90aba9 ON config_variable (key)');
|
|
||||||
$this->addSql('ALTER TABLE config_variable ADD PRIMARY KEY (id)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240909114239 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP SEQUENCE room_category_details_id_seq CASCADE');
|
|
||||||
$this->addSql('ALTER TABLE room_category_details DROP CONSTRAINT fk_3735fabb67333dd');
|
|
||||||
$this->addSql('DROP TABLE room_category_details');
|
|
||||||
$this->addSql('ALTER TABLE room_category_photo ADD photo_order INT DEFAULT NULL');
|
|
||||||
$this->addSql('ALTER TABLE room_category_photo DROP kind');
|
|
||||||
$this->addSql('ALTER TABLE room_fare DROP CONSTRAINT FK_35C9B213A7233EF2');
|
|
||||||
$this->addSql('ALTER TABLE room_fare ADD CONSTRAINT FK_35C9B213A7233EF2 FOREIGN KEY (fare_table_id) REFERENCES fare_table (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE SCHEMA public');
|
|
||||||
$this->addSql('CREATE SEQUENCE room_category_details_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
|
||||||
$this->addSql('CREATE TABLE room_category_details (id INT NOT NULL, room_category_id INT NOT NULL, details_section VARCHAR(255) NOT NULL, detail_value VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('CREATE INDEX idx_3735fabb67333dd ON room_category_details (room_category_id)');
|
|
||||||
$this->addSql('ALTER TABLE room_category_details ADD CONSTRAINT fk_3735fabb67333dd FOREIGN KEY (room_category_id) REFERENCES room_category (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
|
||||||
$this->addSql('ALTER TABLE room_fare DROP CONSTRAINT fk_35c9b213a7233ef2');
|
|
||||||
$this->addSql('ALTER TABLE room_fare ADD CONSTRAINT fk_35c9b213a7233ef2 FOREIGN KEY (fare_table_id) REFERENCES fare_table (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
|
||||||
$this->addSql('ALTER TABLE room_category_photo ADD kind VARCHAR(30) NOT NULL');
|
|
||||||
$this->addSql('ALTER TABLE room_category_photo DROP photo_order');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240909131104 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE SEQUENCE bed_type_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
|
||||||
$this->addSql('CREATE TABLE bed_type (id INT NOT NULL, name VARCHAR(255) DEFAULT NULL, icon_url VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('CREATE TABLE room_category_bed_type (room_category_id INT NOT NULL, bed_type_id INT NOT NULL, amount INT NOT NULL, PRIMARY KEY(room_category_id, bed_type_id))');
|
|
||||||
$this->addSql('CREATE INDEX IDX_FB45954567333DD ON room_category_bed_type (room_category_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_FB4595458158330E ON room_category_bed_type (bed_type_id)');
|
|
||||||
$this->addSql('ALTER TABLE room_category_bed_type ADD CONSTRAINT FK_FB45954567333DD FOREIGN KEY (room_category_id) REFERENCES room_category (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
|
||||||
$this->addSql('ALTER TABLE room_category_bed_type ADD CONSTRAINT FK_FB4595458158330E FOREIGN KEY (bed_type_id) REFERENCES bed_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE SCHEMA public');
|
|
||||||
$this->addSql('DROP SEQUENCE bed_type_id_seq CASCADE');
|
|
||||||
$this->addSql('ALTER TABLE room_category_bed_type DROP CONSTRAINT FK_FB45954567333DD');
|
|
||||||
$this->addSql('ALTER TABLE room_category_bed_type DROP CONSTRAINT FK_FB4595458158330E');
|
|
||||||
$this->addSql('DROP TABLE bed_type');
|
|
||||||
$this->addSql('DROP TABLE room_category_bed_type');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240910160534 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE room_category DROP bed_type');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE SCHEMA public');
|
|
||||||
$this->addSql('ALTER TABLE room_category ADD bed_type VARCHAR(64) DEFAULT NULL');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240916181831 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE room_category ADD order_home_page SMALLINT DEFAULT NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE SCHEMA public');
|
|
||||||
$this->addSql('ALTER TABLE room_category DROP order_home_page');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
* Migrate Postgres SEQUENCE to IDENTITY after updating to DBAL 4.
|
|
||||||
*/
|
|
||||||
final class Version20250605182840 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return 'Migrate Postgres SEQUENCE to IDENTITY after updating to DBAL 4.';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE bed_type ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE bed_type ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking_customer ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking_customer ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking_room ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking_room ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE fare_table ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE fare_table ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_category ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_category ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_category_photo ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_category_photo ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_fare ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_fare ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE "user" ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE "user" ALTER id ADD GENERATED BY DEFAULT AS IDENTITY
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_fare ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_fare ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_category ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_category ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking_room ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking_room ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking_customer ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking_customer ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE bed_type ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE bed_type ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE fare_table ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE fare_table ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE "user" ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE "user" ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_category_photo ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_category_photo ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking ALTER id TYPE INT
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE booking ALTER id DROP IDENTITY
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20251017201308 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE TABLE room_price (date VARCHAR(10) NOT NULL, guest_number SMALLINT NOT NULL, price INT NOT NULL, room_category_id INT NOT NULL, PRIMARY KEY(room_category_id, date, guest_number))
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE INDEX IDX_8074837267333DD ON room_price (room_category_id)
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_price ADD CONSTRAINT FK_8074837267333DD FOREIGN KEY (room_category_id) REFERENCES room_category (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE room_price DROP CONSTRAINT FK_8074837267333DD
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
DROP TABLE room_price
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
images/
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "Double Bed",
|
|
||||||
"iconUrl": "lucide:bed-double"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Single Bed",
|
|
||||||
"iconUrl": "lucide:bed-single"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"amount": 1,
|
|
||||||
"roomCategory": 0,
|
|
||||||
"bedType": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"amount": 1,
|
|
||||||
"roomCategory": 2,
|
|
||||||
"bedType": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App;
|
|
||||||
|
|
||||||
use App\DTO\ApiErrorMessage;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
||||||
|
|
||||||
class ApiErrorException extends HttpException
|
|
||||||
{
|
|
||||||
|
|
||||||
private ApiErrorMessage $apiErrorMsg;
|
|
||||||
|
|
||||||
public function __construct(ApiErrorMessage $apiErrorMsg, $detailsData = null, ?\Exception $previous = null, array $headers = array(), $code = 0)
|
|
||||||
{
|
|
||||||
$this->apiErrorMsg = $apiErrorMsg;
|
|
||||||
|
|
||||||
if (isset($detailsData)) {
|
|
||||||
$this->apiErrorMsg->setDetails($detailsData);
|
|
||||||
}
|
|
||||||
|
|
||||||
$statusCode = $apiErrorMsg->getStatusCode();
|
|
||||||
$message = $apiErrorMsg->getTitle();
|
|
||||||
|
|
||||||
parent::__construct($statusCode, $message, $previous, $headers, $code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getApiErrorMsg(): ApiErrorMessage
|
|
||||||
{
|
|
||||||
return $this->apiErrorMsg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<?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 PUBLIC_SECTION_LIST = [
|
|
||||||
ConfigVariableSectionEnum::Company->value,
|
|
||||||
ConfigVariableSectionEnum::Frontend->value,
|
|
||||||
ConfigVariableSectionEnum::API_CONFIG->value,
|
|
||||||
];
|
|
||||||
|
|
||||||
public const CONFIG_COMPANY_NAME = ["CompanyName" => [ConfigVariableSectionEnum::Company, "SomniaRooms"]];
|
|
||||||
public const CONFIG_COMPANY_WEBSITE = ["CompanyWebsite" => [ConfigVariableSectionEnum::Company, "somnia.dev"]];
|
|
||||||
public const CONFIG_COMPANY_CONTACT_FORMURL = ["CompanyContactFormUrl" => [ConfigVariableSectionEnum::Company, "somnia.dev"]];
|
|
||||||
public const CONFIG_COMPANY_LOGO = ["CompanyLogo" => [ConfigVariableSectionEnum::Company, ""]];
|
|
||||||
public const CONFIG_COMPANY_LOGOSIZE = ["CompanyLogoSize" => [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 = ["NightCountForContactDialog" => [ConfigVariableSectionEnum::Frontend, "10"]];
|
|
||||||
public const CONFIG_FRONTEND_GUESTSFORCONTACT = ["GuestNumberForContactDialog" => [ConfigVariableSectionEnum::Frontend, "6"]];
|
|
||||||
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_CONTACT_FORMURL,
|
|
||||||
ConfigVariableConsts::CONFIG_COMPANY_LOGO,
|
|
||||||
ConfigVariableConsts::CONFIG_COMPANY_LOGOSIZE,
|
|
||||||
ConfigVariableConsts::CONFIG_COMPANY_ADDRESS,
|
|
||||||
ConfigVariableConsts::CONFIG_COMPANY_EMAIL,
|
|
||||||
ConfigVariableConsts::CONFIG_COMPANY_PHONE,
|
|
||||||
ConfigVariableConsts::CONFIG_FRONTEND_NIGHTSFORCONTACT,
|
|
||||||
ConfigVariableConsts::CONFIG_FRONTEND_GUESTSFORCONTACT,
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function IsSectionValuePublicForApi(string $sectionValue)
|
|
||||||
{
|
|
||||||
return in_array($sectionValue, ConfigVariableConsts::PUBLIC_SECTION_LIST);
|
|
||||||
}
|
|
||||||
public static function IsSectionPublicForApi(ConfigVariableSectionEnum $section)
|
|
||||||
{
|
|
||||||
return ConfigVariableConsts::IsSectionValuePublicForApi($section->value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Consts;
|
|
||||||
|
|
||||||
class Consts
|
|
||||||
{
|
|
||||||
public const FORMAT_DATE = 'Y-m-d';
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@ class BookingCrudController extends AbstractCrudController
|
|||||||
public function configureFields(string $pageName): iterable
|
public function configureFields(string $pageName): iterable
|
||||||
{
|
{
|
||||||
$fieldRefNumber = TextField::new ('refNumber');
|
$fieldRefNumber = TextField::new ('refNumber');
|
||||||
$fieldBookingTime = DateTimeField::new('bookingTime');
|
$fieldBookingTime = DateTimeField::new ('BookingTime');
|
||||||
if ($pageName == Crud::PAGE_EDIT) {
|
if ($pageName == Crud::PAGE_EDIT) {
|
||||||
$fieldRefNumber->setDisabled(true);
|
$fieldRefNumber->setDisabled(true);
|
||||||
$fieldBookingTime->setDisabled(true);
|
$fieldBookingTime->setDisabled(true);
|
||||||
@@ -33,8 +33,8 @@ class BookingCrudController extends AbstractCrudController
|
|||||||
yield $fieldRefNumber;
|
yield $fieldRefNumber;
|
||||||
yield $fieldBookingTime;
|
yield $fieldBookingTime;
|
||||||
|
|
||||||
yield DateField::new('arrivalDate');
|
yield DateField::new ('ArrivalDate');
|
||||||
yield DateField::new('departureDate');
|
yield DateField::new ('DepartureDate');
|
||||||
|
|
||||||
yield AssociationField::new ('bookingCustomer')->renderAsEmbeddedForm(BookingCustomerCrudController::class);
|
yield AssociationField::new ('bookingCustomer')->renderAsEmbeddedForm(BookingCustomerCrudController::class);
|
||||||
yield CollectionField::new ('bookingRooms')->useEntryCrudForm();
|
yield CollectionField::new ('bookingRooms')->useEntryCrudForm();
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ class BookingCustomerCrudController extends AbstractCrudController
|
|||||||
|
|
||||||
public function configureFields(string $pageName): iterable
|
public function configureFields(string $pageName): iterable
|
||||||
{
|
{
|
||||||
yield TextField::new('name');
|
yield TextField::new('Name');
|
||||||
yield TextField::new('surname');
|
yield TextField::new('Surname');
|
||||||
yield TextField::new('email');
|
yield TextField::new('Email');
|
||||||
yield TextField::new('phoneNumber');
|
yield TextField::new('PhoneNumber');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ class BookingRoomCrudController extends AbstractCrudController
|
|||||||
|
|
||||||
public function configureFields(string $pageName): iterable
|
public function configureFields(string $pageName): iterable
|
||||||
{
|
{
|
||||||
yield AssociationField::new('roomCategory')->setCrudController(RoomCategoryCrudController::class);
|
yield AssociationField::new ('RoomCategory')->setCrudController(RoomCategoryCrudController::class);
|
||||||
yield NumberField::new('guestNumber');
|
yield NumberField::new ('GuestNumber');
|
||||||
yield MoneyField::new('roomPrice')->setCurrency("EUR");
|
yield MoneyField::new ('RoomPrice')->setCurrency("EUR");
|
||||||
yield NumberField::new('amount');
|
yield NumberField::new ('Amount');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
namespace App\Controller\Admin;
|
namespace App\Controller\Admin;
|
||||||
|
|
||||||
use App\Entity\ConfigVariable;
|
use App\Entity\ConfigVariable;
|
||||||
use App\Enum\ConfigVariableSectionEnum;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
|
|
||||||
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\TextField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
||||||
|
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
|
||||||
|
|
||||||
class ConfigVariableCrudController extends AbstractCrudController
|
class ConfigVariableCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
@@ -17,19 +14,10 @@ class ConfigVariableCrudController extends AbstractCrudController
|
|||||||
return ConfigVariable::class;
|
return ConfigVariable::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureCrud(Crud $crud): 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(ConfigVariableSectionEnum::cases());
|
yield TextField::new('section');
|
||||||
yield TextField::new('key');
|
yield TextField::new('key');
|
||||||
yield TextField::new('value');
|
yield TextField::new('value');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ class FareTableCrudController extends AbstractCrudController
|
|||||||
|
|
||||||
yield TextField::new('comment')->setLabel('Name');
|
yield TextField::new('comment')->setLabel('Name');
|
||||||
|
|
||||||
yield AssociationField::new('roomCategory')
|
yield AssociationField::new('roomCategory');
|
||||||
->setCrudController(RoomCategoryCrudController::class);
|
|
||||||
|
|
||||||
yield DateField::new('startDate');
|
yield DateField::new('startDate');
|
||||||
yield DateField::new('endDate');
|
yield DateField::new('endDate');
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller\Admin\Field;
|
|
||||||
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
|
||||||
|
|
||||||
final class IconField implements FieldInterface
|
|
||||||
{
|
|
||||||
use FieldTrait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TranslatableInterface|string|false|null $label
|
|
||||||
*/
|
|
||||||
public static function new(string $propertyName, $label = null): self
|
|
||||||
{
|
|
||||||
return (new self())
|
|
||||||
->setProperty($propertyName)
|
|
||||||
->setLabel($label)
|
|
||||||
|
|
||||||
// this template is used in 'index' and 'detail' pages
|
|
||||||
->setTemplatePath('admin/field/icon.html.twig')
|
|
||||||
|
|
||||||
// this is used in 'edit' and 'new' pages to edit the field contents
|
|
||||||
// you can use your own form types too
|
|
||||||
->setFormType(TextType::class)
|
|
||||||
->addCssClass('field-icon')
|
|
||||||
|
|
||||||
// these methods allow to define the web assets loaded when the
|
|
||||||
// field is displayed in any CRUD page (index/detail/edit/new)
|
|
||||||
//->addCssFiles('admin/field/field-icon.css')
|
|
||||||
//->addJsFiles('admin/field/field-icon.js')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller\Admin;
|
|
||||||
|
|
||||||
use App\Service\AdminUtils;
|
|
||||||
use App\Service\FileUploaderService;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\Asset\PathPackage;
|
|
||||||
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|
||||||
use Symfony\Component\Validator\Constraints\File;
|
|
||||||
|
|
||||||
#[IsGranted("ROLE_ADMIN")]
|
|
||||||
class PhotoManagerAdminController extends AbstractController
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private FileUploaderService $fileUploader
|
|
||||||
) {}
|
|
||||||
|
|
||||||
|
|
||||||
#[AdminRoute('/photos', name: 'photos')]
|
|
||||||
public function index(): Response
|
|
||||||
{
|
|
||||||
$fileData = [];
|
|
||||||
|
|
||||||
$dir = $this->fileUploader->getTargetDirectory();
|
|
||||||
$pathPackage = new PathPackage($dir, new EmptyVersionStrategy());
|
|
||||||
|
|
||||||
foreach (glob($dir . '*.*') as $file) {
|
|
||||||
$filename = basename($file);
|
|
||||||
$fileData[] = [
|
|
||||||
"filename" => $filename,
|
|
||||||
"url" => $pathPackage->getUrl($filename),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
//return new JsonResponse(["dir" => $dir, "response" => $fileData]);
|
|
||||||
|
|
||||||
return $this->render('admin/photo_manager/index.html.twig', [
|
|
||||||
'photoList' => $fileData,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[AdminRoute('/photos/upload', name: 'photos_upload')]
|
|
||||||
public function uploadPhoto(
|
|
||||||
Request $request
|
|
||||||
): Response {
|
|
||||||
|
|
||||||
$form = $this->createFormBuilder()
|
|
||||||
->add('file', FileType::class, [
|
|
||||||
'mapped' => false,
|
|
||||||
'required' => false,
|
|
||||||
'constraints' => [
|
|
||||||
new File(
|
|
||||||
maxSize: '2048k',
|
|
||||||
maxSizeMessage: 'Selected image is too big!',
|
|
||||||
mimeTypes: [
|
|
||||||
'image/jpeg',
|
|
||||||
'image/png',
|
|
||||||
'image/svg+xml',
|
|
||||||
'image/webp',
|
|
||||||
],
|
|
||||||
mimeTypesMessage: 'Please upload a valid image!'
|
|
||||||
)
|
|
||||||
],
|
|
||||||
])
|
|
||||||
->add('submit', SubmitType::class)
|
|
||||||
->getForm();
|
|
||||||
|
|
||||||
$form->handleRequest($request);
|
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
/** @var UploadedFile $uploadedPhoto */
|
|
||||||
$uploadedPhoto = $form->get('file')->getData();
|
|
||||||
|
|
||||||
if ($uploadedPhoto) {
|
|
||||||
$filename = $this->fileUploader->upload($uploadedPhoto);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addFlash('success', 'Photo uploaded!');
|
|
||||||
return $this->redirectToRoute(AdminUtils::GetAdminRoute('photos'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('admin/adminEdit.html.twig', [
|
|
||||||
'form' => $form,
|
|
||||||
'ElementName' => "Photo",
|
|
||||||
'ElementType' => "New Photo",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[AdminRoute('/photos/remove/{filename}', name: 'photos_remove')]
|
|
||||||
public function remove(string $filename): Response
|
|
||||||
{
|
|
||||||
$filePath = $this->fileUploader->getTargetDirectory() . $filename;
|
|
||||||
$deleted = $this->fileUploader->deleteFile($filePath);
|
|
||||||
|
|
||||||
if (!$deleted) {
|
|
||||||
throw new HttpException("404", "File can't be deleted");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->addFlash('success', 'Photo deleted!');
|
|
||||||
return $this->redirectToRoute(AdminUtils::GetAdminRoute('photos'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,42 +2,49 @@
|
|||||||
|
|
||||||
namespace App\Controller\Admin;
|
namespace App\Controller\Admin;
|
||||||
|
|
||||||
|
use App\Entity\RoomAvailability;
|
||||||
use App\Entity\RoomCategory;
|
use App\Entity\RoomCategory;
|
||||||
use App\Enum\AdminErrorEnum;
|
|
||||||
use App\Repository\RoomAvailabilityRepository;
|
use App\Repository\RoomAvailabilityRepository;
|
||||||
use App\Repository\RoomCategoryRepository;
|
use App\Repository\RoomCategoryRepository;
|
||||||
use App\Service\AdminUtils;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
|
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[IsGranted("ROLE_ADMIN")]
|
#[IsGranted("ROLE_ADMIN")]
|
||||||
class RoomAvailabilityAdminController extends AbstractController
|
class RoomAvailabilityAdminController extends AbstractController
|
||||||
{
|
{
|
||||||
public const string ROUTE_INDEX = 'roomAvailability_category';
|
|
||||||
public const string ROUTE_BULKEDIT = 'roomAvailability_category_bulkupdate';
|
|
||||||
|
|
||||||
|
private $adminUrlGenerator;
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(AdminUrlGenerator $adminUrlGenerator, EntityManagerInterface $em)
|
||||||
private EntityManagerInterface $entityManager,
|
{
|
||||||
private AdminUtils $adminUtils,
|
$this->adminUrlGenerator = $adminUrlGenerator;
|
||||||
) {}
|
$this->entityManager = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/roomAvailability', name: 'admin_roomAvailability')]
|
||||||
|
public function index(RoomAvailabilityRepository $repo, RoomCategoryRepository $catRepo): Response
|
||||||
|
{
|
||||||
|
$categoryList = $catRepo->findAll();
|
||||||
|
|
||||||
|
return $this->render('admin/room_availability/index.html.twig', [
|
||||||
|
'roomCategoryList' => $categoryList,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
// List RoomAvailability items from category.
|
// List RoomAvailability items from category.
|
||||||
#[AdminRoute('/roomAvailability/{categoryId}', name: RoomAvailabilityAdminController::ROUTE_INDEX)]
|
#[Route('/admin/roomAvailability/{categoryId}', name: 'admin_roomAvailability_category')]
|
||||||
public function categoryAvailability(RoomAvailabilityRepository $repo, RoomCategoryRepository $catRepo, string $categoryId): Response
|
public function categoryAvailability(RoomAvailabilityRepository $repo, RoomCategoryRepository $catRepo, string $categoryId): Response
|
||||||
{
|
{
|
||||||
$category = $catRepo->find($categoryId);
|
$category = $catRepo->find($categoryId);
|
||||||
if ($category == null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$availabilityList = $repo->findByRoomCategory($categoryId);
|
$availabilityList = $repo->findByRoomCategory($categoryId);
|
||||||
|
|
||||||
return $this->render('admin/room_availability/category_view.html.twig', [
|
return $this->render('admin/room_availability/category_view.html.twig', [
|
||||||
@@ -47,12 +54,18 @@ class RoomAvailabilityAdminController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Edit a RoomAvailability numAvailable.
|
// Edit a RoomAvailability numAvailable.
|
||||||
#[AdminRoute('/roomAvailability/edit/{categoryId}/{day}', name: 'roomAvailability_category_update')]
|
#[Route('/admin/roomAvailability/edit/{categoryId}/{day}', name: 'admin_roomAvailability_category_update')]
|
||||||
public function updateCategoryAvailability(Request $request, RoomAvailabilityRepository $repo, string $categoryId, string $day): Response
|
public function updateCategoryAvailability(Request $request, RoomAvailabilityRepository $repo, string $categoryId, string $day): Response
|
||||||
{
|
{
|
||||||
$availabilityItem = $repo->find(["day" => $day, "roomCategory" => $categoryId]);
|
$availabilityItem = $repo->find(["day" => $day, "roomCategory" => $categoryId]);
|
||||||
|
|
||||||
if ($availabilityItem == null) {
|
if ($availabilityItem == null) {
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
//TODOME: Aquí deberíamos llevar a una página de error o algo.
|
||||||
|
|
||||||
|
$url = $this->adminUrlGenerator->setRoute('admin_roomAvailability_category', [
|
||||||
|
'categoryId' => $item->getId(),
|
||||||
|
])->generateUrl();
|
||||||
|
return $this->redirect($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = $this->createFormBuilder($availabilityItem)
|
$form = $this->createFormBuilder($availabilityItem)
|
||||||
@@ -67,11 +80,11 @@ class RoomAvailabilityAdminController extends AbstractController
|
|||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
$this->addFlash('success', 'Availability Updated!');
|
$url = $this->adminUrlGenerator->setRoute('admin_roomAvailability_category', [
|
||||||
return $this->redirectToRoute(
|
'categoryId' => $categoryId,
|
||||||
AdminUtils::GetAdminRoute(RoomAvailabilityAdminController::ROUTE_INDEX),
|
])->generateUrl();
|
||||||
['categoryId' => $categoryId,]
|
|
||||||
);
|
return $this->redirect($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('admin/room_availability/availability_day_edit.html.twig', [
|
return $this->render('admin/room_availability/availability_day_edit.html.twig', [
|
||||||
@@ -81,13 +94,9 @@ class RoomAvailabilityAdminController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bulk edit all roomAvailability of a category on period.
|
// Bulk edit all roomAvailability of a category on period.
|
||||||
#[AdminRoute('/roomAvailability/{categoryId}/bulkedit', name: RoomAvailabilityAdminController::ROUTE_BULKEDIT)]
|
#[Route('/admin/roomAvailability/{categoryId}/bulkedit', name: 'admin_roomAvailability_category_bulkupdate')]
|
||||||
public function bulkUpdateCategoryAvailability(Request $request, RoomAvailabilityRepository $repo, string $categoryId): Response
|
public function bulkUpdateCategoryAvailability(Request $request, RoomAvailabilityRepository $repo, string $categoryId): Response
|
||||||
{
|
{
|
||||||
$categoryItem = $this->entityManager->find(RoomCategory::class, $categoryId);
|
|
||||||
if ($categoryItem == null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$form = $this->createFormBuilder()
|
$form = $this->createFormBuilder()
|
||||||
->add('startDate', DateType::class, [
|
->add('startDate', DateType::class, [
|
||||||
@@ -106,15 +115,33 @@ class RoomAvailabilityAdminController extends AbstractController
|
|||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
// $form->getData() holds the submitted values
|
// $form->getData() holds the submitted values
|
||||||
$data = $form->getData();
|
$data = $form->getData();
|
||||||
|
|
||||||
|
$categoryItem = $this->entityManager->find(RoomCategory::class, $categoryId);
|
||||||
$repo->bulkEditRoomAvailabilityForCategoryOnPeriod($categoryItem, $data['startDate'], $data['endDate'], $data['numAvailable']);
|
$repo->bulkEditRoomAvailabilityForCategoryOnPeriod($categoryItem, $data['startDate'], $data['endDate'], $data['numAvailable']);
|
||||||
|
|
||||||
$this->addFlash('success', 'Period Availability Updated!');
|
$url = $this->adminUrlGenerator->setRoute('admin_roomAvailability')->generateUrl();
|
||||||
return $this->redirectToRoute(AdminUtils::GetAdminRoute('room_availability_index'));
|
|
||||||
|
return $this->redirect($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('admin/room_availability/availability_bulk_edit.html.twig', [
|
return $this->render('admin/room_availability/availability_bulk_edit.html.twig', [
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
'category' => $categoryItem,
|
'categoryId' => $categoryId,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/roomAvailabilityTEST/', name: 'admin_roomAvailability_test')]
|
||||||
|
public function availabilityTest(EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$avl = new RoomAvailability();
|
||||||
|
|
||||||
|
$avl->setDay(new \DateTime("2024-04-24"));
|
||||||
|
$avl->setRoomCategory($entityManager->find(RoomCategory::class, 4));
|
||||||
|
$avl->setNumAvailable(2);
|
||||||
|
|
||||||
|
$entityManager->persist($avl);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->json($avl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller\Admin;
|
|
||||||
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\{TextField};
|
|
||||||
|
|
||||||
use App\Entity\RoomCategory;
|
|
||||||
use App\Service\AdminUtils;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
|
|
||||||
|
|
||||||
class RoomAvailabilityCrudController extends AbstractCrudController
|
|
||||||
{
|
|
||||||
public static function getEntityFqcn(): string
|
|
||||||
{
|
|
||||||
//Availability is configured by RoomCategory.
|
|
||||||
return RoomCategory::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configureCrud(Crud $crud): Crud
|
|
||||||
{
|
|
||||||
return $crud
|
|
||||||
->setPageTitle(Crud::PAGE_INDEX, "Room Category Availability")
|
|
||||||
->setHelp(Crud::PAGE_INDEX, "Edit Availability for a RoomCategory.")
|
|
||||||
->showEntityActionsInlined();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configureFields(string $pageName): iterable
|
|
||||||
{
|
|
||||||
//$catRepo = $this->entityManager->getRepository(SomeEntity::class);
|
|
||||||
|
|
||||||
yield TextField::new('name');
|
|
||||||
yield TextField::new('description');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configureActions(Actions $actions): Actions
|
|
||||||
{
|
|
||||||
$actions->disable(Action::NEW, Action::DETAIL, Action::EDIT, Action::DELETE);
|
|
||||||
|
|
||||||
$viewAvailabilityAction = Action::new('viewAvailability', 'View Availability')
|
|
||||||
->linkToRoute(
|
|
||||||
AdminUtils::GetAdminRoute(RoomAvailabilityAdminController::ROUTE_INDEX),
|
|
||||||
function (RoomCategory $categoryItem): array {
|
|
||||||
return ['categoryId' => $categoryItem->getId()];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$updatePeriodAction = Action::new("updatePeriod", "Update Period")
|
|
||||||
->linkToRoute(
|
|
||||||
AdminUtils::GetAdminRoute(RoomAvailabilityAdminController::ROUTE_BULKEDIT),
|
|
||||||
function (RoomCategory $categoryItem): array {
|
|
||||||
return ['categoryId' => $categoryItem->getId()];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $actions
|
|
||||||
->add(Crud::PAGE_INDEX, $viewAvailabilityAction)
|
|
||||||
->add(Crud::PAGE_INDEX, $updatePeriodAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller\Admin;
|
|
||||||
|
|
||||||
use App\Entity\RoomCategory;
|
|
||||||
use App\Entity\RoomCategoryBedType;
|
|
||||||
use App\Enum\AdminErrorEnum;
|
|
||||||
use App\Repository\RoomCategoryRepository;
|
|
||||||
use App\Repository\RoomCategoryBedTypeRepository;
|
|
||||||
use App\Form\RoomCategoryBedTypeFormType;
|
|
||||||
use App\Service\AdminUtils;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|
||||||
|
|
||||||
#[IsGranted("ROLE_ADMIN")]
|
|
||||||
class RoomCategoryBedTypeAdminController extends AbstractController
|
|
||||||
{
|
|
||||||
public const string ROUTE_INDEX = 'roomCategoryBedType_category';
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
private EntityManagerInterface $entityManager,
|
|
||||||
private AdminUtils $adminUtils
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// List BedTypes from category.
|
|
||||||
#[AdminRoute('/roomCategoryBedType/{categoryId}', name: RoomCategoryBedTypeAdminController::ROUTE_INDEX)]
|
|
||||||
public function categoryBedTypes(RoomCategoryRepository $catRepo, RoomCategoryBedTypeRepository $repo, string $categoryId): Response
|
|
||||||
{
|
|
||||||
$roomCategoryItem = $catRepo->find($categoryId);
|
|
||||||
if ($roomCategoryItem === null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$bedTypeList = $repo->findByRoomCategory($categoryId);
|
|
||||||
|
|
||||||
return $this->render('admin/room_category_bed_type_admin/categoryBedTypes.html.twig', [
|
|
||||||
'categoryData' => $roomCategoryItem,
|
|
||||||
'bedTypeList' => $bedTypeList,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a RoomCategory BedType.
|
|
||||||
#[AdminRoute('/roomCategoryBedType/{categoryId}/new', name: 'roomCategoryBedType_category_create')]
|
|
||||||
public function createCategoryBedType(Request $request, RoomCategoryBedTypeRepository $repo, string $categoryId): Response
|
|
||||||
{
|
|
||||||
$roomCategoryItem = $this->entityManager->find(RoomCategory::class, $categoryId);
|
|
||||||
if ($roomCategoryItem === null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$roomCatBedType = new RoomCategoryBedType();
|
|
||||||
$roomCatBedType->setRoomCategory($roomCategoryItem);
|
|
||||||
|
|
||||||
$form = $this->createForm(RoomCategoryBedTypeFormType::class, $roomCatBedType, [
|
|
||||||
'selectRoomCategory' => false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$form->handleRequest($request);
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
// $form->getData() holds the submitted values
|
|
||||||
// but, the original `$availabilityItem` variable has also been updated
|
|
||||||
|
|
||||||
//TODO: Validate if selected BedType already exist for current RoomCategory.
|
|
||||||
|
|
||||||
$this->entityManager->persist($roomCatBedType);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
$this->addFlash('success', 'Element created!');
|
|
||||||
return $this->goToIndex($categoryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('admin/adminEdit.html.twig', [
|
|
||||||
'form' => $form,
|
|
||||||
'ElementType' => "Room Categoy Bed Type",
|
|
||||||
'ElementName' => "Room Categoy - " . $roomCategoryItem
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit a RoomCategory BedType.
|
|
||||||
#[AdminRoute('/roomCategoryBedType/{categoryId}/{bedTypeId}/edit', name: 'roomCategoryBedType_category_update')]
|
|
||||||
public function updateCategoryBedType(Request $request, RoomCategoryBedTypeRepository $repo, string $categoryId, string $bedTypeId): Response
|
|
||||||
{
|
|
||||||
$categoryBedTypeItem = $repo->find(["bedType" => $bedTypeId, "roomCategory" => $categoryId]);
|
|
||||||
|
|
||||||
if ($categoryBedTypeItem === null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$form = $this->createForm(RoomCategoryBedTypeFormType::class, $categoryBedTypeItem, [
|
|
||||||
'selectRoomCategory' => false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$form->handleRequest($request);
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
// $form->getData() holds the submitted values
|
|
||||||
// but, the original `$categoryBedTypeItem` variable has also been updated
|
|
||||||
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
$this->addFlash('primary', 'Element updated!');
|
|
||||||
return $this->goToIndex($categoryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('admin/adminEdit.html.twig', [
|
|
||||||
'form' => $form,
|
|
||||||
'ElementType' => "Room Categoy Bed Type",
|
|
||||||
'ElementName' => "Room Categoy - " . $categoryBedTypeItem->getRoomCategory()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a RoomCategoryBedType.
|
|
||||||
#[AdminRoute('/roomCategoryBedType/{categoryId}/{bedTypeId}/remove', name: 'roomCategoryBedType_category_remove')]
|
|
||||||
public function removeCategoryBedType(RoomCategoryBedTypeRepository $repo, string $categoryId, string $bedTypeId): Response
|
|
||||||
{
|
|
||||||
$categoryBedTypeItem = $repo->find(["bedType" => $bedTypeId, "roomCategory" => $categoryId]);
|
|
||||||
if ($categoryBedTypeItem === null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->entityManager->remove($categoryBedTypeItem);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
$this->addFlash('success', 'Element deleted!');
|
|
||||||
return $this->goToIndex($categoryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function goToIndex(int $roomCategory): Response
|
|
||||||
{
|
|
||||||
return $this->redirectToRoute(AdminUtils::GetAdminRoute(RoomCategoryBedTypeAdminController::ROUTE_INDEX), [
|
|
||||||
'categoryId' => $roomCategory,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,10 +4,14 @@ namespace App\Controller\Admin;
|
|||||||
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
|
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\{TextField, IntegerField, CollectionField};
|
use EasyCorp\Bundle\EasyAdminBundle\Field\Field;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\{Actions, Action};
|
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
|
||||||
|
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
||||||
|
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
|
||||||
|
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
|
||||||
|
use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField;
|
||||||
|
|
||||||
use App\Entity\RoomCategory;
|
use App\Entity\RoomCategory;
|
||||||
use App\Service\AdminUtils;
|
|
||||||
|
|
||||||
class RoomCategoryCrudController extends AbstractCrudController
|
class RoomCategoryCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
@@ -23,25 +27,14 @@ class RoomCategoryCrudController extends AbstractCrudController
|
|||||||
|
|
||||||
public function configureFields(string $pageName): iterable
|
public function configureFields(string $pageName): iterable
|
||||||
{
|
{
|
||||||
yield TextField::new('name');
|
return [
|
||||||
yield TextField::new('description');
|
//IdField::new('id'),
|
||||||
yield IntegerField::new('maxGuestNum');
|
TextField::new('name'),
|
||||||
yield IntegerField::new('orderHomePage');
|
TextField::new('description'),
|
||||||
yield CollectionField::new('roomCategoryPhotos')->useEntryCrudForm();
|
TextField::new('bedType'),
|
||||||
}
|
IntegerField::new('maxGuestNum'),
|
||||||
|
CollectionField::new('roomCategoryDetails')->useEntryCrudForm(),
|
||||||
public function configureActions(Actions $actions): Actions
|
CollectionField::new('roomCategoryPhotos')->useEntryCrudForm()
|
||||||
{
|
];
|
||||||
$viewBedTypes = Action::new('bedTypes', 'Bed Types')
|
|
||||||
->setIcon('fas fa-bed')
|
|
||||||
->linkToRoute(
|
|
||||||
AdminUtils::GetAdminRoute(RoomCategoryBedTypeAdminController::ROUTE_INDEX),
|
|
||||||
function (RoomCategory $categoryItem): array {
|
|
||||||
return ['categoryId' => $categoryItem->getId()];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $actions
|
|
||||||
->add(Crud::PAGE_INDEX, $viewBedTypes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-7
@@ -2,22 +2,23 @@
|
|||||||
|
|
||||||
namespace App\Controller\Admin;
|
namespace App\Controller\Admin;
|
||||||
|
|
||||||
use App\Controller\Admin\Field\IconField;
|
use App\Entity\RoomCategoryDetails;
|
||||||
use App\Entity\BedType;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField;
|
|
||||||
|
|
||||||
class BedTypeCrudController extends AbstractCrudController
|
class RoomCategoryDetailsCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
public static function getEntityFqcn(): string
|
public static function getEntityFqcn(): string
|
||||||
{
|
{
|
||||||
return BedType::class;
|
return RoomCategoryDetails::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function configureFields(string $pageName): iterable
|
public function configureFields(string $pageName): iterable
|
||||||
{
|
{
|
||||||
yield TextField::new('name');
|
return [
|
||||||
yield IconField::new('iconUrl');
|
TextField::new('detailsSection'),
|
||||||
|
TextField::new('detailValue'),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace App\Controller\Admin;
|
|||||||
use App\Entity\RoomCategoryPhoto;
|
use App\Entity\RoomCategoryPhoto;
|
||||||
use App\Entity\RoomCategoryPhotoKinds;
|
use App\Entity\RoomCategoryPhotoKinds;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\{TextField, IntegerField};
|
use EasyCorp\Bundle\EasyAdminBundle\Field\{TextField, ChoiceField};
|
||||||
|
|
||||||
class RoomCategoryPhotoCrudController extends AbstractCrudController
|
class RoomCategoryPhotoCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@ class RoomCategoryPhotoCrudController extends AbstractCrudController
|
|||||||
{
|
{
|
||||||
yield TextField::new('path');
|
yield TextField::new('path');
|
||||||
yield TextField::new('altText');
|
yield TextField::new('altText');
|
||||||
yield IntegerField::new('photoOrder');
|
yield ChoiceField::new('kind')->setChoices(['Main' => 'main', 'Bathroom' => 'bathroom', 'Other' => 'other']);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -20,8 +20,7 @@ class RoomFareCrudController extends AbstractCrudController
|
|||||||
|
|
||||||
public function configureFields(string $pageName): iterable
|
public function configureFields(string $pageName): iterable
|
||||||
{
|
{
|
||||||
$fieldFareTable = AssociationField::new('fareTable')->setRequired(true)
|
$fieldFareTable = AssociationField::new ('fareTable')->setRequired(true);
|
||||||
->formatValue(fn ($value) => $value->getComment() . " (" . $value->getRoomCategory() . ")");
|
|
||||||
if ($pageName == Crud::PAGE_EDIT) {
|
if ($pageName == Crud::PAGE_EDIT) {
|
||||||
$fieldFareTable->setDisabled(true);
|
$fieldFareTable->setDisabled(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller\Admin;
|
|
||||||
|
|
||||||
use App\Consts\Consts;
|
|
||||||
use App\DTO\SearchFilterByDate;
|
|
||||||
use App\Entity\RoomCategory;
|
|
||||||
use App\Entity\RoomPrice;
|
|
||||||
use App\Enum\AdminErrorEnum;
|
|
||||||
use App\Form\RoomPriceFormType;
|
|
||||||
use App\Form\SearchFilterByDateFormType;
|
|
||||||
use App\Repository\RoomCategoryRepository;
|
|
||||||
use App\Repository\RoomPriceRepository;
|
|
||||||
use App\Service\AdminUtils;
|
|
||||||
use DateInterval;
|
|
||||||
use DateTimeImmutable;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|
||||||
|
|
||||||
#[IsGranted("ROLE_ADMIN")]
|
|
||||||
class RoomPriceAdminController extends AbstractController
|
|
||||||
{
|
|
||||||
public const string ROUTE_INDEX = 'roomPrice_prices';
|
|
||||||
public const string ROUTE_CREATE = 'roomPrice_prices_create';
|
|
||||||
public const string ROUTE_REMOVE = 'roomPrice_prices_remove';
|
|
||||||
|
|
||||||
private const string SESSION_SEARCHFILTER = 'RoomPrice_SearchFilter';
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
private EntityManagerInterface $entityManager,
|
|
||||||
private AdminUtils $adminUtils,
|
|
||||||
private RoomCategoryRepository $catRepo,
|
|
||||||
private RoomPriceRepository $priceRepo
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// List Room Prices from category.
|
|
||||||
#[AdminRoute('/roomPrices/{categoryId}', name: RoomPriceAdminController::ROUTE_INDEX)]
|
|
||||||
public function roomPrices(Request $request, string $categoryId): Response
|
|
||||||
{
|
|
||||||
$session = $request->getSession();
|
|
||||||
|
|
||||||
$roomCategoryItem = $this->catRepo->find($categoryId);
|
|
||||||
if ($roomCategoryItem === null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var SearchFilterByDate */
|
|
||||||
$searchFilter = $session->get(RoomPriceAdminController::SESSION_SEARCHFILTER, null);
|
|
||||||
|
|
||||||
//Set default values to filter.
|
|
||||||
if ($searchFilter == null) {
|
|
||||||
$searchFilter = new SearchFilterByDate();
|
|
||||||
|
|
||||||
$dtFrom = (new DateTimeImmutable())->sub(new DateInterval("P1M"));
|
|
||||||
$searchFilter->setDtFrom($dtFrom);
|
|
||||||
$dtTo = (new DateTimeImmutable())->add(new DateInterval("P365D"));
|
|
||||||
$searchFilter->setDtTo($dtTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$searchForm = $this->createForm(SearchFilterByDateFormType::class, $searchFilter);
|
|
||||||
$searchForm->handleRequest($request);
|
|
||||||
|
|
||||||
$roomPriceList = $this->priceRepo->getListForPeriod($searchFilter->getDtFrom(), $searchFilter->getDtTo(), $categoryId);
|
|
||||||
|
|
||||||
//Store value in session for following requests.
|
|
||||||
$session->set(RoomPriceAdminController::SESSION_SEARCHFILTER, $searchFilter);
|
|
||||||
|
|
||||||
return $this->render('admin/room_price/roomPriceList.html.twig', [
|
|
||||||
'searchForm' => $searchForm,
|
|
||||||
'categoryData' => $roomCategoryItem,
|
|
||||||
'roomPriceList' => $roomPriceList,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[AdminRoute('/roomPrices/{categoryId}/new', name: RoomPriceAdminController::ROUTE_CREATE)]
|
|
||||||
public function create(Request $request, string $categoryId): Response
|
|
||||||
{
|
|
||||||
$roomCategoryItem = $this->catRepo->find($categoryId);
|
|
||||||
if ($roomCategoryItem === null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$roomPriceItem = new RoomPrice();
|
|
||||||
$roomPriceItem->setRoomCategory($roomCategoryItem);
|
|
||||||
|
|
||||||
$form = $this->createForm(RoomPriceFormType::class, $roomPriceItem, [
|
|
||||||
'selectRoomCategory' => false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$form->handleRequest($request);
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
// $form->getData() holds the submitted values
|
|
||||||
// but, the original `$availabilityItem` variable has also been updated
|
|
||||||
|
|
||||||
if ($this->isRoomPriceCreationValid($roomPriceItem, $roomCategoryItem)) {
|
|
||||||
$this->entityManager->persist($roomPriceItem);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
$this->addFlash('success', 'Element created!');
|
|
||||||
return $this->goToIndex($categoryId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('admin/adminEdit.html.twig', [
|
|
||||||
'form' => $form,
|
|
||||||
'ElementType' => "Room Categoy Bed Type",
|
|
||||||
'ElementName' => "Room Categoy - " . $roomCategoryItem
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[AdminRoute('/roomPrices/{categoryId}/remove/{date}/{guestNumber}', name: RoomPriceAdminController::ROUTE_REMOVE)]
|
|
||||||
public function remove(string $categoryId, string $date, string $guestNumber): Response
|
|
||||||
{
|
|
||||||
$item = $this->priceRepo->find(['roomCategory' => $categoryId, 'date' => $date, 'guestNumber' => $guestNumber]);
|
|
||||||
if ($item === null) {
|
|
||||||
return $this->adminUtils->goToAdminErrorType(AdminErrorEnum::ENTITY_NOTFOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->entityManager->remove($item);
|
|
||||||
$this->entityManager->flush();
|
|
||||||
|
|
||||||
$this->addFlash('success', 'Element deleted!');
|
|
||||||
return $this->goToIndex($categoryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private function goToIndex(int $roomCategory): Response
|
|
||||||
{
|
|
||||||
return $this->redirectToRoute(AdminUtils::GetAdminRoute(RoomPriceAdminController::ROUTE_INDEX), [
|
|
||||||
'categoryId' => $roomCategory,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isRoomPriceCreationValid(RoomPrice $priceItem, RoomCategory $roomCategoryItem): bool
|
|
||||||
{
|
|
||||||
$roomMaxGuestNum = $roomCategoryItem->getMaxGuestNum();
|
|
||||||
if ($priceItem->getGuestNumber() > $roomMaxGuestNum) {
|
|
||||||
$msg = "Guest number is greater than Room Category max Guest number ('$roomMaxGuestNum')";
|
|
||||||
$this->addFlash('error', $msg);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$alreadyExistsItem = $this->priceRepo->find([
|
|
||||||
'roomCategory' => $priceItem->getRoomCategory()->getId(),
|
|
||||||
'date' => $priceItem->getDate()->format(Consts::FORMAT_DATE),
|
|
||||||
'guestNumber' => $priceItem->getGuestNumber()
|
|
||||||
]);
|
|
||||||
if ($alreadyExistsItem != null) {
|
|
||||||
$this->addFlash('error', 'Price already exist!');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller\Admin;
|
|
||||||
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\{TextField};
|
|
||||||
|
|
||||||
use App\Entity\RoomCategory;
|
|
||||||
use App\Service\AdminUtils;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
|
|
||||||
|
|
||||||
class RoomPriceCrudController extends AbstractCrudController
|
|
||||||
{
|
|
||||||
public static function getEntityFqcn(): string
|
|
||||||
{
|
|
||||||
//Room Prices are configured by RoomCategory.
|
|
||||||
return RoomCategory::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configureCrud(Crud $crud): Crud
|
|
||||||
{
|
|
||||||
return $crud
|
|
||||||
->setPageTitle(Crud::PAGE_INDEX, "Room Prices")
|
|
||||||
->setHelp(Crud::PAGE_INDEX, "Edit prices for a RoomCategory.")
|
|
||||||
->showEntityActionsInlined();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configureFields(string $pageName): iterable
|
|
||||||
{
|
|
||||||
yield TextField::new('name');
|
|
||||||
yield TextField::new('description');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configureActions(Actions $actions): Actions
|
|
||||||
{
|
|
||||||
$actions->disable(Action::NEW, Action::DETAIL, Action::EDIT, Action::DELETE);
|
|
||||||
|
|
||||||
$viewAvailabilityAction = Action::new('priceList', 'Prices')
|
|
||||||
->linkToRoute(
|
|
||||||
AdminUtils::GetAdminRoute(RoomPriceAdminController::ROUTE_INDEX),
|
|
||||||
function (RoomCategory $categoryItem): array {
|
|
||||||
return ['categoryId' => $categoryItem->getId()];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $actions
|
|
||||||
->add(Crud::PAGE_INDEX, $viewAvailabilityAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,22 +2,21 @@
|
|||||||
|
|
||||||
namespace App\Controller\Admin;
|
namespace App\Controller\Admin;
|
||||||
|
|
||||||
use App\Entity\BedType;
|
|
||||||
use App\Entity\Booking;
|
use App\Entity\Booking;
|
||||||
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\RoomFare;
|
use App\Entity\RoomFare;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard;
|
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
|
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
|
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
|
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
#[AdminDashboard(routePath: '/admin', routeName: 'admin')]
|
|
||||||
class SomniaDashboardController extends AbstractDashboardController
|
class SomniaDashboardController extends AbstractDashboardController
|
||||||
{
|
{
|
||||||
|
#[Route('/admin', name: 'admin')]
|
||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
return $this->render('admin/somniaDashboard.html.twig');
|
return $this->render('admin/somniaDashboard.html.twig');
|
||||||
@@ -25,7 +24,7 @@ class SomniaDashboardController extends AbstractDashboardController
|
|||||||
|
|
||||||
public function configureDashboard(): Dashboard
|
public function configureDashboard(): Dashboard
|
||||||
{
|
{
|
||||||
return Dashboard::new()
|
return Dashboard::new ()
|
||||||
->setTitle('SomniaRooms');
|
->setTitle('SomniaRooms');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,17 +36,10 @@ class SomniaDashboardController extends AbstractDashboardController
|
|||||||
yield MenuItem::linkToCrud('Bookings', 'fas fa-book', Booking::class);
|
yield MenuItem::linkToCrud('Bookings', 'fas fa-book', Booking::class);
|
||||||
|
|
||||||
yield MenuItem::Section('Room Management');
|
yield MenuItem::Section('Room Management');
|
||||||
yield MenuItem::linkToCrud('Availability', 'fas fa-calendar-check', RoomCategory::class)->setController(RoomAvailabilityCrudController::class);
|
yield MenuItem::linkToCrud('Category', 'fas fa-layer-group', RoomCategory::class);
|
||||||
|
yield MenuItem::linkToRoute('Room Availability', 'fas fa-calendar-check', 'admin_roomAvailability');
|
||||||
yield MenuItem::linkToCrud('Fare Tables', 'fas fa-table', FareTable::class);
|
yield MenuItem::linkToCrud('Fare Tables', 'fas fa-table', FareTable::class);
|
||||||
yield MenuItem::linkToCrud('Room Fares', 'fas fa-tag', RoomFare::class);
|
yield MenuItem::linkToCrud('Room Fares', 'fas fa-tag', RoomFare::class);
|
||||||
yield MenuItem::linkToCrud('Room Prices', 'fas fa-coins', RoomCategory::class)->setController(RoomPriceCrudController::class);
|
|
||||||
|
|
||||||
yield MenuItem::Section('Category Configuration');
|
|
||||||
yield MenuItem::linkToCrud('Category', 'fas fa-layer-group', RoomCategory::class)->setController(RoomCategoryCrudController::class);
|
|
||||||
yield MenuItem::linkToCrud('Bed Types', 'fas fa-bed', BedType::class);
|
|
||||||
|
|
||||||
yield MenuItem::Section('Resources');
|
|
||||||
yield MenuItem::linkToRoute('Photos', 'fas fa-image', 'admin_photos');
|
|
||||||
|
|
||||||
yield MenuItem::Section('App Management');
|
yield MenuItem::Section('App Management');
|
||||||
yield MenuItem::linkToCrud('Config Vars', 'fas fa-gear', ConfigVariable::class);
|
yield MenuItem::linkToCrud('Config Vars', 'fas fa-gear', ConfigVariable::class);
|
||||||
|
|||||||
@@ -28,18 +28,18 @@ class UserCrudController extends AbstractCrudController
|
|||||||
|
|
||||||
public function configureFields(string $pageName): iterable
|
public function configureFields(string $pageName): iterable
|
||||||
{
|
{
|
||||||
yield TextField::new('username');
|
yield TextField::new ('username');
|
||||||
yield TextField::new('username')->onlyWhenUpdating()->setDisabled(true);
|
yield TextField::new ('username')->onlyWhenUpdating()->setDisabled(true);
|
||||||
|
|
||||||
$availableRoles = ['User' => 'ROLE_USER', 'Admin' => 'ROLE_ADMIN', 'Test' => 'ROLE_TEST', 'SuperAdmin' => 'ROLE_SUPERADMIN'];
|
$availableRoles = ['User' => 'ROLE_USER', 'Admin' => 'ROLE_ADMIN', 'Test' => 'ROLE_TEST', 'SuperAdmin' => 'ROLE_SUPERADMIN'];
|
||||||
yield ChoiceField::new('roles')
|
yield ChoiceField::new ('roles')
|
||||||
->setChoices($availableRoles)
|
->setChoices($availableRoles)
|
||||||
->allowMultipleChoices(true);
|
->allowMultipleChoices(true);
|
||||||
|
|
||||||
|
|
||||||
yield FormField::addFieldset('Set password')->setIcon('fa fa-key');
|
yield FormField::addPanel('Set password')->setIcon('fa fa-key');
|
||||||
|
|
||||||
$fieldPassword = Field::new('plainPassword', 'New password')
|
$fieldPassword = Field::new ('plainPassword', 'New password')
|
||||||
->hideOnIndex()
|
->hideOnIndex()
|
||||||
->setRequired(false)
|
->setRequired(false)
|
||||||
->setFormType(RepeatedType::class)
|
->setFormType(RepeatedType::class)
|
||||||
@@ -50,7 +50,7 @@ class UserCrudController extends AbstractCrudController
|
|||||||
'error_bubbling' => true,
|
'error_bubbling' => true,
|
||||||
'invalid_message' => 'The password fields do not match.',
|
'invalid_message' => 'The password fields do not match.',
|
||||||
]);
|
]);
|
||||||
if ($pageName == Crud::PAGE_NEW) {
|
if($pageName == Crud::PAGE_NEW) {
|
||||||
$fieldPassword->setRequired(true);
|
$fieldPassword->setRequired(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ class UserCrudController extends AbstractCrudController
|
|||||||
{
|
{
|
||||||
$plaintextPassword = $entityInstance->getPlainPassword();
|
$plaintextPassword = $entityInstance->getPlainPassword();
|
||||||
|
|
||||||
if ($plaintextPassword && $plaintextPassword != "") {
|
if($plaintextPassword && $plaintextPassword != "") {
|
||||||
//New password set. Hash password.
|
//New password set. Hash password.
|
||||||
$hashedPassword = $this->userPasswordHasher->hashPassword($entityInstance, $plaintextPassword);
|
$hashedPassword = $this->userPasswordHasher->hashPassword($entityInstance, $plaintextPassword);
|
||||||
$entityInstance->setPassword($hashedPassword);
|
$entityInstance->setPassword($hashedPassword);
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller;
|
|
||||||
|
|
||||||
use App\Consts\ConfigVariableConsts;
|
|
||||||
use App\DTO\AppSetupConfig;
|
|
||||||
use App\DTO\ExampleDataConfig;
|
|
||||||
use App\Entity\ConfigVariable;
|
|
||||||
use App\Form\AppSetupFormType;
|
|
||||||
use App\Form\ExampleDataFormType;
|
|
||||||
use App\Repository\RoomCategoryRepository;
|
|
||||||
use App\Service\AppSetupService;
|
|
||||||
use App\Service\ConfigVariableService;
|
|
||||||
use App\Service\ExampleDataService;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
|
||||||
|
|
||||||
class AppSetupController extends AbstractController
|
|
||||||
{
|
|
||||||
#[Route(path: '/setup', name: 'app_setup_setup')]
|
|
||||||
public function addAdmin(
|
|
||||||
LoggerInterface $logger,
|
|
||||||
AppSetupService $setupService,
|
|
||||||
Request $request,
|
|
||||||
ConfigVariableService $configVars
|
|
||||||
): Response {
|
|
||||||
|
|
||||||
$setupVar_key = ConfigVariableConsts::getKeyFromConstant(ConfigVariableConsts::CONFIG_INTERNAL_SETUP);
|
|
||||||
$setupStatus = $configVars->getVariable($setupVar_key);
|
|
||||||
|
|
||||||
if ($setupStatus != null && $setupStatus->getValue()) {
|
|
||||||
$logger->warning("SomniaRooms was already setup");
|
|
||||||
|
|
||||||
return $this->GoToResultPage(
|
|
||||||
isError: false,
|
|
||||||
message: "SomniaRooms already setup.",
|
|
||||||
linkRoute: "admin",
|
|
||||||
linkLabel: "Go to admin panel"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$config = new AppSetupConfig();
|
|
||||||
$form = $this->createForm(AppSetupFormType::class, $config);
|
|
||||||
|
|
||||||
$form->handleRequest($request);
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
|
|
||||||
$rs = $setupService->InitialSetup($config);
|
|
||||||
|
|
||||||
if (!$rs) {
|
|
||||||
return $this->GoToResultPage(
|
|
||||||
isError: false,
|
|
||||||
message: "An error occurred during setup proccess, try again."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($config->getGenExampleData()) {
|
|
||||||
return $this->redirectToRoute("app_setup_gendata");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->GoToResultPage(
|
|
||||||
isError: false,
|
|
||||||
message: "SomniaRooms is setup! Go to admin panel to start!",
|
|
||||||
linkRoute: "admin",
|
|
||||||
linkLabel: "Go to admin panel"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('app_setup/setup.html.twig', [
|
|
||||||
'form' => $form,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[Route('/debug/exampleData', name: 'app_setup_gendata')]
|
|
||||||
public function generateData(
|
|
||||||
Request $request,
|
|
||||||
ExampleDataService $dataService,
|
|
||||||
RoomCategoryRepository $roomCategoryRepository
|
|
||||||
): Response {
|
|
||||||
|
|
||||||
$config = new ExampleDataConfig();
|
|
||||||
$form = $this->createForm(ExampleDataFormType::class, $config);
|
|
||||||
$isFormInvalid = $form->isSubmitted() && !$form->isValid();
|
|
||||||
|
|
||||||
//Check for RoomCategories on db.
|
|
||||||
$roomCategoryCount = $roomCategoryRepository->count();
|
|
||||||
$hasRoomCategories = $roomCategoryCount > 0;
|
|
||||||
|
|
||||||
$form->handleRequest($request);
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
|
|
||||||
$rs = $dataService->AddExampleData($config);
|
|
||||||
$isOk = $rs == "OK";
|
|
||||||
$msg = $isOk ? "Example data generated! Go to admin panel to start!" : $rs;
|
|
||||||
|
|
||||||
return $this->GoToResultPage(
|
|
||||||
isError: !$isOk,
|
|
||||||
message: $msg,
|
|
||||||
linkRoute: $isOk ? "admin" : "",
|
|
||||||
linkLabel: $isOk ? "Go to admin panel" : ""
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('app_setup/exampleData.html.twig', [
|
|
||||||
'form' => $form,
|
|
||||||
'isFormInvalid' => $isFormInvalid,
|
|
||||||
'hasRoomCategories' => $hasRoomCategories
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function GoToResultPage(bool $isError, string $message, ?string $linkRoute = null, ?string $linkLabel = null): Response
|
|
||||||
{
|
|
||||||
return $this->render('app_setup/result.html.twig', [
|
|
||||||
'isError' => $isError,
|
|
||||||
'message' => $message,
|
|
||||||
'linkLabel' => $linkLabel,
|
|
||||||
'linkRoute' => $linkRoute
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,170 +2,122 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\ApiErrorException;
|
|
||||||
use App\Consts\ConfigVariableConsts;
|
|
||||||
use App\DTO\ApiErrorMessage;
|
|
||||||
use App\Entity\Booking;
|
use App\Entity\Booking;
|
||||||
|
use App\Entity\BookingCustomer;
|
||||||
use App\Entity\BookingRoom;
|
use App\Entity\BookingRoom;
|
||||||
use App\Repository\BookingRepository;
|
use App\Repository\BookingRepository;
|
||||||
use App\Repository\RoomCategoryRepository;
|
use App\Repository\RoomCategoryRepository;
|
||||||
use App\Repository\RoomAvailabilityRepository;
|
use App\Repository\RoomAvailabilityRepository;
|
||||||
use App\Request\BookingCreateRequest;
|
|
||||||
use App\Request\BookingDetailsRequest;
|
|
||||||
use App\Response\BookingCreateResponse;
|
|
||||||
use App\Service\ConfigVariableService;
|
|
||||||
use App\Service\PricesService;
|
use App\Service\PricesService;
|
||||||
use Doctrine\DBAL\TransactionIsolationLevel;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use OpenApi\Attributes as OA;
|
|
||||||
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
|
|
||||||
class BookingController extends AbstractController
|
class BookingController extends AbstractController
|
||||||
{
|
{
|
||||||
/**
|
#[Route('/api/booking/details', name: 'app_booking_details')]
|
||||||
* Get booking details
|
public function getBookingDetails(Request $request, BookingRepository $repoBooking): JsonResponse
|
||||||
*/
|
{
|
||||||
#[Route('/api/booking/details', name: 'app_booking_details', methods: ["POST"], format: "json")]
|
$payload = $request->toArray();
|
||||||
#[OA\Tag(name: "Booking")]
|
if (\is_null($payload)) {
|
||||||
#[OA\Response(
|
throw new \Exception('Invalid Payload', 400);
|
||||||
response: 200,
|
|
||||||
description: 'Returns booking details',
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
ref: new Model(type: Booking::class),
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[OA\RequestBody(
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
ref: new Model(type: BookingDetailsRequest::class),
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
public function getBookingDetails(
|
|
||||||
BookingRepository $repoBooking,
|
|
||||||
#[MapRequestPayload()] BookingDetailsRequest $detailsRequest
|
|
||||||
): JsonResponse {
|
|
||||||
|
|
||||||
$selectedBooking = $repoBooking->findOneBy(["refNumber" => $detailsRequest->refNumber]);
|
|
||||||
if (\is_null($selectedBooking)) {
|
|
||||||
throw new NotFoundHttpException('Booking not found');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($selectedBooking->getBookingCustomer()->getEmail() != $detailsRequest->email) {
|
$refNumber = $payload["refNumber"];
|
||||||
throw new NotFoundHttpException('Booking not found');
|
$customerEmail = $payload["email"];
|
||||||
|
|
||||||
|
$bookingList = $repoBooking->findBy(["refNumber" => $refNumber]);
|
||||||
|
if (\is_null($bookingList) || \count($bookingList) == 0) {
|
||||||
|
throw new HttpException(404, 'Invalid Booking');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->json($selectedBooking);
|
$customerBooking = $bookingList[0];
|
||||||
|
if ($customerBooking->getBookingCustomer()->getEmail() != $customerEmail) {
|
||||||
|
throw new HttpException(404, 'Invalid Booking');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json($customerBooking);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Route('/api/booking/create', name: 'app_booking_create')]
|
||||||
* Create a new Booking.
|
public function newBooking(Request $request, BookingRepository $repoBooking, EntityManagerInterface $entityManager,
|
||||||
*/
|
RoomCategoryRepository $repoCat, RoomAvailabilityRepository $repoAvailability,
|
||||||
#[Route('/api/booking/create', name: 'app_booking_create', methods: ["POST"], format: "json")]
|
PricesService $priceSrv): JsonResponse
|
||||||
#[OA\Tag(name: "Booking")]
|
{
|
||||||
#[OA\Response(
|
$payload = $request->toArray();
|
||||||
response: 200,
|
if (\is_null($payload)) {
|
||||||
description: 'Returns booking details',
|
throw new \Exception('Invalid Payload', 400);
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
ref: new Model(type: BookingCreateResponse::class)
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[OA\RequestBody(
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
ref: new Model(type: BookingCreateRequest::class),
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
public function newBooking(
|
|
||||||
EntityManagerInterface $entityManager,
|
|
||||||
RoomCategoryRepository $repoCat,
|
|
||||||
RoomAvailabilityRepository $repoAvailability,
|
|
||||||
PricesService $priceSrv,
|
|
||||||
ConfigVariableService $config,
|
|
||||||
#[MapRequestPayload()] BookingCreateRequest $createRequest
|
|
||||||
): JsonResponse {
|
|
||||||
|
|
||||||
if ($createRequest->arrivalDate >= $createRequest->departureDate) {
|
|
||||||
throw new ApiErrorException(
|
|
||||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_INVALID_DATE),
|
|
||||||
'Arrival cant be before departure',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\count($createRequest->rooms) == 0) {
|
|
||||||
throw new ApiErrorException(
|
|
||||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_VALIDATION_ERROR),
|
|
||||||
'Booking must contain rooms',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$maxAllowedGuestNumber = $config->getVariableFromConst(ConfigVariableConsts::CONFIG_API_NAXGUESTS)?->getValue() ?? 0;
|
|
||||||
if ($maxAllowedGuestNumber > 0) {
|
|
||||||
$totalGuestNumber = array_reduce($createRequest->rooms, function ($carry, $item) {
|
|
||||||
return $carry += $item->guestNumber;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
if ($totalGuestNumber > $maxAllowedGuestNumber) {
|
|
||||||
throw new ApiErrorException(
|
|
||||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_VALIDATION_ERROR),
|
|
||||||
'Max allowed guest number exceeded. Contact the company to complete the booking',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentTime = new \DateTimeImmutable("now");
|
|
||||||
$booking = new Booking();
|
$booking = new Booking();
|
||||||
try {
|
try {
|
||||||
|
$reqArrivalDate = $payload["arrivalDate"];
|
||||||
|
$reqDepartureDate = $payload["departureDate"];
|
||||||
|
|
||||||
$entityManager->beginTransaction(TransactionIsolationLevel::REPEATABLE_READ);
|
$arrivalDate = new \DateTime($reqArrivalDate);
|
||||||
|
$departureDate = new \DateTime($reqDepartureDate);
|
||||||
|
|
||||||
|
if ($arrivalDate >= $departureDate) {
|
||||||
|
throw new HttpException(400, "Arrival cant be before departure");
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityManager->beginTransaction();
|
||||||
|
|
||||||
|
$currentTime = new \DateTime();
|
||||||
|
|
||||||
// Create booking from request data.
|
// Create booking from request data.
|
||||||
$booking->setBookingTime($currentTime);
|
$booking->setBookingTime($currentTime);
|
||||||
$booking->setArrivalDate($createRequest->arrivalDate);
|
$booking->setArrivalDate($arrivalDate);
|
||||||
$booking->setDepartureDate($createRequest->departureDate);
|
$booking->setDepartureDate($departureDate);
|
||||||
$booking->setBookingCustomer($createRequest->customerDetails);
|
|
||||||
|
|
||||||
//TODOME: Improve refNumber generation.
|
|
||||||
$highest_id = $entityManager->createQueryBuilder()
|
$highest_id = $entityManager->createQueryBuilder()
|
||||||
->select('MAX(e.id)')
|
->select('MAX(e.id)')
|
||||||
->from('App\Entity\Booking', 'e')
|
->from('App\Entity\Booking', 'e')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getSingleScalarResult();
|
->getSingleScalarResult();
|
||||||
|
|
||||||
$booking->setRefNumber($currentTime->format("Y") . $highest_id + 1);
|
$booking->setRefNumber($currentTime->format("Y") . $highest_id + 1);
|
||||||
|
|
||||||
$entityManager->persist($booking);
|
$entityManager->persist($booking);
|
||||||
|
|
||||||
|
// Create booking customer.
|
||||||
|
$reqCustomer = $payload["customerDetails"];
|
||||||
|
$bookingCustomer = new BookingCustomer();
|
||||||
|
$bookingCustomer->setName($reqCustomer["name"]);
|
||||||
|
$bookingCustomer->setSurname($reqCustomer["surname"]);
|
||||||
|
$bookingCustomer->setEmail($reqCustomer["email"]);
|
||||||
|
$bookingCustomer->setPhoneNumber($reqCustomer["phoneNumber"]);
|
||||||
|
$bookingCustomer->setBooking($booking);
|
||||||
|
|
||||||
|
$entityManager->persist($bookingCustomer);
|
||||||
|
|
||||||
|
$reqRoomList = $payload["rooms"];
|
||||||
|
if (\count($reqRoomList) == 0) {
|
||||||
|
throw new HttpException(400, "Se debe indicar al menos una habitación");
|
||||||
|
}
|
||||||
|
|
||||||
// Create each room.
|
// Create each room.
|
||||||
foreach ($createRequest->rooms as $roomItem) {
|
foreach ($reqRoomList as $roomItem) {
|
||||||
$room = new BookingRoom();
|
$room = new BookingRoom();
|
||||||
$room->setBooking($booking);
|
$room->setBooking($booking);
|
||||||
|
|
||||||
//Get Room category.
|
//Get Room category.
|
||||||
$roomCategory = $repoCat->find($roomItem->roomCategory);
|
$roomCategory = $repoCat->find($roomItem["roomCategory"]);
|
||||||
if ($roomCategory == null) {
|
if ($roomCategory == null) {
|
||||||
throw new ApiErrorException(
|
throw new HttpException(400, "La RoomCategory no es válida.");
|
||||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_VALIDATION_ERROR),
|
|
||||||
'Invalid RoomCategory: ' . $roomItem->roomCategory,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check room availability.
|
//Check room availability.
|
||||||
$amountAvailable = $repoAvailability->getAvailabilityForCategoryInPeriod($roomCategory, $createRequest->arrivalDate, $createRequest->departureDate);
|
$amountAvailable = $repoAvailability->getAvailabilityForCategoryInPeriod($roomCategory, $arrivalDate, $departureDate);
|
||||||
$selectedAmount = $roomItem->amount;
|
$selectedAmount = $roomItem['amount'];
|
||||||
$selectedGuestAmount = $roomItem->guestNumber;
|
$selectedGuestAmount = $roomItem["guestNumber"];
|
||||||
|
|
||||||
if ($amountAvailable == null || $selectedAmount > $amountAvailable) {
|
if($amountAvailable == null || $selectedAmount > $amountAvailable) {
|
||||||
throw new ApiErrorException(
|
throw new HttpException(400, "No se pueden reservar más habitaciones de las disponibles.");
|
||||||
new ApiErrorMessage(400, ApiErrorMessage::TYPE_LOGICAL_ERROR),
|
|
||||||
'Not enought rooms available for category: ' . $roomItem->roomCategory,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$room->setRoomCategory($roomCategory);
|
$room->setRoomCategory($roomCategory);
|
||||||
@@ -173,35 +125,32 @@ class BookingController extends AbstractController
|
|||||||
$room->setAmount($selectedAmount);
|
$room->setAmount($selectedAmount);
|
||||||
|
|
||||||
//Get Price for period.
|
//Get Price for period.
|
||||||
$priceList = $priceSrv->getFinalPricesForSearch($roomCategory, $createRequest->arrivalDate, $createRequest->departureDate);
|
$priceList = $priceSrv->getFinalPricesForSearch($roomCategory, $arrivalDate, $departureDate);
|
||||||
$priceFound = false;
|
$priceFound = false;
|
||||||
foreach ($priceList as $priceItem) {
|
foreach ($priceList as $key => $value) {
|
||||||
if ($priceItem->guestNumber == $selectedGuestAmount) {
|
if($key == $selectedGuestAmount) {
|
||||||
$room->setRoomPrice($priceItem->price);
|
$room->setRoomPrice($value);
|
||||||
$priceFound = true;
|
$priceFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$priceFound) {
|
if(!$priceFound) {
|
||||||
throw new ApiErrorException(
|
throw new HttpException(500,
|
||||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_LOGICAL_ERROR),
|
"No se puedo obtener el precio para la categoría: " . $roommCategory->getId() . " , guests: " . $selecctedGuestAmount);
|
||||||
'Price not found for category: ' . $roomCategory->getId() . ', guests: ' . $selectedGuestAmount,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$entityManager->persist($room);
|
$entityManager->persist($room);
|
||||||
|
|
||||||
//If booking is nice, let's update current availability.
|
//If booking is nice, let's update current availability.
|
||||||
$repoAvailability->updateAvailabilityForRoomCategoryOnPeriod($roomCategory, $createRequest->arrivalDate, $createRequest->departureDate, $selectedAmount, true); //Update using "diff" mode.
|
$repoAvailability->updateAvailabilityForRoomCategoryOnPeriod($roomCategory, $arrivalDate, $departureDate, $selectedAmount, true); //Update using "diff" mode.
|
||||||
}
|
}
|
||||||
|
|
||||||
$entityManager->flush();
|
|
||||||
$entityManager->commit();
|
$entityManager->commit();
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
if ($entityManager->getConnection()->isTransactionActive()) {
|
$entityManager->rollback();
|
||||||
$entityManager->rollback();
|
|
||||||
}
|
|
||||||
throw $th;
|
throw $th;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,71 +2,50 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\ApiErrorException;
|
|
||||||
use App\Consts\ConfigVariableConsts;
|
|
||||||
use App\DTO\ApiErrorMessage;
|
|
||||||
use App\Entity\ConfigVariable;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use App\Repository\ConfigVariableRepository;
|
use App\Repository\ConfigVariableRepository;
|
||||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
|
||||||
use OpenApi\Attributes as OA;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
|
|
||||||
class ConfigVariableController extends AbstractController
|
class ConfigVariableController extends AbstractController
|
||||||
{
|
{
|
||||||
/**
|
#[Route('/api/config', name: 'app_config_variable')]
|
||||||
* Get All config variables.
|
|
||||||
*/
|
|
||||||
#[Route('/api/config', name: 'app_config_allVariable', methods: ["GET"])]
|
|
||||||
#[OA\Tag(name: "Config")]
|
|
||||||
#[OA\Response(
|
|
||||||
response: 200,
|
|
||||||
description: 'Returns all public config variables.',
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
properties: [
|
|
||||||
new OA\Property(
|
|
||||||
property: "results",
|
|
||||||
type: "array",
|
|
||||||
items: new OA\Items(ref: new Model(type: ConfigVariable::class))
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
public function getAllVariables(ConfigVariableRepository $repo): Response
|
public function getAllVariables(ConfigVariableRepository $repo): Response
|
||||||
{
|
{
|
||||||
$varList = $repo->findAllVariables();
|
$varList = $repo->findAllVariables();
|
||||||
$filteredResults = array_filter($varList, function ($varItem) {
|
|
||||||
return ConfigVariableConsts::IsSectionPublicForApi($varItem->getSection());
|
|
||||||
});
|
|
||||||
|
|
||||||
return $this->json(['results' => array_values($filteredResults)]);
|
return $this->json(['results' => $varList]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/config/var/{key}', name: 'app_config_variable', methods: ["GET"])]
|
#[Route('/api/config/section', name: 'app_config_sections')]
|
||||||
#[OA\Tag(name: "Config")]
|
public function getSections(ConfigVariableRepository $repo): Response
|
||||||
#[OA\Response(
|
{
|
||||||
response: 200,
|
$sectionList = $repo->getAllSections();
|
||||||
description: 'Get config variable by key.',
|
$returnList = [];
|
||||||
content: new OA\JsonContent(
|
foreach ($sectionList as $section) {
|
||||||
type: 'object',
|
$sectionValue = $section["section"];
|
||||||
ref: new Model(type: ConfigVariable::class),
|
$returnList[] = $sectionValue;
|
||||||
)
|
}
|
||||||
)]
|
|
||||||
#[OA\Parameter(
|
return $this->json(['results' => $returnList]);
|
||||||
name: 'key',
|
}
|
||||||
in: 'path',
|
|
||||||
description: 'Variable key. (Capital letters must match)',
|
#[Route('/api/config/section/{section}', name: 'app_config_variableBySection')]
|
||||||
schema: new OA\Schema(type: 'string')
|
public function getVariablesBySection(ConfigVariableRepository $repo, string $section): Response
|
||||||
)]
|
{
|
||||||
|
if(\is_null($section) || $section == '') {
|
||||||
|
return new Response("Invalid section value", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$varList = $repo->findAllBySection($section);
|
||||||
|
|
||||||
|
return $this->json(['results' => $varList]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/config/var/{key}', name: 'app_config_variable')]
|
||||||
public function getByKey(ConfigVariableRepository $repo, string $key): Response
|
public function getByKey(ConfigVariableRepository $repo, string $key): Response
|
||||||
{
|
{
|
||||||
$var = $repo->findVariableByKey($key);
|
$var = $repo->findVariableByKey($key);
|
||||||
if (is_null($var)) {
|
|
||||||
throw new NotFoundHttpException('Config Variable not found!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->json($var);
|
return $this->json($var);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,35 +3,21 @@
|
|||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use App\Repository\RoomCategoryRepository;
|
use App\Repository\RoomCategoryRepository;
|
||||||
use App\Entity\RoomCategory;
|
use App\Entity\RoomCategory;
|
||||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
use App\Entity\RoomCategoryDetails;
|
||||||
|
use App\Entity\RoomCategoryPhoto;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use OpenApi\Attributes as OA;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder;
|
||||||
|
|
||||||
class RoomCategoryController extends AbstractController
|
class RoomCategoryController extends AbstractController
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
#[Route('/api/room/category', name: 'app_room_AllCategories')]
|
||||||
* Get All room categories.
|
|
||||||
*/
|
|
||||||
#[Route('/api/room/category', name: 'app_room_AllCategories', methods: ["GET"])]
|
|
||||||
#[OA\Tag(name: "Rooms")]
|
|
||||||
#[OA\Response(
|
|
||||||
response: 200,
|
|
||||||
description: 'Returns all room categories',
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
properties: [
|
|
||||||
new OA\Property(
|
|
||||||
property: "results",
|
|
||||||
type: "array",
|
|
||||||
items: new OA\Items(ref: new Model(type: RoomCategory::class))
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
public function getAll(RoomCategoryRepository $catRepo): Response
|
public function getAll(RoomCategoryRepository $catRepo): Response
|
||||||
{
|
{
|
||||||
$catList = $catRepo->findAll();
|
$catList = $catRepo->findAll();
|
||||||
@@ -39,24 +25,7 @@ class RoomCategoryController extends AbstractController
|
|||||||
return $this->json(["results" => $catList]);
|
return $this->json(["results" => $catList]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[Route('/api/room/category/{id}', name: 'app_room_category')]
|
||||||
* Get a room category by ID.
|
|
||||||
*/
|
|
||||||
#[Route('/api/room/category/{id}', name: 'app_room_category', methods: ["GET"])]
|
|
||||||
#[OA\Tag(name: "Rooms")]
|
|
||||||
#[OA\Response(
|
|
||||||
response: 200,
|
|
||||||
description: 'Returns a room category',
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
ref: new Model(type: RoomCategory::class),
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[OA\Parameter(
|
|
||||||
name: 'id',
|
|
||||||
in: 'path',
|
|
||||||
description: 'RoomCategory ID',
|
|
||||||
)]
|
|
||||||
public function getById(RoomCategory $category): Response
|
public function getById(RoomCategory $category): Response
|
||||||
{
|
{
|
||||||
return $this->json($category);
|
return $this->json($category);
|
||||||
|
|||||||
@@ -2,189 +2,95 @@
|
|||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\ApiErrorException;
|
use App\Repository\FareTableRepository;
|
||||||
use App\Consts\ConfigVariableConsts;
|
|
||||||
use App\DTO\ApiErrorMessage;
|
|
||||||
use App\Repository\RoomAvailabilityRepository;
|
use App\Repository\RoomAvailabilityRepository;
|
||||||
use App\Repository\RoomCategoryRepository;
|
use App\Repository\RoomCategoryRepository;
|
||||||
use App\Request\SearchPeriodRequest;
|
|
||||||
use App\Response\SearchAvailabilityResponse;
|
|
||||||
use App\Request\SearchPricesForCategoryRequest;
|
|
||||||
use App\Response\SearchPricesResponse;
|
|
||||||
use App\Response\SearchPricesResponsePriceItem;
|
|
||||||
use App\Service\ConfigVariableService;
|
|
||||||
use App\Service\PricesService;
|
use App\Service\PricesService;
|
||||||
use DateTimeInterface;
|
|
||||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use OpenApi\Attributes as OA;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
|
||||||
|
|
||||||
class SearchRoomsController extends AbstractController
|
class SearchRoomsController extends AbstractController
|
||||||
{
|
{
|
||||||
private string $configMaxAllowedNightAmount;
|
#[Route('/api/search/availability', name: 'app_search_availability')]
|
||||||
|
public function getAvailabilityForPeriod(Request $request, RoomAvailabilityRepository $repoAvailability): Response
|
||||||
public function __construct(
|
|
||||||
private ConfigVariableService $configVarService,
|
|
||||||
private LoggerInterface $logger
|
|
||||||
) {
|
|
||||||
$this->configMaxAllowedNightAmount = $configVarService->getVariableFromConst(ConfigVariableConsts::CONFIG_API_NAXNIGHTS)->getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get availability for all rooms on period.
|
|
||||||
* For each room, the minimal availability will be returned.
|
|
||||||
*/
|
|
||||||
#[Route('/api/search/availability', name: 'app_search_availability', methods: ["POST"], format: "json")]
|
|
||||||
#[OA\Tag(name: "Searchs")]
|
|
||||||
#[OA\Response(
|
|
||||||
response: 200,
|
|
||||||
description: 'Each room available',
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
properties: [
|
|
||||||
new OA\Property(
|
|
||||||
property: "results",
|
|
||||||
type: "array",
|
|
||||||
items: new OA\Items(ref: new Model(type: SearchAvailabilityResponse::class))
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[OA\RequestBody(
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
ref: new Model(type: SearchPeriodRequest::class),
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
public function getAvailabilityForPeriod(
|
|
||||||
RoomAvailabilityRepository $repoAvailability,
|
|
||||||
#[MapRequestPayload()] SearchPeriodRequest $searchRequest
|
|
||||||
): JsonResponse {
|
|
||||||
|
|
||||||
$datesValid = $this->areRequestDatesValid($searchRequest->startDate, $searchRequest->endDate);
|
|
||||||
if ($datesValid != null) {
|
|
||||||
throw $datesValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rs = $repoAvailability->getAvailabilityForPeriod($searchRequest->startDate, $searchRequest->endDate);
|
|
||||||
|
|
||||||
return $this->json(["results" => $rs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the price of each room on each day of a period.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#[Route('/api/search/prices', name: 'app_search_prices', methods: ["POST"], format: "json")]
|
|
||||||
#[OA\Tag(name: "Searchs")]
|
|
||||||
#[OA\RequestBody(
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
ref: new Model(type: SearchPeriodRequest::class),
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[OA\Response(
|
|
||||||
response: 200,
|
|
||||||
description: 'Total Prices for the period of each room',
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
properties: [
|
|
||||||
new OA\Property(
|
|
||||||
property: "results",
|
|
||||||
type: "array",
|
|
||||||
items: new OA\Items(ref: new Model(type: SearchPricesResponse::class))
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
public function getPricesForPeriod(
|
|
||||||
PricesService $priceService,
|
|
||||||
#[MapRequestPayload()] SearchPeriodRequest $searchRequest
|
|
||||||
): JsonResponse {
|
|
||||||
|
|
||||||
$datesValid = $this->areRequestDatesValid($searchRequest->startDate, $searchRequest->endDate);
|
|
||||||
if ($datesValid != null) {
|
|
||||||
throw $datesValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rs = $priceService->getRoomSearch($searchRequest->startDate, $searchRequest->endDate);
|
|
||||||
|
|
||||||
return $this->json(["results" => $rs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get room category price for given period.
|
|
||||||
* Returns price for a room category.
|
|
||||||
* This price is the total for all the selected days by guest number.
|
|
||||||
*/
|
|
||||||
#[Route('/api/search/pricesCategory', name: 'app_search_pricesForCategory', methods: ["POST"], format: "json")]
|
|
||||||
#[OA\Tag(name: "Searchs")]
|
|
||||||
#[OA\Response(
|
|
||||||
response: 200,
|
|
||||||
description: 'Room price by guest number',
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
properties: [
|
|
||||||
new OA\Property(
|
|
||||||
property: "results",
|
|
||||||
type: "array",
|
|
||||||
items: new OA\Items(ref: new Model(type: SearchPricesResponsePriceItem::class))
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[OA\RequestBody(
|
|
||||||
content: new OA\JsonContent(
|
|
||||||
type: 'object',
|
|
||||||
ref: new Model(type: SearchPricesForCategoryRequest::class),
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
public function getPricesForCategoryOnPeriod(
|
|
||||||
RoomCategoryRepository $repoCat,
|
|
||||||
PricesService $priceService,
|
|
||||||
#[MapRequestPayload()] SearchPricesForCategoryRequest $searchRequest
|
|
||||||
): JsonResponse {
|
|
||||||
|
|
||||||
$datesValid = $this->areRequestDatesValid($searchRequest->startDate, $searchRequest->endDate);
|
|
||||||
if ($datesValid != null) {
|
|
||||||
throw $datesValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
$roomCategory = $repoCat->find($searchRequest->roomCategory);
|
|
||||||
if ($roomCategory === null) {
|
|
||||||
throw new ApiErrorException(
|
|
||||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_VALIDATION_ERROR),
|
|
||||||
'Invalid RoomCategory: ' . $searchRequest->roomCategory,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$rs = $priceService->getFinalPricesForSearch($roomCategory, $searchRequest->startDate, $searchRequest->endDate);
|
|
||||||
|
|
||||||
return $this->json(["results" => $rs]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function areRequestDatesValid(DateTimeInterface $dateStart, DateTimeInterface $dateEnd): ApiErrorException|null
|
|
||||||
{
|
{
|
||||||
if ($dateStart >= $dateEnd) {
|
$payload = $request->toArray();
|
||||||
return new ApiErrorException(
|
if (\is_null($payload)) {
|
||||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_INVALID_DATE),
|
throw new \Exception('Invalid Payload', 400);
|
||||||
'startDate cant be before endDate',
|
}
|
||||||
);
|
$filter = $payload["filter"];
|
||||||
|
|
||||||
|
$startDate = new \DateTime($filter["startDate"]);
|
||||||
|
$endDate = new \DateTime($filter["endDate"]);
|
||||||
|
|
||||||
|
$rs = $repoAvailability->getAvailabilityForPeriod($startDate, $endDate);
|
||||||
|
|
||||||
|
return $this->json(["results" => $rs]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/search/availabilityDetails', name: 'app_search_availabilityDetails')]
|
||||||
|
public function getAvailabilityForPeriodDetails(Request $request, RoomAvailabilityRepository $repoAvailability): Response
|
||||||
|
{
|
||||||
|
$payload = $request->toArray();
|
||||||
|
if (\is_null($payload)) {
|
||||||
|
throw new \Exception('Invalid Payload', 400);
|
||||||
|
}
|
||||||
|
$filter = $payload["filter"];
|
||||||
|
|
||||||
|
$startDate = new \DateTime($filter["startDate"]);
|
||||||
|
$endDate = new \DateTime($filter["endDate"]);
|
||||||
|
|
||||||
|
$rs = $repoAvailability->getAvailabilityForPeriodDetails($startDate, $endDate);
|
||||||
|
|
||||||
|
return $this->json($rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/search/fares', name: 'app_search_faresForCategory')]
|
||||||
|
public function getFaresForCategoryOnPeriod(Request $request, RoomCategoryRepository $repoCat, PricesService $priceService): Response
|
||||||
|
{
|
||||||
|
$payload = $request->toArray();
|
||||||
|
if (\is_null($payload)) {
|
||||||
|
throw new \Exception('Invalid Payload', 400);
|
||||||
|
}
|
||||||
|
$filter = $payload["filter"];
|
||||||
|
|
||||||
|
$catId = $filter["roomCategory"];
|
||||||
|
$roomCategory = $repoCat->find($catId);
|
||||||
|
if ($roomCategory === null) {
|
||||||
|
throw new \Exception('Invalid Category', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$daysDiff = $dateStart->diff($dateEnd)->days;
|
$startDate = new \DateTime($filter["startDate"]);
|
||||||
if ($daysDiff > $this->configMaxAllowedNightAmount) {
|
$endDate = new \DateTime($filter["endDate"]);
|
||||||
return new ApiErrorException(
|
|
||||||
new ApiErrorMessage(400, ApiErrorMessage::TYPE_API_LIMIT),
|
$daysDiff = $startDate->diff($endDate)->days;
|
||||||
'Query exceeded max allowed night amount',
|
if (!$daysDiff || $startDate > $endDate) {
|
||||||
);
|
throw new \Exception('Invalid time interval', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
$fareList = $priceService->getFinalPricesForSearch($roomCategory, $startDate, $endDate);
|
||||||
|
$rs = [];
|
||||||
|
foreach ($fareList as $key => $value) {
|
||||||
|
$rs[] = [ "guestNumber" => $key, "price" => $value];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json(["results" => $rs]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/search/debug', name: 'app_search_faresDebug')]
|
||||||
|
public function faresDebug(Request $request, PricesService $priceService, FareTableRepository $repoFareTable, RoomCategoryRepository $repoCat): Response
|
||||||
|
{
|
||||||
|
$fareTable[] = $repoFareTable->find(1);
|
||||||
|
$fareTable[] = $repoFareTable->find(2);
|
||||||
|
$cat = $repoCat->find(4);
|
||||||
|
|
||||||
|
$startDate = new \DateTime("2024-04-27");
|
||||||
|
$endDate = new \DateTime("2024-05-02");
|
||||||
|
|
||||||
|
$rs = $priceService->getFinalPricesForSearch($cat, $startDate, $endDate);
|
||||||
|
|
||||||
|
return $this->json($rs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
|
||||||
class SecurityController extends AbstractController
|
class SecurityController extends AbstractController
|
||||||
{
|
{
|
||||||
@@ -29,4 +34,25 @@ class SecurityController extends AbstractController
|
|||||||
{
|
{
|
||||||
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route(path: '/addAdmin', name: 'app_security_addAdmin')]
|
||||||
|
public function addAdmin(UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$user->setUsername("admin");
|
||||||
|
$user->setRoles(["ROLE_USER", "ROLE_ADMIN"]);
|
||||||
|
$plaintextPassword = "admin";
|
||||||
|
|
||||||
|
// hash the password (based on the security.yaml config for the $user class)
|
||||||
|
$hashedPassword = $passwordHasher->hashPassword(
|
||||||
|
$user,
|
||||||
|
$plaintextPassword
|
||||||
|
);
|
||||||
|
$user->setPassword($hashedPassword);
|
||||||
|
|
||||||
|
$entityManager->persist($user);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return new Response("Created user - Admin");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\DTO;
|
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
class ApiErrorMessage
|
|
||||||
{
|
|
||||||
const TYPE_VALIDATION_ERROR = 'validation_error';
|
|
||||||
const TYPE_INVALID_REQUEST_BODY_FORMAT = 'invalid_body_format';
|
|
||||||
const TYPE_LOGICAL_ERROR = 'logical_error';
|
|
||||||
const TYPE_SERVER_ERROR = 'server_error';
|
|
||||||
const TYPE_INVALID_DATE = 'invalid_date';
|
|
||||||
const TYPE_API_LIMIT = 'api_limit';
|
|
||||||
|
|
||||||
private static $titles = array(
|
|
||||||
self::TYPE_VALIDATION_ERROR => 'There was a validation error',
|
|
||||||
self::TYPE_INVALID_REQUEST_BODY_FORMAT => 'Invalid JSON format sent',
|
|
||||||
self::TYPE_LOGICAL_ERROR => 'Invalid request due to a logic error',
|
|
||||||
self::TYPE_SERVER_ERROR => 'Error on server side',
|
|
||||||
self::TYPE_INVALID_DATE => 'Invalid date on request',
|
|
||||||
self::TYPE_API_LIMIT => 'Reached API Limit'
|
|
||||||
);
|
|
||||||
|
|
||||||
private int $statusCode;
|
|
||||||
private string $type;
|
|
||||||
private string $title;
|
|
||||||
private array $extraData = array();
|
|
||||||
|
|
||||||
public function __construct($statusCode, $type = null)
|
|
||||||
{
|
|
||||||
if ($type === null) {
|
|
||||||
// no type? The default is about:blank and the title should
|
|
||||||
// be the standard status code message
|
|
||||||
$type = 'about:blank';
|
|
||||||
$title = isset(Response::$statusTexts[$statusCode]) ?
|
|
||||||
Response::$statusTexts[$statusCode] : 'Unknown status code :(';
|
|
||||||
} else {
|
|
||||||
if (!isset(self::$titles[$type])) {
|
|
||||||
throw new \InvalidArgumentException('No title for type ' . $type);
|
|
||||||
}
|
|
||||||
$title = self::$titles[$type];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->statusCode = $statusCode;
|
|
||||||
$this->type = $type;
|
|
||||||
$this->title = $title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStatusCode(): int
|
|
||||||
{
|
|
||||||
return $this->statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTitle(): string
|
|
||||||
{
|
|
||||||
return $this->title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setExtraData($name, $value)
|
|
||||||
{
|
|
||||||
$this->extraData[$name] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDetails($value)
|
|
||||||
{
|
|
||||||
$this->extraData['details'] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
return array_merge(
|
|
||||||
$this->extraData,
|
|
||||||
array(
|
|
||||||
'status' => $this->statusCode,
|
|
||||||
'type' => $this->type,
|
|
||||||
'title' => $this->title,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\DTO;
|
|
||||||
|
|
||||||
class AppSetupConfig
|
|
||||||
{
|
|
||||||
protected string $adminUser;
|
|
||||||
protected string $adminPass;
|
|
||||||
|
|
||||||
protected bool $genExampleData;
|
|
||||||
|
|
||||||
public function getAdminUser(): string
|
|
||||||
{
|
|
||||||
return $this->adminUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAdminUser(string $adminUser): void
|
|
||||||
{
|
|
||||||
$this->adminUser = $adminUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAdminPass(): string
|
|
||||||
{
|
|
||||||
return $this->adminPass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAdminPass(string $adminPass): void
|
|
||||||
{
|
|
||||||
$this->adminPass = $adminPass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGenExampleData(): bool
|
|
||||||
{
|
|
||||||
return $this->genExampleData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setGenExampleData(bool $genExampleData): void
|
|
||||||
{
|
|
||||||
$this->genExampleData = $genExampleData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\DTO;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
|
|
||||||
class ExampleDataConfig
|
|
||||||
{
|
|
||||||
protected DateTime $startDate;
|
|
||||||
protected DateTime $endDate;
|
|
||||||
|
|
||||||
protected bool $genRoomCategories;
|
|
||||||
|
|
||||||
protected bool $genFaretables;
|
|
||||||
protected bool $genAvailability;
|
|
||||||
protected string $genMode;
|
|
||||||
|
|
||||||
public const GENMODE_EXAMPLE = "EXAMPLE";
|
|
||||||
public const GENMODE_RANDOM = "RANDOM";
|
|
||||||
|
|
||||||
public function getStartDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->startDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setStartDate(DateTime $startDate): void
|
|
||||||
{
|
|
||||||
$this->startDate = $startDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEndDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->endDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEndDate(DateTime $endDate): void
|
|
||||||
{
|
|
||||||
$this->endDate = $endDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGenRoomCategories(): bool
|
|
||||||
{
|
|
||||||
return $this->genRoomCategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setGenRoomCategories(bool $genRoomCategories): void
|
|
||||||
{
|
|
||||||
$this->genRoomCategories = $genRoomCategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGenFaretables(): bool
|
|
||||||
{
|
|
||||||
return $this->genFaretables;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setGenFaretables(bool $genFaretables): void
|
|
||||||
{
|
|
||||||
$this->genFaretables = $genFaretables;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGenAvailability(): bool
|
|
||||||
{
|
|
||||||
return $this->genAvailability;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setGenAvailability(bool $genAvailability): void
|
|
||||||
{
|
|
||||||
$this->genAvailability = $genAvailability;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGenMode(): string
|
|
||||||
{
|
|
||||||
return $this->genMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setGenMode(string $genMode): void
|
|
||||||
{
|
|
||||||
$this->genMode = $genMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\DTO;
|
|
||||||
|
|
||||||
use DateTimeImmutable;
|
|
||||||
|
|
||||||
class SearchFilterByDate
|
|
||||||
{
|
|
||||||
protected DateTimeImmutable $dtFrom;
|
|
||||||
protected DateTimeImmutable $dtTo;
|
|
||||||
|
|
||||||
public function getDtFrom(): DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->dtFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDtFrom(DateTimeImmutable $dt)
|
|
||||||
{
|
|
||||||
$this->dtFrom = $dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDtTo(): DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->dtTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDtTo(DateTimeImmutable $dt)
|
|
||||||
{
|
|
||||||
$this->dtTo = $dt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use App\Repository\BedTypeRepository;
|
|
||||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BedTypeRepository::class)]
|
|
||||||
class BedType
|
|
||||||
{
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\GeneratedValue]
|
|
||||||
#[ORM\Column]
|
|
||||||
private ?int $id = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
|
||||||
private ?string $name = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
|
||||||
private ?string $iconUrl = null;
|
|
||||||
|
|
||||||
public function getId(): ?int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): ?string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setName(string $name): static
|
|
||||||
{
|
|
||||||
$this->name = $name;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIconUrl(): ?string
|
|
||||||
{
|
|
||||||
return $this->iconUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setIconUrl(?string $iconUrl): static
|
|
||||||
{
|
|
||||||
$this->iconUrl = $iconUrl;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString(): string {
|
|
||||||
return $this->getName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+20
-22
@@ -7,7 +7,6 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BookingRepository::class)]
|
#[ORM\Entity(repositoryClass: BookingRepository::class)]
|
||||||
class Booking
|
class Booking
|
||||||
@@ -15,30 +14,29 @@ class Booking
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Ignore]
|
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 20)]
|
#[ORM\Column(type: Types::DATE_MUTABLE)]
|
||||||
private ?string $refNumber = null;
|
private ?\DateTimeInterface $ArrivalDate = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATE_IMMUTABLE)]
|
#[ORM\Column(type : Types::DATE_MUTABLE)]
|
||||||
private ?\DateTimeInterface $arrivalDate = null;
|
private ?\DateTimeInterface $DepartureDate = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATE_IMMUTABLE)]
|
#[ORM\Column(type : Types::DATETIMETZ_MUTABLE)]
|
||||||
private ?\DateTimeInterface $departureDate = null;
|
private ?\DateTimeInterface $BookingTime = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)]
|
#[ORM\OneToOne(mappedBy : 'Booking', cascade: ['persist', 'remove'])]
|
||||||
private ?\DateTimeInterface $bookingTime = null;
|
|
||||||
|
|
||||||
#[ORM\OneToOne(mappedBy: 'booking', cascade: ['persist', 'remove'])]
|
|
||||||
private ?BookingCustomer $bookingCustomer = null;
|
private ?BookingCustomer $bookingCustomer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, BookingRoom>
|
* @var Collection<int, BookingRoom>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(targetEntity: BookingRoom::class, mappedBy: 'booking', orphanRemoval: true, cascade: ['persist'])]
|
#[ORM\OneToMany(targetEntity: BookingRoom::class, mappedBy: 'Booking', orphanRemoval: true, cascade: ['persist'])]
|
||||||
private Collection $bookingRooms;
|
private Collection $bookingRooms;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 20)]
|
||||||
|
private ?string $refNumber = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->bookingRooms = new ArrayCollection();
|
$this->bookingRooms = new ArrayCollection();
|
||||||
@@ -51,36 +49,36 @@ class Booking
|
|||||||
|
|
||||||
public function getArrivalDate(): ?\DateTimeInterface
|
public function getArrivalDate(): ?\DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->arrivalDate;
|
return $this->ArrivalDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setArrivalDate(\DateTimeInterface $arrivalDate): static
|
public function setArrivalDate(\DateTimeInterface $ArrivalDate): static
|
||||||
{
|
{
|
||||||
$this->arrivalDate = $arrivalDate;
|
$this->ArrivalDate = $ArrivalDate;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDepartureDate(): ?\DateTimeInterface
|
public function getDepartureDate(): ?\DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->departureDate;
|
return $this->DepartureDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDepartureDate(\DateTimeInterface $departureDate): static
|
public function setDepartureDate(\DateTimeInterface $DepartureDate): static
|
||||||
{
|
{
|
||||||
$this->departureDate = $departureDate;
|
$this->DepartureDate = $DepartureDate;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBookingTime(): ?\DateTimeInterface
|
public function getBookingTime(): ?\DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->bookingTime;
|
return $this->BookingTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setBookingTime(\DateTimeInterface $bookingTime): static
|
public function setBookingTime(\DateTimeInterface $BookingTime): static
|
||||||
{
|
{
|
||||||
$this->bookingTime = $bookingTime;
|
$this->BookingTime = $BookingTime;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace App\Entity;
|
|||||||
use App\Repository\BookingCustomerRepository;
|
use App\Repository\BookingCustomerRepository;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
use Symfony\Component\Serializer\Annotation\Ignore;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BookingCustomerRepository::class)]
|
#[ORM\Entity(repositoryClass: BookingCustomerRepository::class)]
|
||||||
class BookingCustomer
|
class BookingCustomer
|
||||||
@@ -13,92 +12,88 @@ class BookingCustomer
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Ignore]
|
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\OneToOne(inversedBy: 'bookingCustomer', cascade: ['persist', 'remove'])]
|
#[ORM\OneToOne(inversedBy: 'bookingCustomer', cascade: ['persist', 'remove'])]
|
||||||
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
private ?Booking $booking = null;
|
private ?Booking $Booking = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 64)]
|
#[ORM\Column(length: 64)]
|
||||||
#[Assert\NotBlank]
|
private ?string $Name = null;
|
||||||
private ?string $name = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
#[Assert\NotBlank]
|
private ?string $Surname = null;
|
||||||
private ?string $surname = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
#[Assert\NotBlank]
|
private ?string $Email = null;
|
||||||
#[Assert\Email]
|
|
||||||
private ?string $email = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
#[Assert\NotBlank]
|
private ?string $PhoneNumber = null;
|
||||||
private ?string $phoneNumber = null;
|
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Ignore]
|
||||||
public function getBooking(): ?Booking
|
public function getBooking(): ?Booking
|
||||||
{
|
{
|
||||||
return $this->booking;
|
return $this->Booking;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setBooking(Booking $booking): static
|
#[Ignore]
|
||||||
|
public function setBooking(Booking $Booking): static
|
||||||
{
|
{
|
||||||
$this->booking = $booking;
|
$this->Booking = $Booking;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName(): ?string
|
public function getName(): ?string
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setName(string $name): static
|
public function setName(string $Name): static
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->Name = $Name;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSurname(): ?string
|
public function getSurname(): ?string
|
||||||
{
|
{
|
||||||
return $this->surname;
|
return $this->Surname;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setSurname(string $surname): static
|
public function setSurname(string $Surname): static
|
||||||
{
|
{
|
||||||
$this->surname = $surname;
|
$this->Surname = $Surname;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmail(): ?string
|
public function getEmail(): ?string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->Email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setEmail(string $email): static
|
public function setEmail(string $Email): static
|
||||||
{
|
{
|
||||||
$this->email = $email;
|
$this->Email = $Email;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPhoneNumber(): ?string
|
public function getPhoneNumber(): ?string
|
||||||
{
|
{
|
||||||
return $this->phoneNumber;
|
return $this->PhoneNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPhoneNumber(string $phoneNumber): static
|
public function setPhoneNumber(string $PhoneNumber): static
|
||||||
{
|
{
|
||||||
$this->phoneNumber = $phoneNumber;
|
$this->PhoneNumber = $PhoneNumber;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-21
@@ -12,53 +12,56 @@ class BookingRoom
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Ignore]
|
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'bookingRooms')]
|
#[ORM\ManyToOne(inversedBy: 'bookingRooms')]
|
||||||
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
private ?Booking $booking = null;
|
private ?Booking $Booking = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
private ?RoomCategory $roomCategory = null;
|
private ?RoomCategory $RoomCategory = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?int $guestNumber = null;
|
private ?int $GuestNumber = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?int $roomPrice = null;
|
private ?int $RoomPrice = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?int $amount = null;
|
private ?int $Amount = null;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Ignore]
|
||||||
public function getBooking(): ?Booking
|
public function getBooking(): ?Booking
|
||||||
{
|
{
|
||||||
return $this->booking;
|
return $this->Booking;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setBooking(?Booking $booking): static
|
#[Ignore]
|
||||||
|
public function setBooking(?Booking $Booking): static
|
||||||
{
|
{
|
||||||
$this->booking = $booking;
|
$this->Booking = $Booking;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Ignore]
|
||||||
public function getRoomCategory(): ?RoomCategory
|
public function getRoomCategory(): ?RoomCategory
|
||||||
{
|
{
|
||||||
return $this->roomCategory;
|
return $this->RoomCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setRoomCategory(?RoomCategory $roomCategory): static
|
#[Ignore]
|
||||||
|
public function setRoomCategory(?RoomCategory $RoomCategory): static
|
||||||
{
|
{
|
||||||
$this->roomCategory = $roomCategory;
|
$this->RoomCategory = $RoomCategory;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -70,36 +73,36 @@ class BookingRoom
|
|||||||
|
|
||||||
public function getGuestNumber(): ?int
|
public function getGuestNumber(): ?int
|
||||||
{
|
{
|
||||||
return $this->guestNumber;
|
return $this->GuestNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setGuestNumber(int $guestNumber): static
|
public function setGuestNumber(int $GuestNumber): static
|
||||||
{
|
{
|
||||||
$this->guestNumber = $guestNumber;
|
$this->GuestNumber = $GuestNumber;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRoomPrice(): ?int
|
public function getRoomPrice(): ?int
|
||||||
{
|
{
|
||||||
return $this->roomPrice;
|
return $this->RoomPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setRoomPrice(int $roomPrice): static
|
public function setRoomPrice(int $RoomPrice): static
|
||||||
{
|
{
|
||||||
$this->roomPrice = $roomPrice;
|
$this->RoomPrice = $RoomPrice;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAmount(): ?int
|
public function getAmount(): ?int
|
||||||
{
|
{
|
||||||
return $this->amount;
|
return $this->Amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAmount(int $amount): static
|
public function setAmount(int $Amount): static
|
||||||
{
|
{
|
||||||
$this->amount = $amount;
|
$this->Amount = $Amount;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,31 @@
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use App\Repository\ConfigVariableRepository;
|
use App\Repository\ConfigVariableRepository;
|
||||||
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(strategy: "NONE")]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column(length: 32, unique: true, nullable: false)]
|
#[ORM\Column]
|
||||||
private string $key = "";
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: false)]
|
#[ORM\Column(length: 32, unique: true)]
|
||||||
|
private ?string $key = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
private ?string $value = null;
|
private ?string $value = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'string', enumType: ConfigVariableSectionEnum::class, nullable: false)]
|
#[ORM\Column(length: 64, nullable: true)]
|
||||||
private ConfigVariableSectionEnum $section = ConfigVariableSectionEnum::Internal;
|
private ?string $section = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
public function getKey(): string
|
public function getKey(): ?string
|
||||||
{
|
{
|
||||||
return $this->key;
|
return $this->key;
|
||||||
}
|
}
|
||||||
@@ -45,20 +51,15 @@ class ConfigVariable
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSection(): ConfigVariableSectionEnum
|
public function getSection(): ?string
|
||||||
{
|
{
|
||||||
return $this->section;
|
return $this->section;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setSection(ConfigVariableSectionEnum $section): static
|
public function setSection(?string $section): static
|
||||||
{
|
{
|
||||||
$this->section = $section;
|
$this->section = $section;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function __toString(): string
|
|
||||||
{
|
|
||||||
return $this->getKey();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ class FareTable
|
|||||||
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
||||||
private ?RoomCategory $roomCategory = null;
|
private ?RoomCategory $roomCategory = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: false)]
|
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
||||||
private ?\DateTimeInterface $startDate = null;
|
private ?\DateTimeInterface $startDate = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
|
#[ORM\Column(type : Types::DATETIME_MUTABLE, nullable: true)]
|
||||||
private ?\DateTimeInterface $endDate = null;
|
private ?\DateTimeInterface $endDate = null;
|
||||||
|
|
||||||
#[ORM\Column(length : 255, nullable: true)]
|
#[ORM\Column(length : 255, nullable: true)]
|
||||||
@@ -125,14 +125,10 @@ class FareTable
|
|||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
$str = $this->id;
|
|
||||||
if (!\is_null($this->comment)) {
|
if (!\is_null($this->comment)) {
|
||||||
$str = $this->comment;
|
return $this->comment;
|
||||||
}
|
}
|
||||||
$str . "(" . $this->roomCategory . ")";
|
return $this->id;
|
||||||
if (!\is_null($this->roomCategory)) {
|
|
||||||
}
|
|
||||||
return $str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,7 +160,7 @@ class FareTable
|
|||||||
/**
|
/**
|
||||||
* Obtiene las tarifas que aplican al GuestNumber indicado.
|
* Obtiene las tarifas que aplican al GuestNumber indicado.
|
||||||
* @param int $guestNumber GuestNumber para búsqueda.
|
* @param int $guestNumber GuestNumber para búsqueda.
|
||||||
* @return Collection<RoomFare> Lista de tarifas.
|
* @return RoomFare[] Lista de tarifas.
|
||||||
*/
|
*/
|
||||||
public function getRoomFaresByGuestNumber(int $guestNumber)
|
public function getRoomFaresByGuestNumber(int $guestNumber)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use App\Repository\RoomAvailabilityRepository;
|
use App\Repository\RoomAvailabilityRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: RoomAvailabilityRepository::class)]
|
#[ORM\Entity(repositoryClass: RoomAvailabilityRepository::class)]
|
||||||
@@ -18,12 +19,12 @@ class RoomAvailability
|
|||||||
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
||||||
private ?RoomCategory $roomCategory = null;
|
private ?RoomCategory $roomCategory = null;
|
||||||
|
|
||||||
#[ORM\Column(nullable: false)]
|
#[ORM\Column]
|
||||||
private ?int $numAvailable = null;
|
private ?int $numAvailable = null;
|
||||||
|
|
||||||
public function getDay(): ?\DateTimeImmutable
|
public function getDay(): ?\DateTimeInterface
|
||||||
{
|
{
|
||||||
return new \DateTimeImmutable($this->day);
|
return new \DateTime($this->day);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDay(\DateTimeInterface $day): static
|
public function setDay(\DateTimeInterface $day): static
|
||||||
|
|||||||
+36
-36
@@ -22,11 +22,11 @@ class RoomCategory
|
|||||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
private ?string $description = null;
|
private ?string $description = null;
|
||||||
|
|
||||||
#[ORM\Column(nullable: false)]
|
#[ORM\Column(length: 64, nullable: true)]
|
||||||
private ?int $maxGuestNum = null;
|
private ?string $bedType = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::SMALLINT, nullable: true)]
|
#[ORM\Column]
|
||||||
private ?int $orderHomePage = null;
|
private ?int $maxGuestNum = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, RoomCategoryPhoto>
|
* @var Collection<int, RoomCategoryPhoto>
|
||||||
@@ -35,15 +35,15 @@ class RoomCategory
|
|||||||
private Collection $roomCategoryPhotos;
|
private Collection $roomCategoryPhotos;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, RoomCategoryBedType>
|
* @var Collection<int, RoomCategoryDetails>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(targetEntity: RoomCategoryBedType::class, mappedBy: 'roomCategory', orphanRemoval: true, cascade: ['persist'])]
|
#[ORM\OneToMany(targetEntity: RoomCategoryDetails::class, mappedBy: 'roomCategory', orphanRemoval: true, cascade: ['persist'])]
|
||||||
private Collection $roomCategoryBedTypes;
|
private Collection $roomCategoryDetails;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->roomCategoryPhotos = new ArrayCollection();
|
$this->roomCategoryPhotos = new ArrayCollection();
|
||||||
$this->roomCategoryBedTypes = new ArrayCollection();
|
$this->roomCategoryDetails = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@@ -75,6 +75,18 @@ class RoomCategory
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBedType(): ?string
|
||||||
|
{
|
||||||
|
return $this->bedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBedType(?string $bedType): static
|
||||||
|
{
|
||||||
|
$this->bedType = $bedType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getMaxGuestNum(): ?int
|
public function getMaxGuestNum(): ?int
|
||||||
{
|
{
|
||||||
return $this->maxGuestNum;
|
return $this->maxGuestNum;
|
||||||
@@ -87,18 +99,6 @@ class RoomCategory
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOrderHomePage(): ?int
|
|
||||||
{
|
|
||||||
return $this->orderHomePage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setOrderHomePage(?int $orderHomePage): static
|
|
||||||
{
|
|
||||||
$this->orderHomePage = $orderHomePage;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, RoomCategoryPhoto>
|
* @return Collection<int, RoomCategoryPhoto>
|
||||||
*/
|
*/
|
||||||
@@ -129,38 +129,38 @@ class RoomCategory
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, RoomCategoryBedType>
|
* @return Collection<int, RoomCategoryDetails>
|
||||||
*/
|
*/
|
||||||
public function getRoomCategoryBedTypes(): Collection
|
public function getRoomCategoryDetails(): Collection
|
||||||
{
|
{
|
||||||
return $this->roomCategoryBedTypes;
|
return $this->roomCategoryDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addRoomCategoryBedType(RoomCategoryBedType $roomCategoryBedType): static
|
public function addRoomCategoryDetail(RoomCategoryDetails $roomCategoryDetail): static
|
||||||
{
|
{
|
||||||
if (!$this->roomCategoryBedTypes->contains($roomCategoryBedType)) {
|
if (!$this->roomCategoryDetails->contains($roomCategoryDetail)) {
|
||||||
$this->roomCategoryBedTypes->add($roomCategoryBedType);
|
$this->roomCategoryDetails->add($roomCategoryDetail);
|
||||||
$roomCategoryBedType->setRoomCategory($this);
|
$roomCategoryDetail->setRoomCategory($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removeRoomCategoryBedType(RoomCategoryBedType $roomCategoryBedType): static
|
public function removeRoomCategoryDetail(RoomCategoryDetails $roomCategoryDetail): static
|
||||||
{
|
{
|
||||||
if ($this->roomCategoryBedTypes->removeElement($roomCategoryBedType)) {
|
if ($this->roomCategoryDetails->removeElement($roomCategoryDetail)) {
|
||||||
// set the owning side to null (unless already changed)
|
// set the owning side to null (unless already changed)
|
||||||
if ($roomCategoryBedType->getRoomCategory() === $this) {
|
if ($roomCategoryDetail->getRoomCategory() === $this) {
|
||||||
$roomCategoryBedType->setRoomCategory(null);
|
$roomCategoryDetail->setRoomCategory(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use App\Repository\RoomCategoryBedTypeRepository;
|
|
||||||
use Symfony\Component\Serializer\Annotation\Ignore;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: RoomCategoryBedTypeRepository::class)]
|
|
||||||
class RoomCategoryBedType
|
|
||||||
{
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'roomCategoryBedTypes')]
|
|
||||||
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
|
|
||||||
#[Ignore]
|
|
||||||
private ?RoomCategory $roomCategory = null;
|
|
||||||
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
|
||||||
#[Ignore]
|
|
||||||
private ?BedType $bedType = null;
|
|
||||||
|
|
||||||
#[ORM\Column]
|
|
||||||
private ?int $amount = null;
|
|
||||||
|
|
||||||
// public function __construct(RoomCategory $roomCat, BedType $bedType) {
|
|
||||||
// $this->roomCategory = $roomCat;
|
|
||||||
// $this->bedType = $bedType;
|
|
||||||
// }
|
|
||||||
|
|
||||||
public function getAmount(): ?int
|
|
||||||
{
|
|
||||||
return $this->amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAmount(int $amount): static
|
|
||||||
{
|
|
||||||
$this->amount = $amount;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRoomCategory(): ?RoomCategory
|
|
||||||
{
|
|
||||||
return $this->roomCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setRoomCategory(?RoomCategory $roomCategory): static
|
|
||||||
{
|
|
||||||
$this->roomCategory = $roomCategory;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBedType(): ?BedType
|
|
||||||
{
|
|
||||||
return $this->bedType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBedType(?BedType $bedType): static
|
|
||||||
{
|
|
||||||
$this->bedType = $bedType;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBedTypeName(): string {
|
|
||||||
return $this->getBedType()->getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBedTypeIcon(): string {
|
|
||||||
return $this->getBedType()->getIconUrl();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user