| @@ -0,0 +1,10 @@ | |||||
| name: phpmyadmin | |||||
| repository: ddev/ddev-phpmyadmin | |||||
| version: v0.3.8 | |||||
| install_date: "2024-10-01T15:41:59+02:00" | |||||
| project_files: | |||||
| - docker-compose.phpmyadmin.yaml | |||||
| - docker-compose.phpmyadmin_norouter.yaml | |||||
| - commands/host/phpmyadmin | |||||
| global_files: [] | |||||
| removal_actions: [] | |||||
| @@ -0,0 +1,14 @@ | |||||
| #!/bin/bash | |||||
| ## #ddev-generated: If you want to edit and own this file, remove this line. | |||||
| ## Description: Launch a browser with PhpMyAdmin | |||||
| ## Usage: phpmyadmin | |||||
| ## Example: "ddev phpmyadmin" | |||||
| DDEV_PHPMYADMIN_PORT=8036 | |||||
| DDEV_PHPMYADMIN_HTTPS_PORT=8037 | |||||
| if [ ${DDEV_PRIMARY_URL%://*} = "http" ] || [ -n "${GITPOD_WORKSPACE_ID:-}" ] || [ "${CODESPACES:-}" = "true" ]; then | |||||
| ddev launch :$DDEV_PHPMYADMIN_PORT | |||||
| else | |||||
| ddev launch :$DDEV_PHPMYADMIN_HTTPS_PORT | |||||
| fi | |||||
| @@ -0,0 +1,281 @@ | |||||
| name: imaq | |||||
| type: php | |||||
| docroot: public | |||||
| php_version: "8.2" | |||||
| webserver_type: nginx-fpm | |||||
| xdebug_enabled: false | |||||
| additional_hostnames: [] | |||||
| additional_fqdns: [] | |||||
| database: | |||||
| type: mariadb | |||||
| version: "10.11" | |||||
| use_dns_when_possible: true | |||||
| composer_version: "2" | |||||
| web_environment: [] | |||||
| corepack_enable: false | |||||
| router_http_port: 8082 | |||||
| router_https_port: 8454 | |||||
| # Key features of DDEV's config.yaml: | |||||
| # name: <projectname> # Name of the project, automatically provides | |||||
| # http://projectname.ddev.site and https://projectname.ddev.site | |||||
| # type: <projecttype> # backdrop, craftcms, django4, drupal, drupal6, drupal7, laravel, magento, magento2, php, python, shopware6, silverstripe, typo3, wordpress | |||||
| # See https://ddev.readthedocs.io/en/stable/users/quickstart/ for more | |||||
| # information on the different project types | |||||
| # "drupal" covers recent Drupal 8+ | |||||
| # docroot: <relative_path> # Relative path to the directory containing index.php. | |||||
| # php_version: "8.2" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4" | |||||
| # You can explicitly specify the webimage but this | |||||
| # is not recommended, as the images are often closely tied to DDEV's' behavior, | |||||
| # so this can break upgrades. | |||||
| # webimage: <docker_image> # nginx/php docker image. | |||||
| # database: | |||||
| # type: <dbtype> # mysql, mariadb, postgres | |||||
| # version: <version> # database version, like "10.11" or "8.0" | |||||
| # MariaDB versions can be 5.5-10.8, 10.11, and 11.4. | |||||
| # MySQL versions can be 5.5-8.0. | |||||
| # PostgreSQL versions can be 9-16. | |||||
| # router_http_port: <port> # Port to be used for http (defaults to global configuration, usually 80) | |||||
| # router_https_port: <port> # Port for https (defaults to global configuration, usually 443) | |||||
| # xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart" | |||||
| # Note that for most people the commands | |||||
| # "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better, | |||||
| # as leaving Xdebug enabled all the time is a big performance hit. | |||||
| # xhprof_enabled: false # Set to true to enable Xhprof and "ddev start" or "ddev restart" | |||||
| # Note that for most people the commands | |||||
| # "ddev xhprof" to enable Xhprof and "ddev xhprof off" to disable it work better, | |||||
| # as leaving Xhprof enabled all the time is a big performance hit. | |||||
| # webserver_type: nginx-fpm, apache-fpm, or nginx-gunicorn | |||||
| # timezone: Europe/Berlin | |||||
| # This is the timezone used in the containers and by PHP; | |||||
| # it can be set to any valid timezone, | |||||
| # see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | |||||
| # For example Europe/Dublin or MST7MDT | |||||
| # composer_root: <relative_path> | |||||
| # Relative path to the Composer root directory from the project root. This is | |||||
| # the directory which contains the composer.json and where all Composer related | |||||
| # commands are executed. | |||||
| # composer_version: "2" | |||||
| # You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1 | |||||
| # to use the latest major version available at the time your container is built. | |||||
| # It is also possible to use each other Composer version channel. This includes: | |||||
| # - 2.2 (latest Composer LTS version) | |||||
| # - stable | |||||
| # - preview | |||||
| # - snapshot | |||||
| # Alternatively, an explicit Composer version may be specified, for example "2.2.18". | |||||
| # To reinstall Composer after the image was built, run "ddev debug refresh". | |||||
| # nodejs_version: "20" | |||||
| # change from the default system Node.js version to any other version. | |||||
| # See https://ddev.readthedocs.io/en/stable/users/configuration/config/#nodejs_version for more information | |||||
| # and https://www.npmjs.com/package/n#specifying-nodejs-versions for the full documentation, | |||||
| # Note that using of 'ddev nvm' is discouraged because "nodejs_version" is much easier to use, | |||||
| # can specify any version, and is more robust than using 'nvm'. | |||||
| # corepack_enable: false | |||||
| # Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm | |||||
| # additional_hostnames: | |||||
| # - somename | |||||
| # - someothername | |||||
| # would provide http and https URLs for "somename.ddev.site" | |||||
| # and "someothername.ddev.site". | |||||
| # additional_fqdns: | |||||
| # - example.com | |||||
| # - sub1.example.com | |||||
| # would provide http and https URLs for "example.com" and "sub1.example.com" | |||||
| # Please take care with this because it can cause great confusion. | |||||
| # upload_dirs: "custom/upload/dir" | |||||
| # | |||||
| # upload_dirs: | |||||
| # - custom/upload/dir | |||||
| # - ../private | |||||
| # | |||||
| # would set the destination paths for ddev import-files to <docroot>/custom/upload/dir | |||||
| # When Mutagen is enabled this path is bind-mounted so that all the files | |||||
| # in the upload_dirs don't have to be synced into Mutagen. | |||||
| # disable_upload_dirs_warning: false | |||||
| # If true, turns off the normal warning that says | |||||
| # "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set" | |||||
| # ddev_version_constraint: "" | |||||
| # Example: | |||||
| # ddev_version_constraint: ">= 1.22.4" | |||||
| # This will enforce that the running ddev version is within this constraint. | |||||
| # See https://github.com/Masterminds/semver#checking-version-constraints for | |||||
| # supported constraint formats | |||||
| # working_dir: | |||||
| # web: /var/www/html | |||||
| # db: /home | |||||
| # would set the default working directory for the web and db services. | |||||
| # These values specify the destination directory for ddev ssh and the | |||||
| # directory in which commands passed into ddev exec are run. | |||||
| # omit_containers: [db, ddev-ssh-agent] | |||||
| # Currently only these containers are supported. Some containers can also be | |||||
| # omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit | |||||
| # the "db" container, several standard features of DDEV that access the | |||||
| # database container will be unusable. In the global configuration it is also | |||||
| # possible to omit ddev-router, but not here. | |||||
| # performance_mode: "global" | |||||
| # DDEV offers performance optimization strategies to improve the filesystem | |||||
| # performance depending on your host system. Should be configured globally. | |||||
| # | |||||
| # If set, will override the global config. Possible values are: | |||||
| # - "global": uses the value from the global config. | |||||
| # - "none": disables performance optimization for this project. | |||||
| # - "mutagen": enables Mutagen for this project. | |||||
| # - "nfs": enables NFS for this project. | |||||
| # | |||||
| # See https://ddev.readthedocs.io/en/stable/users/install/performance/#nfs | |||||
| # See https://ddev.readthedocs.io/en/stable/users/install/performance/#mutagen | |||||
| # fail_on_hook_fail: False | |||||
| # Decide whether 'ddev start' should be interrupted by a failing hook | |||||
| # host_https_port: "59002" | |||||
| # The host port binding for https can be explicitly specified. It is | |||||
| # dynamic unless otherwise specified. | |||||
| # This is not used by most people, most people use the *router* instead | |||||
| # of the localhost port. | |||||
| # host_webserver_port: "59001" | |||||
| # The host port binding for the ddev-webserver can be explicitly specified. It is | |||||
| # dynamic unless otherwise specified. | |||||
| # This is not used by most people, most people use the *router* instead | |||||
| # of the localhost port. | |||||
| # host_db_port: "59002" | |||||
| # The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic | |||||
| # unless explicitly specified. | |||||
| # mailpit_http_port: "8025" | |||||
| # mailpit_https_port: "8026" | |||||
| # The Mailpit ports can be changed from the default 8025 and 8026 | |||||
| # host_mailpit_port: "8025" | |||||
| # The mailpit port is not normally bound on the host at all, instead being routed | |||||
| # through ddev-router, but it can be bound directly to localhost if specified here. | |||||
| # webimage_extra_packages: [php7.4-tidy, php-bcmath] | |||||
| # Extra Debian packages that are needed in the webimage can be added here | |||||
| # dbimage_extra_packages: [telnet,netcat] | |||||
| # Extra Debian packages that are needed in the dbimage can be added here | |||||
| # use_dns_when_possible: true | |||||
| # If the host has internet access and the domain configured can | |||||
| # successfully be looked up, DNS will be used for hostname resolution | |||||
| # instead of editing /etc/hosts | |||||
| # Defaults to true | |||||
| # project_tld: ddev.site | |||||
| # The top-level domain used for project URLs | |||||
| # The default "ddev.site" allows DNS lookup via a wildcard | |||||
| # If you prefer you can change this to "ddev.local" to preserve | |||||
| # pre-v1.9 behavior. | |||||
| # ngrok_args: --basic-auth username:pass1234 | |||||
| # Provide extra flags to the "ngrok http" command, see | |||||
| # https://ngrok.com/docs/ngrok-agent/config or run "ngrok http -h" | |||||
| # disable_settings_management: false | |||||
| # If true, DDEV will not create CMS-specific settings files like | |||||
| # Drupal's settings.php/settings.ddev.php or TYPO3's additional.php | |||||
| # In this case the user must provide all such settings. | |||||
| # You can inject environment variables into the web container with: | |||||
| # web_environment: | |||||
| # - SOMEENV=somevalue | |||||
| # - SOMEOTHERENV=someothervalue | |||||
| # no_project_mount: false | |||||
| # (Experimental) If true, DDEV will not mount the project into the web container; | |||||
| # the user is responsible for mounting it manually or via a script. | |||||
| # This is to enable experimentation with alternate file mounting strategies. | |||||
| # For advanced users only! | |||||
| # bind_all_interfaces: false | |||||
| # If true, host ports will be bound on all network interfaces, | |||||
| # not the localhost interface only. This means that ports | |||||
| # will be available on the local network if the host firewall | |||||
| # allows it. | |||||
| # default_container_timeout: 120 | |||||
| # The default time that DDEV waits for all containers to become ready can be increased from | |||||
| # the default 120. This helps in importing huge databases, for example. | |||||
| #web_extra_exposed_ports: | |||||
| #- name: nodejs | |||||
| # container_port: 3000 | |||||
| # http_port: 2999 | |||||
| # https_port: 3000 | |||||
| #- name: something | |||||
| # container_port: 4000 | |||||
| # https_port: 4000 | |||||
| # http_port: 3999 | |||||
| # Allows a set of extra ports to be exposed via ddev-router | |||||
| # Fill in all three fields even if you don’t intend to use the https_port! | |||||
| # If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start. | |||||
| # | |||||
| # The port behavior on the ddev-webserver must be arranged separately, for example | |||||
| # using web_extra_daemons. | |||||
| # For example, with a web app on port 3000 inside the container, this config would | |||||
| # expose that web app on https://<project>.ddev.site:9999 and http://<project>.ddev.site:9998 | |||||
| # web_extra_exposed_ports: | |||||
| # - name: myapp | |||||
| # container_port: 3000 | |||||
| # http_port: 9998 | |||||
| # https_port: 9999 | |||||
| #web_extra_daemons: | |||||
| #- name: "http-1" | |||||
| # command: "/var/www/html/node_modules/.bin/http-server -p 3000" | |||||
| # directory: /var/www/html | |||||
| #- name: "http-2" | |||||
| # command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000" | |||||
| # directory: /var/www/html | |||||
| # override_config: false | |||||
| # By default, config.*.yaml files are *merged* into the configuration | |||||
| # But this means that some things can't be overridden | |||||
| # For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge | |||||
| # and you can't erase existing hooks or all environment variables. | |||||
| # However, with "override_config: true" in a particular config.*.yaml file, | |||||
| # 'use_dns_when_possible: false' can override the existing values, and | |||||
| # hooks: | |||||
| # post-start: [] | |||||
| # or | |||||
| # web_environment: [] | |||||
| # or | |||||
| # additional_hostnames: [] | |||||
| # can have their intended affect. 'override_config' affects only behavior of the | |||||
| # config.*.yaml file it exists in. | |||||
| # Many DDEV commands can be extended to run tasks before or after the | |||||
| # DDEV command is executed, for example "post-start", "post-import-db", | |||||
| # "pre-composer", "post-composer" | |||||
| # See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more | |||||
| # information on the commands that can be extended and the tasks you can define | |||||
| # for them. Example: | |||||
| #hooks: | |||||
| @@ -0,0 +1,30 @@ | |||||
| #ddev-generated | |||||
| services: | |||||
| phpmyadmin: | |||||
| container_name: ddev-${DDEV_SITENAME}-phpmyadmin | |||||
| image: phpmyadmin:5.2.0 | |||||
| working_dir: "/root" | |||||
| restart: "no" | |||||
| labels: | |||||
| com.ddev.site-name: ${DDEV_SITENAME} | |||||
| com.ddev.approot: $DDEV_APPROOT | |||||
| volumes: | |||||
| - ".:/mnt/ddev_config" | |||||
| - "ddev-global-cache:/mnt/ddev-global-cache" | |||||
| expose: | |||||
| - "80" | |||||
| environment: | |||||
| - PMA_USER=root | |||||
| - PMA_PASSWORD=root | |||||
| - PMA_HOST=db | |||||
| - PMA_PORT=3306 | |||||
| - VIRTUAL_HOST=$DDEV_HOSTNAME | |||||
| - UPLOAD_LIMIT=4000M | |||||
| - HTTP_EXPOSE=8036:80 | |||||
| - HTTPS_EXPOSE=8037:80 | |||||
| healthcheck: | |||||
| interval: 120s | |||||
| timeout: 2s | |||||
| retries: 1 | |||||
| depends_on: | |||||
| - db | |||||
| @@ -0,0 +1,4 @@ | |||||
| #ddev-generated | |||||
| # If omit_containers[ddev-router] then this file will be replaced | |||||
| # with another with a `ports` statement to directly expose port 80 to 8036 | |||||
| services: {} | |||||
| @@ -0,0 +1,2 @@ | |||||
| [PHP] | |||||
| selenium.webdriver.firefox.driver = "/usr/local/bin/geckodriver" | |||||
| @@ -0,0 +1,8 @@ | |||||
| ;xdebug.log = /home/danielknudsen/xdebug.log | |||||
| ;xdebug.client_port = 9003 | |||||
| ;xdebug.client_host=host.docker.internal | |||||
| xdebug.client_host=docker.for.mac.localhost | |||||
| xdebug.mode = debug | |||||
| xdebug.start_with_request = yes | |||||
| xdebug.log = /tmp/xdebug.log | |||||
| xdebug.log_level = 7 | |||||
| @@ -0,0 +1,40 @@ | |||||
| # In all environments, the following files are loaded if they exist, | |||||
| # the latter taking precedence over the former: | |||||
| # | |||||
| # * .env contains default values for the environment variables needed by the app | |||||
| # * .env.local uncommitted file with local overrides | |||||
| # * .env.$APP_ENV committed environment-specific defaults | |||||
| # * .env.$APP_ENV.local uncommitted environment-specific overrides | |||||
| # | |||||
| # Real environment variables win over .env files. | |||||
| # | |||||
| # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. | |||||
| # https://symfony.com/doc/current/configuration/secrets.html | |||||
| # | |||||
| # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). | |||||
| # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration | |||||
| ###> symfony/framework-bundle ### | |||||
| APP_ENV=dev | |||||
| APP_SECRET=69c529ee7dd816087689aff862c66cef | |||||
| ###< symfony/framework-bundle ### | |||||
| ###> doctrine/doctrine-bundle ### | |||||
| # 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 | |||||
| # | |||||
| # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" | |||||
| # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" | |||||
| # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" | |||||
| DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" | |||||
| ###< doctrine/doctrine-bundle ### | |||||
| ###> nelmio/cors-bundle ### | |||||
| CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' | |||||
| ###< nelmio/cors-bundle ### | |||||
| ###> lexik/jwt-authentication-bundle ### | |||||
| JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem | |||||
| JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem | |||||
| JWT_PASSPHRASE=ba2be40da887cc55efae10bb2666175b97fdb52c78112deb43d1d1bb85d98e69 | |||||
| ###< lexik/jwt-authentication-bundle ### | |||||
| @@ -0,0 +1,14 @@ | |||||
| ###> symfony/framework-bundle ### | |||||
| /.env.local | |||||
| /.env.local.php | |||||
| /.env.*.local | |||||
| /config/secrets/prod/prod.decrypt.private.php | |||||
| /public/bundles/ | |||||
| /var/ | |||||
| /vendor/ | |||||
| ###< symfony/framework-bundle ### | |||||
| ###> lexik/jwt-authentication-bundle ### | |||||
| /config/jwt/*.pem | |||||
| ###< lexik/jwt-authentication-bundle ### | |||||
| @@ -0,0 +1,198 @@ | |||||
| # matsen-tool-be | |||||
| # Most needed commands | |||||
| ddev exec bin/console make:migration | |||||
| ddev exec bin/console doctrine:migration:migrate | |||||
| ddev exec bin/console doctrine:fixtures:load | |||||
| ddev exec php bin/console doctrine:fixtures:load --append (nur neue Datensaätz, nicht Datenbank löschen!) | |||||
| ddev exec bin/console make:entity | |||||
| ddev exec bin/console make:factory | |||||
| ddev exec php bin/phpunit | |||||
| ddev exec php bin/phpunit --filter=testPostToCreateNewUserPost | |||||
| # Installation: | |||||
| gehe ins root Verzeichnis des Projekts und für aus: ddev config | |||||
| gehe den Installationsprozess durch (3x Enter drücken) | |||||
| starte danach ddev mit: ddev start | |||||
| projekt installieren: ddev composer install | |||||
| # Troubleshooting: | |||||
| Unable to listen on required ports, port 443 is already in use: | |||||
| -> setze andere Ports in .ddev/config.yaml z.B. | |||||
| router_http_port: 8080 | |||||
| router_https_port: 8443 | |||||
| - https://stackoverflow.com/questions/76507208/symfony-6-3-migration-causes-problems-with-stateless-authenticators-forcing-requ | |||||
| Xdebug not working at all: | |||||
| ddev logs -> xdebug problems? like: | |||||
| "NOTICE: PHP message: Xdebug: [Step Debug] Could not connect to debugging client. Tried: 172.27.0.1:9003 (from HTTP_X_FORWARDED_FOR HTTP header), host.docker.internal:9003 (fallback through xdebug.client_host/xdebug.client_port)." | |||||
| - https://stackoverflow.com/questions/31324981/how-to-access-host-port-from-docker-container/43541732#43541732 | |||||
| -> create folder "php" in ".ddev"-folder, create a file "xdebug.ini" (here could be also a php.ini - settings made override ddev php.ini) and insert this line into xdebug.ini: xdebug.client_host=docker.for.mac.localhost | |||||
| -> ddev restart afterwards | |||||
| Xdebug: When one is not working, use the other. | |||||
| xdebug.client_host=host.docker.internal | |||||
| ;xdebug.client_host=docker.for.mac.localhost | |||||
| telnet: Unable to connect to remote host: Connection refused | |||||
| # PHPMyAdmin installieren: | |||||
| ddev get ddev/ddev-phpmyadmin | |||||
| ddev restart | |||||
| # Symfony Konsolenbefehle mit Ddev ausführen, z.B.: | |||||
| ddev exec php bin/console make:migration | |||||
| # Ddev Commands: | |||||
| ddev describe - zeigt Urls und installierte Komponenten | |||||
| # Xdebug | |||||
| ddev ssh -> export XDEBUG_CONFIG="idekey=PHPSTORM" | |||||
| - Cli Skript xebug läuft nicht korrekt: | |||||
| - ddev ssh | |||||
| - in Terminal: PHP_IDE_CONFIG="serverName=matsen-tool-be.ddev.site" | |||||
| - Servername ist hier konfiguriert:  | |||||
| ------------------------- | |||||
| # Symfony: | |||||
| # User with Maker Bundle: | |||||
| - https://symfonycasts.com/screencast/api-platform/user-entity | |||||
| ddev composer require maker-bundle --dev | |||||
| ddev exec bin/console make:user -> erstellt user entity und schreibt in die security.yaml | |||||
| # Entity erzeugen oder erweitern: | |||||
| ddev exec bin/console make:entity | |||||
| # Foundry fixtures: | |||||
| ddev exec composer require foundry orm-fixtures --dev | |||||
| ddev exec bin/console make:factory | |||||
| ddev exec bin/console doctrine:fixtures:load | |||||
| # Doctrine: | |||||
| ddev exec bin/console doctrine:database:drop --force | |||||
| ddev exec bin/console doctrine:database:create | |||||
| - Testdatenbank anlegen: | |||||
| ddev exec bin/console doctrine:database:create --env=test | |||||
| ddev exec bin/console doctrine:schema:create --env=test | |||||
| - Migrations: | |||||
| ddev exec bin/console make:migration | |||||
| ddev exec bin/console doctrine:migration:migrate | |||||
| # Profiler | |||||
| ddev composer require debug | |||||
| # Php Unit | |||||
| - https://symfony.com/doc/current/testing.html#configuring-a-database-for-tests | |||||
| Setup: | |||||
| # .env.test.local -> "mysql://root:root@db:3306/db?serverVersion=10.4.30-MariaDB-1:10.4.30+maria~ubu2004-log - mariadb.org binary distribution" | |||||
| -> this creates a db named db_test (it takes the name of the main database "db" and adds "_test" to its name) | |||||
| # Create db and create schema | |||||
| php bin/console --env=test doctrine:database:create | |||||
| php bin/console --env=test doctrine:schema:create | |||||
| - https://symfonycasts.com/screencast/api-platform-security/test-setup | |||||
| ddev composer require test -> testpack incl. phpunit | |||||
| ddev composer require zenstruck/browser --dev -> browser test package to imporve testing | |||||
| -> add extension to phpunit.xml.dist | |||||
| <extensions> | |||||
| <extension class="Zenstruck\Browser\Test\BrowserExtension" /> | |||||
| </extensions> | |||||
| ddev exec php bin/phpunit --filter=testPostToCreateNewUserPost | |||||
| ddev composer require --dev mtdowling/jmespath.php | |||||
| # Api Platform | |||||
| - https://api-platform.com/docs/core/dto/ | |||||
| - https://api-platform.com/docs/distribution/ | |||||
| - https://api-platform.com/docs/core/extending/ | |||||
| Installation: ddev composer require api | |||||
| Micro Mapper: ddev composer require symfonycasts/micro-mapper | |||||
| Vich Uploader bundle: ddev composer require vich/uploader-bundle | |||||
| Foundry: composer require --dev foundry orm-fixtures | |||||
| Ausführen, um openApi.yaml und openApi.json zu generieren: | |||||
| - cd export | |||||
| - php exportApi.php | |||||
| openApi.yaml und openApi.json in Frontend-Projekt (root) kopieren | |||||
| # Lexik JWT | |||||
| - https://symfony.com/bundles/LexikJWTAuthenticationBundle/current/index.html | |||||
| - Nur bei NEU Installation: | |||||
| - php bin/console lexik:jwt:generate-keypair | |||||
| - Open Api export: | |||||
| ddev exec bin/console api:openapi:export --yaml >> openapi.yaml | |||||
| - File Uploading: | |||||
| https://api-platform.com/docs/core/file-upload/ | |||||
| https://github.com/dustin10/VichUploaderBundle/blob/master/docs/index.md | |||||
| # List Routes and Service Tags | |||||
| - ddev exec bin/console debug:router | |||||
| - ddev exec bin/console debug:container | |||||
| # Constraints | |||||
| - https://symfony.com/doc/current/validation.html | |||||
| # LIVE DEPLOYMENT | |||||
| - URL is https://matsen.spawntree.de | |||||
| - Clear DB: https://spawntree.de:8443 | |||||
| - Run shell script in: /var/www/vhosts/spawntree.de/matsen-api.spawntree.de/httpdocs | |||||
| - (If JWT PEM files don't exist in /config/jwt: bin/console lexik:jwt:generate-keypair) | |||||
| - Frontend: ng build --configuration=development --aot | |||||
| - Delete all files in /matsen.spawntree.de/matsen-tool/browser via FTP | |||||
| - Copy content of /matsen-tool/dist/matsen-tool/browser into /matsen.spawntree.de/matsen-tool/browser | |||||
| # Gecko Firefox driver | |||||
| - WICHTIG: chmod +x .ddev/post-start.sh (die Datei muss ausführbar sein) | |||||
| - beim live deployment muss der driver per Linux installiert werden | |||||
| - manuell mit ddev ssh: | |||||
| sudo apt-get install -y firefox-esr | |||||
| wget -q https://github.com/mozilla/geckodriver/releases/download/v$GECKODRIVER_VERSION/geckodriver-v$GECKODRIVER_VERSION-linux64.tar.gz \ | |||||
| && tar -xzf geckodriver-v$GECKODRIVER_VERSION-linux64.tar.gz -C /usr/local/bin \ | |||||
| && rm geckodriver-v$GECKODRIVER_VERSION-linux64.tar.gz \ | |||||
| && chmod +x /usr/local/bin/geckodriver | |||||
| # Nord vpn | |||||
| - https://support.nordvpn.com/hc/de/articles/20398283005457-Installation-und-Gebrauch-von-NordVPN-unter-RHEL-und-CentOS-Linux | |||||
| # apache 403 beim sortieren nach "profile" | |||||
| Ja, wenn Sie Plesk verwenden, gibt es tatsächlich Möglichkeiten, Whitelist-Regeln für ModSecurity einzurichten. Hier sind die Schritte, die Sie in Plesk unternehmen können: | |||||
| Öffnen Sie das Plesk Control Panel. | |||||
| Navigieren Sie zu "Websites & Domains" und wählen Sie die betroffene Domain aus. | |||||
| Suchen Sie nach "Apache & nginx Settings" oder "Web-Server-Einstellungen" (je nach Ihrer Plesk-Version). | |||||
| Scrollen Sie nach unten zum Abschnitt "Additional Apache directives" oder "Zusätzliche Apache-Direktiven". | |||||
| Hier können Sie benutzerdefinierte Apache-Konfigurationen hinzufügen, einschließlich ModSecurity-Regeln. | |||||
| Fügen Sie eine Whitelist-Regel wie folgt hinzu: | |||||
| <IfModule mod_security2.c> | |||||
| SecRule REQUEST_URI "^/api/game_accounts" \ | |||||
| "id:1000,\ | |||||
| phase:1,\ | |||||
| pass,\ | |||||
| nolog,\ | |||||
| ctl:ruleRemoveById=210580" | |||||
| </IfModule> | |||||
| @@ -0,0 +1,21 @@ | |||||
| #!/usr/bin/env php | |||||
| <?php | |||||
| use App\Kernel; | |||||
| use Symfony\Bundle\FrameworkBundle\Console\Application; | |||||
| if (!is_dir(dirname(__DIR__).'/vendor')) { | |||||
| throw new LogicException('Dependencies are missing. Try running "composer install".'); | |||||
| } | |||||
| if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) { | |||||
| throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".'); | |||||
| } | |||||
| require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; | |||||
| return function (array $context) { | |||||
| $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); | |||||
| return new Application($kernel); | |||||
| }; | |||||
| @@ -0,0 +1,93 @@ | |||||
| { | |||||
| "type": "project", | |||||
| "license": "proprietary", | |||||
| "minimum-stability": "stable", | |||||
| "prefer-stable": true, | |||||
| "require": { | |||||
| "php": ">=8.2", | |||||
| "ext-ctype": "*", | |||||
| "ext-iconv": "*", | |||||
| "api-platform/doctrine-orm": "^4.0", | |||||
| "api-platform/symfony": "^4.0", | |||||
| "doctrine/dbal": "^3", | |||||
| "doctrine/doctrine-bundle": "^2.13", | |||||
| "doctrine/doctrine-migrations-bundle": "^3.3", | |||||
| "doctrine/orm": "^3.2", | |||||
| "lexik/jwt-authentication-bundle": "^3.1", | |||||
| "nelmio/cors-bundle": "^2.5", | |||||
| "phpdocumentor/reflection-docblock": "^5.4", | |||||
| "phpstan/phpdoc-parser": "^1.32", | |||||
| "symfony/asset": "7.1.*", | |||||
| "symfony/console": "7.1.*", | |||||
| "symfony/dotenv": "7.1.*", | |||||
| "symfony/expression-language": "7.1.*", | |||||
| "symfony/flex": "^2", | |||||
| "symfony/form": "7.1.*", | |||||
| "symfony/framework-bundle": "7.1.*", | |||||
| "symfony/property-access": "7.1.*", | |||||
| "symfony/property-info": "7.1.*", | |||||
| "symfony/runtime": "7.1.*", | |||||
| "symfony/security-bundle": "7.1.*", | |||||
| "symfony/serializer": "7.1.*", | |||||
| "symfony/twig-bundle": "7.1.*", | |||||
| "symfony/validator": "7.1.*", | |||||
| "symfony/yaml": "7.1.*", | |||||
| "symfonycasts/micro-mapper": "^0.2.0", | |||||
| "vich/uploader-bundle": "^2.4" | |||||
| }, | |||||
| "config": { | |||||
| "allow-plugins": { | |||||
| "php-http/discovery": true, | |||||
| "symfony/flex": true, | |||||
| "symfony/runtime": true | |||||
| }, | |||||
| "sort-packages": true | |||||
| }, | |||||
| "autoload": { | |||||
| "psr-4": { | |||||
| "App\\": "src/" | |||||
| } | |||||
| }, | |||||
| "autoload-dev": { | |||||
| "psr-4": { | |||||
| "App\\Tests\\": "tests/" | |||||
| } | |||||
| }, | |||||
| "replace": { | |||||
| "symfony/polyfill-ctype": "*", | |||||
| "symfony/polyfill-iconv": "*", | |||||
| "symfony/polyfill-php72": "*", | |||||
| "symfony/polyfill-php73": "*", | |||||
| "symfony/polyfill-php74": "*", | |||||
| "symfony/polyfill-php80": "*", | |||||
| "symfony/polyfill-php81": "*", | |||||
| "symfony/polyfill-php82": "*" | |||||
| }, | |||||
| "scripts": { | |||||
| "auto-scripts": { | |||||
| "cache:clear": "symfony-cmd", | |||||
| "assets:install %PUBLIC_DIR%": "symfony-cmd" | |||||
| }, | |||||
| "post-install-cmd": [ | |||||
| "@auto-scripts" | |||||
| ], | |||||
| "post-update-cmd": [ | |||||
| "@auto-scripts" | |||||
| ] | |||||
| }, | |||||
| "conflict": { | |||||
| "symfony/symfony": "*" | |||||
| }, | |||||
| "extra": { | |||||
| "symfony": { | |||||
| "allow-contrib": true, | |||||
| "require": "7.1.*", | |||||
| "docker": false | |||||
| } | |||||
| }, | |||||
| "require-dev": { | |||||
| "doctrine/doctrine-fixtures-bundle": "^3.6", | |||||
| "symfony/maker-bundle": "^1.61", | |||||
| "zenstruck/foundry": "^2.0" | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| <?php | |||||
| return [ | |||||
| Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], | |||||
| Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], | |||||
| Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], | |||||
| Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], | |||||
| Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], | |||||
| Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], | |||||
| ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], | |||||
| Symfonycasts\MicroMapper\SymfonycastsMicroMapperBundle::class => ['all' => true], | |||||
| Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true], | |||||
| Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], | |||||
| Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], | |||||
| Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true], | |||||
| Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], | |||||
| ]; | |||||
| @@ -0,0 +1,27 @@ | |||||
| api_platform: | |||||
| title: Imaq Platform | |||||
| version: 1.0.0 | |||||
| formats: | |||||
| jsonld: ['application/ld+json'] | |||||
| docs_formats: | |||||
| jsonld: ['application/ld+json'] | |||||
| json: [ 'application/json' ] | |||||
| html: ['text/html'] | |||||
| defaults: | |||||
| stateless: true | |||||
| cache_headers: | |||||
| vary: ['Content-Type', 'Authorization', 'Origin'] | |||||
| extra_properties: | |||||
| standard_put: true | |||||
| pagination_client_items_per_page: true | |||||
| pagination_items_per_page: 50 | |||||
| pagination_maximum_items_per_page: 100 | |||||
| swagger: | |||||
| api_keys: | |||||
| JWT: | |||||
| name: Authorization | |||||
| type: header | |||||
| swagger_ui_extra_configuration: | |||||
| persistAuthorization: true | |||||
| @@ -0,0 +1,19 @@ | |||||
| framework: | |||||
| cache: | |||||
| # Unique name of your app: used to compute stable namespaces for cache keys. | |||||
| #prefix_seed: your_vendor_name/app_name | |||||
| # The "app" cache stores to the filesystem by default. | |||||
| # The data in this cache should persist between deploys. | |||||
| # Other options include: | |||||
| # Redis | |||||
| #app: cache.adapter.redis | |||||
| #default_redis_provider: redis://localhost | |||||
| # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) | |||||
| #app: cache.adapter.apcu | |||||
| # Namespaced pools use the above "app" backend by default | |||||
| #pools: | |||||
| #my.dedicated.cache: null | |||||
| @@ -0,0 +1,52 @@ | |||||
| doctrine: | |||||
| dbal: | |||||
| url: '%env(resolve:DATABASE_URL)%' | |||||
| # IMPORTANT: You MUST configure your server version, | |||||
| # either here or in the DATABASE_URL env var (see .env file) | |||||
| #server_version: '16' | |||||
| profiling_collect_backtrace: '%kernel.debug%' | |||||
| use_savepoints: true | |||||
| orm: | |||||
| auto_generate_proxy_classes: true | |||||
| enable_lazy_ghost_objects: true | |||||
| report_fields_where_declared: true | |||||
| validate_xml_mapping: true | |||||
| naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware | |||||
| auto_mapping: true | |||||
| mappings: | |||||
| App: | |||||
| type: attribute | |||||
| is_bundle: false | |||||
| dir: '%kernel.project_dir%/src/Entity' | |||||
| prefix: 'App\Entity' | |||||
| alias: App | |||||
| controller_resolver: | |||||
| auto_mapping: false | |||||
| when@test: | |||||
| doctrine: | |||||
| dbal: | |||||
| # "TEST_TOKEN" is typically set by ParaTest | |||||
| dbname_suffix: '_test%env(default::TEST_TOKEN)%' | |||||
| when@prod: | |||||
| doctrine: | |||||
| orm: | |||||
| auto_generate_proxy_classes: false | |||||
| proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' | |||||
| query_cache_driver: | |||||
| type: pool | |||||
| pool: doctrine.system_cache_pool | |||||
| result_cache_driver: | |||||
| type: pool | |||||
| pool: doctrine.result_cache_pool | |||||
| framework: | |||||
| cache: | |||||
| pools: | |||||
| doctrine.result_cache_pool: | |||||
| adapter: cache.app | |||||
| doctrine.system_cache_pool: | |||||
| adapter: cache.system | |||||
| @@ -0,0 +1,6 @@ | |||||
| doctrine_migrations: | |||||
| migrations_paths: | |||||
| # namespace is arbitrary but should be different from App\Migrations | |||||
| # as migrations classes should NOT be autoloaded | |||||
| 'DoctrineMigrations': '%kernel.project_dir%/migrations' | |||||
| enable_profiler: false | |||||
| @@ -0,0 +1,31 @@ | |||||
| # see https://symfony.com/doc/current/reference/configuration/framework.html | |||||
| framework: | |||||
| secret: '%env(APP_SECRET)%' | |||||
| #csrf_protection: true | |||||
| http_method_override: false | |||||
| handle_all_throwables: true | |||||
| # Enables session support. Note that the session will ONLY be started if you read or write from it. | |||||
| # Remove or comment this section to explicitly disable session support. | |||||
| session: | |||||
| enabled: true | |||||
| handler_id: 'session.handler.native_file' | |||||
| cookie_secure: auto | |||||
| cookie_samesite: lax | |||||
| storage_factory_id: session.storage.factory.native | |||||
| # Timeout für nicht aktive Sitzungen (in Sekunden) | |||||
| gc_maxlifetime: 3600 | |||||
| # Dauer, für die der Cookie gesetzt wird (in Sekunden) | |||||
| cookie_lifetime: 3600 | |||||
| #esi: true | |||||
| #fragments: true | |||||
| php_errors: | |||||
| log: true | |||||
| when@test: | |||||
| framework: | |||||
| test: true | |||||
| session: | |||||
| storage_factory_id: session.storage.factory.mock_file | |||||
| @@ -0,0 +1,4 @@ | |||||
| lexik_jwt_authentication: | |||||
| secret_key: '%env(resolve:JWT_SECRET_KEY)%' | |||||
| public_key: '%env(resolve:JWT_PUBLIC_KEY)%' | |||||
| pass_phrase: '%env(JWT_PASSPHRASE)%' | |||||
| @@ -0,0 +1,10 @@ | |||||
| nelmio_cors: | |||||
| defaults: | |||||
| origin_regex: true | |||||
| allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] | |||||
| allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] | |||||
| allow_headers: ['Content-Type', 'Authorization'] | |||||
| expose_headers: ['Link'] | |||||
| max_age: 3600 | |||||
| paths: | |||||
| '^/': null | |||||
| @@ -0,0 +1,10 @@ | |||||
| framework: | |||||
| router: | |||||
| # 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 | |||||
| when@prod: | |||||
| framework: | |||||
| router: | |||||
| strict_requirements: null | |||||
| @@ -0,0 +1,49 @@ | |||||
| security: | |||||
| # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords | |||||
| password_hashers: | |||||
| Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' | |||||
| # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider | |||||
| providers: | |||||
| # used to reload user from session & other features (e.g. switch_user) | |||||
| app_user_provider: | |||||
| entity: | |||||
| class: App\Entity\User | |||||
| property: email | |||||
| firewalls: | |||||
| dev: | |||||
| pattern: ^/(_(profiler|wdt)|css|images|js)/ | |||||
| security: false | |||||
| auth: | |||||
| pattern: ^/api/auth | |||||
| stateless: true | |||||
| json_login: | |||||
| check_path: /api/auth | |||||
| username_path: email | |||||
| password_path: password | |||||
| success_handler: lexik_jwt_authentication.handler.authentication_success | |||||
| failure_handler: lexik_jwt_authentication.handler.authentication_failure | |||||
| api: | |||||
| pattern: ^/api/ | |||||
| stateless: true | |||||
| provider: app_user_provider | |||||
| jwt: ~ | |||||
| main: | |||||
| # Easy way to control access for large sections of your site | |||||
| # Note: Only the *first* access control that matches will be used | |||||
| access_control: | |||||
| # - { path: ^/admin, roles: ROLE_ADMIN } | |||||
| # - { path: ^/profile, roles: ROLE_USER } | |||||
| when@test: | |||||
| security: | |||||
| password_hashers: | |||||
| # By default, password hashers are resource intensive and take time. This is | |||||
| # important to generate secure password hashes. In tests however, secure hashes | |||||
| # are not important, waste resources and increase test times. The following | |||||
| # reduces the work factor to the lowest possible values. | |||||
| Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: | |||||
| algorithm: auto | |||||
| cost: 4 # Lowest possible value for bcrypt | |||||
| time_cost: 3 # Lowest possible value for argon | |||||
| memory_cost: 10 # Lowest possible value for argon | |||||
| @@ -0,0 +1,6 @@ | |||||
| twig: | |||||
| file_name_pattern: '*.twig' | |||||
| when@test: | |||||
| twig: | |||||
| strict_variables: true | |||||
| @@ -0,0 +1,11 @@ | |||||
| framework: | |||||
| validation: | |||||
| # Enables validator auto-mapping support. | |||||
| # For instance, basic validation constraints will be inferred from Doctrine's metadata. | |||||
| #auto_mapping: | |||||
| # App\Entity\: [] | |||||
| when@test: | |||||
| framework: | |||||
| validation: | |||||
| not_compromised_password: false | |||||
| @@ -0,0 +1,21 @@ | |||||
| vich_uploader: | |||||
| db_driver: orm | |||||
| metadata: | |||||
| type: attribute | |||||
| mappings: | |||||
| media_object: | |||||
| uri_prefix: /media | |||||
| upload_destination: '%kernel.project_dir%/public/media' | |||||
| # Will rename uploaded files using a uniqueid as a prefix. | |||||
| namer: Vich\UploaderBundle\Naming\SmartUniqueNamer | |||||
| inject_on_load: false | |||||
| delete_on_update: true | |||||
| delete_on_remove: true | |||||
| document_object: | |||||
| uri_prefix: /document | |||||
| upload_destination: '%kernel.project_dir%/public/document' | |||||
| # Will rename uploaded files using a uniqueid as a prefix. | |||||
| namer: Vich\UploaderBundle\Naming\SmartUniqueNamer | |||||
| inject_on_load: false | |||||
| delete_on_update: true | |||||
| delete_on_remove: true | |||||
| @@ -0,0 +1,7 @@ | |||||
| when@dev: &dev | |||||
| # See full configuration: https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#full-default-bundle-configuration | |||||
| zenstruck_foundry: | |||||
| # Whether to auto-refresh proxies by default (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh) | |||||
| auto_refresh_proxies: true | |||||
| when@test: *dev | |||||
| @@ -0,0 +1,5 @@ | |||||
| <?php | |||||
| if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) { | |||||
| require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php'; | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| controllers: | |||||
| resource: | |||||
| path: ../src/Controller/ | |||||
| namespace: App\Controller | |||||
| type: attribute | |||||
| # Bestehende Auth-Route | |||||
| auth: | |||||
| path: /api/auth | |||||
| methods: ['POST'] | |||||
| # API und Media Routen | |||||
| api_routes: | |||||
| resource: . | |||||
| type: api_platform | |||||
| prefix: /api | |||||
| angular_app: | |||||
| path: /{path} | |||||
| controller: App\Controller\AngularController::index | |||||
| requirements: | |||||
| path: "^(?!api|media).*" | |||||
| @@ -0,0 +1,4 @@ | |||||
| api_platform: | |||||
| resource: . | |||||
| type: api_platform | |||||
| prefix: /api | |||||
| @@ -0,0 +1,4 @@ | |||||
| when@dev: | |||||
| _errors: | |||||
| resource: '@FrameworkBundle/Resources/config/routing/errors.xml' | |||||
| prefix: /_error | |||||
| @@ -0,0 +1,3 @@ | |||||
| _security_logout: | |||||
| resource: security.route_loader.logout | |||||
| type: service | |||||
| @@ -0,0 +1,30 @@ | |||||
| # This file is the entry point to configure your own services. | |||||
| # Files in the packages/ subdirectory configure your dependencies. | |||||
| # 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 | |||||
| parameters: | |||||
| services: | |||||
| # default configuration for services in *this* file | |||||
| _defaults: | |||||
| autowire: true # Automatically injects dependencies in your services. | |||||
| autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. | |||||
| # makes classes in src/ available to be used as services | |||||
| # this creates a service per class whose id is the fully-qualified class name | |||||
| App\: | |||||
| resource: '../src/' | |||||
| exclude: | |||||
| - '../src/DependencyInjection/' | |||||
| - '../src/Entity/' | |||||
| - '../src/Kernel.php' | |||||
| # add more service definitions when explicit configuration is needed | |||||
| # please note that last definitions always *replace* previous ones | |||||
| acme_api.event.authentication_success_listener: | |||||
| class: App\EventListener\AuthenticationSuccessListener | |||||
| tags: | |||||
| - { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccessResponse } | |||||
| arguments: [ '@symfonycasts.micro_mapper', '@api_platform.iri_converter' ] | |||||
| @@ -0,0 +1,38 @@ | |||||
| #!/bin/bash | |||||
| # Setze den Pfad des Verzeichnisses, in dem sich das Skript befindet | |||||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |||||
| EXPORT_DIR="${SCRIPT_DIR}" | |||||
| ANGULAR_DIR="${SCRIPT_DIR}/../angular" | |||||
| # Lösche die beiden Dateien openapi.yaml und openapi.json im Export-Verzeichnis, falls sie existieren | |||||
| if [ -f "${EXPORT_DIR}/openapi.yaml" ]; then | |||||
| rm "${EXPORT_DIR}/openapi.yaml" | |||||
| fi | |||||
| if [ -f "${EXPORT_DIR}/openapi.json" ]; then | |||||
| rm "${EXPORT_DIR}/openapi.json" | |||||
| fi | |||||
| # Führe den Konsolenbefehl aus, um die OpenAPI-Datei zu exportieren | |||||
| bin/console api:openapi:export --yaml > "${EXPORT_DIR}/openapi.yaml" | |||||
| # URL der JSON-Datei | |||||
| JSON_URL="https://futbase.ddev.site:8453/api/docs.json" | |||||
| # JSON von der URL abrufen und im Export-Verzeichnis speichern | |||||
| curl -o "${EXPORT_DIR}/openapi.json" "${JSON_URL}" | |||||
| # Sicherstellen, dass das Zielverzeichnis existiert | |||||
| if [ ! -d "${ANGULAR_DIR}" ]; then | |||||
| mkdir -p "${ANGULAR_DIR}" | |||||
| fi | |||||
| # Verschiebe die Dateien in den angular Ordner | |||||
| mv "${EXPORT_DIR}/openapi.yaml" "${ANGULAR_DIR}/openapi.yaml" | |||||
| mv "${EXPORT_DIR}/openapi.json" "${ANGULAR_DIR}/openapi.json" | |||||
| # Wechsle in den angular Ordner und führe das Shell-Skript generateApi.sh aus, falls es existiert | |||||
| if [ -f "${ANGULAR_DIR}/generateApi.sh" ]; then | |||||
| (cd "${ANGULAR_DIR}" && bash generateApi.sh) | |||||
| fi | |||||
| @@ -0,0 +1,37 @@ | |||||
| <?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 Version20241001153007 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 TABLE media_object (id INT AUTO_INCREMENT NOT NULL, created_by_id INT NOT NULL, file_path VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_14D43132B03A8386 (created_by_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('CREATE TABLE `user` (id INT AUTO_INCREMENT NOT NULL, image_id INT DEFAULT NULL, email VARCHAR(180) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, active TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), INDEX IDX_8D93D6493DA5256D (image_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('ALTER TABLE media_object ADD CONSTRAINT FK_14D43132B03A8386 FOREIGN KEY (created_by_id) REFERENCES `user` (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE `user` ADD CONSTRAINT FK_8D93D6493DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||||
| } | |||||
| public function down(Schema $schema): void | |||||
| { | |||||
| // this down() migration is auto-generated, please modify it to your needs | |||||
| $this->addSql('ALTER TABLE media_object DROP FOREIGN KEY FK_14D43132B03A8386'); | |||||
| $this->addSql('ALTER TABLE `user` DROP FOREIGN KEY FK_8D93D6493DA5256D'); | |||||
| $this->addSql('DROP TABLE media_object'); | |||||
| $this->addSql('DROP TABLE `user`'); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| <?php | |||||
| use App\Kernel; | |||||
| require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; | |||||
| return function (array $context) { | |||||
| return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); | |||||
| }; | |||||
| @@ -0,0 +1,92 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.12.23 | |||||
| */ | |||||
| namespace App\ApiResource; | |||||
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |||||
| use ApiPlatform\Doctrine\Orm\State\Options; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use ApiPlatform\Metadata\ApiProperty; | |||||
| use ApiPlatform\Metadata\ApiResource; | |||||
| use ApiPlatform\Metadata\Get; | |||||
| use ApiPlatform\Metadata\GetCollection; | |||||
| use ApiPlatform\Metadata\Post; | |||||
| use App\Entity\MediaObject; | |||||
| use App\Entity\User; | |||||
| use App\Filter\CustomJsonOrderFilter; | |||||
| use App\Filter\UserNameSearchFilter; | |||||
| use App\State\EntityClassDtoStateProcessor; | |||||
| use App\State\EntityToDtoStateProvider; | |||||
| use Symfony\Component\Validator\Constraints as Assert; | |||||
| #[ApiResource( | |||||
| shortName: 'User', | |||||
| operations: [ | |||||
| new Get( | |||||
| security: 'is_granted("ROLE_USER")' | |||||
| ), | |||||
| new GetCollection( | |||||
| security: 'is_granted("ROLE_USER")' | |||||
| ), | |||||
| new Post( | |||||
| security: 'is_granted("ROLE_ADMIN")', | |||||
| validationContext: ['groups' => ['Default', 'postValidation']] | |||||
| ), | |||||
| // new Patch( | |||||
| // security: 'is_granted("is_granted("EDIT", object)")' | |||||
| // ), | |||||
| ], | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| provider: EntityToDtoStateProvider::class, | |||||
| processor: EntityClassDtoStateProcessor::class, | |||||
| stateOptions: new Options(entityClass: User::class), | |||||
| )] | |||||
| #[ApiFilter(SearchFilter::class, properties: ['firstName' => 'ipartial', 'lastName' => 'ipartial'])] | |||||
| #[ApiFilter(UserNameSearchFilter::class)] | |||||
| #[ApiFilter(CustomJsonOrderFilter::class)] | |||||
| class UserApi | |||||
| { | |||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | |||||
| public ?int $id = null; | |||||
| #[Assert\NotBlank] | |||||
| #[Assert\Email] | |||||
| public ?string $email = null; | |||||
| #[Assert\NotBlank] | |||||
| public ?string $firstName = null; | |||||
| #[Assert\NotBlank] | |||||
| public ?string $lastName = null; | |||||
| public ?MediaObject $image = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?string $imageUrl = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?string $fullName = null; | |||||
| /** | |||||
| * The plaintext password when being set or changed. | |||||
| */ | |||||
| #[ApiProperty(readable: false)] | |||||
| #[Assert\NotBlank(groups: ['postValidation'])] | |||||
| public ?string $password = null; | |||||
| // Object is null ONLY during deserialization: so this allows isPublished | |||||
| // to be writable in ALL cases (which is ok because the operations are secured). | |||||
| // During serialization, object will always be a DragonTreasureApi, so our | |||||
| // voter is called. | |||||
| #[ApiProperty(security: 'object === null or is_granted("EDIT", object)')] | |||||
| public bool $active; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?\DateTimeImmutable $createdAt = null; | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| <?php | |||||
| namespace App\Controller; | |||||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |||||
| use Symfony\Component\HttpFoundation\Response; | |||||
| use Symfony\Component\Mime\MimeTypes; | |||||
| use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | |||||
| use Symfony\Component\Routing\Annotation\Route; | |||||
| class AngularController extends AbstractController | |||||
| { | |||||
| public function index(string $path = null): Response | |||||
| { | |||||
| $projectDir = $this->getParameter('kernel.project_dir'); | |||||
| $basePath = $projectDir . '/public/client/'; | |||||
| $filePath = $basePath . ($path ?? ''); | |||||
| // Sicherstellen, dass wir im erlaubten Verzeichnis bleiben | |||||
| if (!str_starts_with(realpath($filePath), realpath($basePath))) { | |||||
| //throw new NotFoundHttpException('Invalid path'); | |||||
| $filePath = $basePath . 'index.html'; | |||||
| } | |||||
| if (is_dir($filePath)) { | |||||
| $filePath = rtrim($filePath, '/') . '/index.html'; | |||||
| } | |||||
| if (!file_exists($filePath) || is_dir($filePath)) { | |||||
| $filePath = $basePath . 'index.html'; | |||||
| } | |||||
| if (!is_readable($filePath)) { | |||||
| throw new NotFoundHttpException('File not found or not readable'); | |||||
| } | |||||
| $content = file_get_contents($filePath); | |||||
| $mimeTypes = new MimeTypes(); | |||||
| $mimeType = $mimeTypes->guessMimeType($filePath) ?? 'application/octet-stream'; | |||||
| $response = new Response($content, Response::HTTP_OK, ['Content-Type' => $mimeType]); | |||||
| $response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'); | |||||
| return $response; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 25.01.24 | |||||
| */ | |||||
| namespace App\Controller; | |||||
| use App\Entity\MediaObject; | |||||
| use App\Entity\User; | |||||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |||||
| use Symfony\Component\HttpFoundation\Request; | |||||
| use Symfony\Component\HttpKernel\Attribute\AsController; | |||||
| use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | |||||
| #[AsController] | |||||
| final class CreateMediaObjectAction extends AbstractController | |||||
| { | |||||
| public function __invoke(Request $request): MediaObject | |||||
| { | |||||
| $uploadedFile = $request->files->get('file'); | |||||
| if (!$uploadedFile) { | |||||
| throw new BadRequestHttpException('"file" is required'); | |||||
| } | |||||
| $user = $this->getUser(); | |||||
| assert($user instanceof User); | |||||
| $mediaObject = new MediaObject($user); | |||||
| $mediaObject->file = $uploadedFile; | |||||
| return $mediaObject; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,42 @@ | |||||
| <?php | |||||
| namespace App\Controller; | |||||
| use ApiPlatform\Api\IriConverterInterface; | |||||
| use App\Entity\User; | |||||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |||||
| use Symfony\Component\HttpFoundation\JsonResponse; | |||||
| use Symfony\Component\HttpFoundation\Response; | |||||
| use Symfony\Component\Routing\Annotation\Route; | |||||
| use Symfony\Component\Security\Http\Attribute\CurrentUser; | |||||
| #[Route('/api', name: 'security_')] | |||||
| class SecurityController extends AbstractController | |||||
| { | |||||
| #[Route('/login', name: 'app_login', methods: ['POST'])] | |||||
| public function login( | |||||
| IriConverterInterface $iriConverter, | |||||
| #[CurrentUser] $user = null | |||||
| ): Response | |||||
| { | |||||
| if (!$user) { | |||||
| return $this->json([ | |||||
| 'error' => 'Invalid login request: check that the Content-Type header is "application/json".', | |||||
| ], 401); | |||||
| } | |||||
| /** @var User $user */ | |||||
| return new JsonResponse([ | |||||
| 'id' => $iriConverter->getIriFromResource($user), | |||||
| 'username' => $user->getFirstName() . '_' . $user->getLastName(), | |||||
| 'firstName' => $user->getFirstName(), | |||||
| 'lastName' => $user->getLastName() | |||||
| ]); | |||||
| } | |||||
| #[Route('/logout', name: 'app_logout')] | |||||
| public function logout(): void | |||||
| { | |||||
| throw new \Exception('This should never be reached!'); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,76 @@ | |||||
| <?php | |||||
| namespace App\DataFixtures; | |||||
| use App\Factory\MediaObjectFactory; | |||||
| use App\Factory\UserFactory; | |||||
| use Doctrine\Bundle\FixturesBundle\Fixture; | |||||
| use Doctrine\Persistence\ObjectManager; | |||||
| use Symfony\Component\HttpKernel\KernelInterface; | |||||
| class AppFixtures extends Fixture | |||||
| { | |||||
| public function __construct( | |||||
| private KernelInterface $appKernel | |||||
| ) | |||||
| { | |||||
| } | |||||
| public function load(ObjectManager $manager): void | |||||
| { | |||||
| $projectRoot = $this->appKernel->getProjectDir(); | |||||
| $mediaPath = $projectRoot . '/public/media/'; | |||||
| $files = glob($mediaPath . '*'); | |||||
| foreach ($files as $file) { | |||||
| if (is_file($file)) { | |||||
| unlink($file); | |||||
| } | |||||
| } | |||||
| $documentPath = $projectRoot . '/public/media/'; | |||||
| $files = glob($documentPath . '*'); | |||||
| foreach ($files as $file) { | |||||
| if (is_file($file)) { | |||||
| unlink($file); | |||||
| } | |||||
| } | |||||
| $system = UserFactory::createOne( | |||||
| [ | |||||
| 'email' => 'admin@spawntree.de', | |||||
| 'firstName' => 'System', | |||||
| 'lastName' => 'System', | |||||
| 'password' => '12spawntree345', | |||||
| 'roles' => ['ROLE_ADMIN'] | |||||
| ] | |||||
| ); | |||||
| $system->_set('image', MediaObjectFactory::createOne()->_real()); | |||||
| $system->_save(); | |||||
| $adminD = UserFactory::createOne( | |||||
| [ | |||||
| 'email' => 'd.knudsen@spawntree.de', | |||||
| 'firstName' => 'Daniel', | |||||
| 'lastName' => 'Knudsen', | |||||
| 'password' => '12spawntree345', | |||||
| 'roles' => ['ROLE_ADMIN'] | |||||
| ] | |||||
| ); | |||||
| $system->_set('image', MediaObjectFactory::createOne()->_real()); | |||||
| $adminD->_save(); | |||||
| UserFactory::createOne( | |||||
| [ | |||||
| 'email' => 'f.eisenmenger@spawntree.de', | |||||
| 'firstName' => 'Florian', | |||||
| 'lastName' => 'Eisenmenger', | |||||
| 'password' => '12spawntree345', | |||||
| 'image' => MediaObjectFactory::createOne(), | |||||
| 'roles' => ['ROLE_ADMIN'] | |||||
| ] | |||||
| ); | |||||
| $system->_set('image', MediaObjectFactory::createOne()->_real()); | |||||
| $adminD->_save(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"><path fill="#FFD800" d="M224 256H32c-17.6 0-32-14.4-32-32V32C0 14.4 14.4 0 32 0h192c17.6 0 32 14.4 32 32v192c0 17.6-14.4 32-32 32z"/><path fill="#333" d="M57.73 78.61c2.06.76 4.79-.61 6.77-.96 1.96-.34 10.01-2.15 8.49-.32-.85 1.03-3.06 1.04-5.03 1.61-2.73.8-7.11 1.26-7.28 4.77-.39 8.35 12.59 2.8 16.4 1.44 10.31-3.59 22.13-4.2 32.56-5.95 11.58-1.93 23.47-2.1 35.16-2.55 12.3-.47 25.53-.99 37.61.72 3.22.45 6.26.81 9.53 1.15 2.84.29 8.81 2.89 11.34 1.7 2.29-1.08 1.25-3.79 2.44-5.18 1.71-1.99 5.07-1.64 2.73-5.9-1.78-3.24-8.26-4.29-11.62-3.63-.34-2.05 1.23-1.14 1.64-2.2.59-1.53.59-3.9-.4-5.6-1.8-3.1-10.36-4.19-10.36-4.19-20.53-3.31-57.47-6.87-96.79-2.28-6.52.76-17.97 1.58-24.65 4.28-2.96 1.2-2.91 1.22-3.28 3.16-.35 2.76.97 5.97 4.29 5.28 6.35-1.31 17.57-3.12 20.78-3.94 15.86-4.04 68.64-3.25 75.44-1.64 4.2.9 27.28 1.65 27.84 5.88-53.86-2.23-74.42-2.21-122.79 3.54-4.04.67-6.47 1.55-9.65 2.63-3.95 1.34-6.6 6.17-1.17 8.18zm71.22-33.39c1.88.16 16.76.61 17.62-5.21.49-3.3-2.78-8.66-3.71-9.63-1.23-1.02-3.95 7.87-4.64-.87-.11-1.43-.47-3.63-1.15-4.98-.34-.67-.75-1.17-1.26-1.17-.32 0-.67.19-.82.29-.43.28-1.04 1.18-1.35 1.94-.84 2.08-1.21 10.1-3.29 10.23-2.26.15-2.19-5.14-1.89-6.67.05-.25-.01-.31-.11-.29-.22.06-.28.1-.46.16-1.36.52-2.91 2.1-3.02 2.22-2.13 2.18-5.01 7.89-3.56 10.51 1.51 2.69 6.05 3.33 7.64 3.47zm103.68 114.69c-.16-1.14-.49-1.84-1.27-2.89-2.8-3.84-15.98-6.4-15.98-6.36-6.62-1.45-14.23-2.69-22.74-3.73 1.21-5.75-.06-16.36-.09-20.05.17-6.59.03-23.62-.46-27.92-.38-4.05-.92-5.81-3.14-9.16-1.59-2.39-2.08-4.72-5.75-4.36-3.34.33-4.57 4.97-3.99 7.78l1.44 5.73c.6 1.19 2.22 38.34 1.85 41.57.03 1.57-.12 3.59-.16 5.3-8.67-.82-18.1-1.46-28.2-1.9l.4-.31c4.83-3.76 3.16-8.75 2.35-14.19-1.1-8.71-.26-33.42-1.3-36.4-.7-2.33-2.69-3.68-4.42-5.46-2.13-2.19-4.8-2.87-7.26-.35-2.41 2.18.6 33.39.77 47.1.22 2.87-.42 6.47.72 9.24-.93-.04-25.73-.55-27.81-.51.03-.48-.3-1.1-.53-1.57-.62-1.26-1.5-4.19-1.75-5.56-.41-2.24-.75-5.19-.86-7.9-.19-5.67.34-23.21.97-27.79.71-5.07 3.41-14.03-5.39-12.06-.95.21-5.61.79-6.86 1.77-1.45 1.14-1.31 3.99-1.36 5.57-.21 5.99-.87 24.86-.83 29.39.05 3.81.72 8.71 2.12 12.42.85 2.26 3.37 4.51 6 5.93-4.84.13-9.5.29-11.16.38-8.17.46-14.44.91-20.55 1.51-.04-.18-.04-.18-.08-.29-1.02-3.15.45-16.17 3.28-40.59.68-5.89-.35-6.12-3.94-9.44-2.06-1.9-3.8-3.19-7.37-.32-2.27 1.82-1.68 4.15-2.07 6.78-1.36 9.16-3.34 23.54-4.18 32.86-.24 2.57.12 8.46.78 12.03.06.17.03.36.12.54-3.55.48-7.12 1-11.15 1.61-9.81 1.52-18.48 1.9-24.38 4.54-3.62 1.63-3.83 2.17-4.68 3.49-.21.36-.38.77-.45 1.56-.01.1.07 1.4.17 1.79.2.78.58 1.5 1.11 2.1.58.64 1.33 1.12 2.16 1.37.45.14 3.18.37 3.47.4 6.58.66 10.21-.94 16.65-3.47 31.89-12.72 123.13-8.87 143.22-5.07 8.73 1.74 22.2 3.56 30.55 8.34 1.36.74 2.64 1.29 3.81 1.64.58.18 1.15.26 1.66.39 1.28.3 2.64.03 2.87-.02.46-.09.9-.22 1.3-.39.81-.34 1.46-.84 1.77-1.45.22-.43.5-1.27.63-1.96.09-.26.05-1.18-.01-1.66zM41.8 187.33h-8.47l7.46 38.09h9.8l5.7-25.79 5.75 25.79h9.8l7.83-38.09h-7.94l-4.79 29.14-6.23-29.14h-8.26l-6.34 29.04-4.31-29.04zm66.78 0H84.76v38.09h24.35v-6.39h-16.3v-9.48h13.9v-6.39h-13.9v-9.43h15.77v-6.4zm6.79 0v38.09h14.17c8.95 0 13.21-4.26 13.21-10.65 0-8.15-7.19-9.06-7.19-9.06s5.11-2.18 5.11-8.52c0-6.39-4.1-9.86-12.57-9.86h-12.73zm8.05 16.04V193.3h2.66c4.74 0 6.66 1.6 6.66 4.95 0 3.41-1.92 5.11-5.86 5.11l-3.46.01zm0 16.09v-10.28h4.9c4.1 0 6.34 1.6 6.34 5.01s-2.08 5.27-6.82 5.27h-4.42zm28.6 6.5c2.45 0 4.15-1.71 4.15-4.1 0-2.45-1.7-3.94-4.05-3.94-2.45 0-4.21 1.54-4.21 4.05.01 2.23 1.4 3.99 4.11 3.99zm11.06-38.63v38.09h9.48c13.11 0 19.82-7.46 19.82-19.39 0-11.67-5.91-18.7-19.02-18.7h-10.28zm8.05 31.7v-25.3h2.18c7.35 0 10.92 4.37 10.92 12.84 0 8.42-3.46 12.47-10.87 12.47l-2.23-.01zm51.1-31.7h-23.81v38.09h24.35v-6.39h-16.3v-9.48h13.9v-6.39h-13.9v-9.43h15.77v-6.4z"/></svg> | |||||
| @@ -0,0 +1 @@ | |||||
| <svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 213.993 213.991"><path fill="#2e7daa" d="M0 0h213.993v213.991H0z"/><path fill="#fffffc" d="M106.995 5.59l101.403 101.404-101.401 101.402L5.594 106.993z"/><path d="M106.996 41.082l65.91 65.91-65.91 65.912-65.911-65.911z"/><path fill="#fffffc" d="M106.99 78.601l28.392 28.392-28.39 28.391-28.393-28.392z"/></svg> | |||||