Compare commits
8 Commits
ec3919ddfb
...
4a1c6e1a72
Author | SHA1 | Date | |
---|---|---|---|
4a1c6e1a72 | |||
d76c0d31f9 | |||
21178d9a1f | |||
39e94d8bdc | |||
3ed53ac05e | |||
e8df5bf019 | |||
0cce30679a | |||
cfaf905c90 |
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,json,yml}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
47
.env
Normal file
47
.env
Normal file
@ -0,0 +1,47 @@
|
||||
# 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=0ac2ef94aa7fbf17c0fa7caca5d59ba8
|
||||
###< 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://MyComments:Plentiful-Easiness-Gracious6-Boggle@127.0.0.1:3306/MyComments?serverVersion=11.1.2-MariaDB&charset=utf8mb4"
|
||||
DATABASE_URL="postgresql://app:Liqueur-Battalion7-Woof-Spokesman@127.0.0.1:5432/app?serverVersion=15&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/messenger ###
|
||||
# Choose one of the transports below
|
||||
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
|
||||
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
||||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
###< symfony/messenger ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
# MAILER_DSN=null://null
|
||||
###< symfony/mailer ###
|
||||
|
||||
###> symfony/lock ###
|
||||
# Choose one of the stores below
|
||||
# postgresql+advisory://db_user:db_password@localhost/db_name
|
||||
LOCK_DSN=flock
|
||||
###< symfony/lock ###
|
6
.env.test
Normal file
6
.env.test
Normal file
@ -0,0 +1,6 @@
|
||||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/.yarn/** linguist-vendored
|
||||
/.yarn/releases/* binary
|
||||
/.yarn/plugins/**/* binary
|
||||
/.pnp.* binary linguist-generated
|
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> symfony/phpunit-bridge ###
|
||||
.phpunit.result.cache
|
||||
/phpunit.xml
|
||||
###< symfony/phpunit-bridge ###
|
||||
|
||||
# IDE folders
|
||||
.idea
|
||||
###> symfony/webpack-encore-bundle ###
|
||||
/node_modules/
|
||||
/public/build/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
###< symfony/webpack-encore-bundle ###
|
2
.yarnrc.yml
Normal file
2
.yarnrc.yml
Normal file
@ -0,0 +1,2 @@
|
||||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
||||
nodeLinker: node-modules
|
20
LICENSE
20
LICENSE
@ -1,9 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 skylord123
|
||||
Copyright (c) 2023 MyComments
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights 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 to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
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 to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
28
assets/app.js
Normal file
28
assets/app.js
Normal file
@ -0,0 +1,28 @@
|
||||
import './bootstrap.js';
|
||||
/*
|
||||
* Welcome to your app's main JavaScript file!
|
||||
*
|
||||
* We recommend including the built version of this JavaScript file
|
||||
* (and its CSS file) in your base layout (base.html.twig).
|
||||
*/
|
||||
|
||||
// any CSS you import will output into a single css file (app.css in this case)
|
||||
import './styles/app.scss';
|
||||
|
||||
require('fontawesome-free/js/all.js');
|
||||
|
||||
import * as mdb from 'mdb-ui-kit'; // lib
|
||||
import { Input } from 'mdb-ui-kit'; // module
|
||||
|
||||
const $ = require('jquery');
|
||||
// this "modifies" the jquery module: adding behavior to it
|
||||
// the bootstrap module doesn't export/return anything
|
||||
require('bootstrap');
|
||||
|
||||
// or you can include specific pieces
|
||||
// require('bootstrap/js/dist/tooltip');
|
||||
// require('bootstrap/js/dist/popover');
|
||||
|
||||
$(document).ready(function() {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
});
|
12
assets/bootstrap.js
vendored
Normal file
12
assets/bootstrap.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// assets/bootstrap.js
|
||||
import { startStimulusApp } from '@symfony/stimulus-bridge';
|
||||
|
||||
// Registers Stimulus controllers from controllers.json and in the controllers/ directory
|
||||
export const app = startStimulusApp(require.context(
|
||||
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
|
||||
true,
|
||||
/\.(j|t)sx?$/
|
||||
));
|
||||
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
4
assets/controllers.json
Normal file
4
assets/controllers.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"controllers": [],
|
||||
"entrypoints": []
|
||||
}
|
21
assets/controllers/domains_controller.js
Normal file
21
assets/controllers/domains_controller.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/*
|
||||
* This is an example Stimulus controller!
|
||||
*
|
||||
* Any element with a data-controller="hello" attribute will cause
|
||||
* this controller to be executed. The name "hello" comes from the filename:
|
||||
* hello_controller.js -> "hello"
|
||||
*
|
||||
* Delete this file or adapt it for your use!
|
||||
*/
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
//this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/domains_controller.js';
|
||||
}
|
||||
|
||||
static targets = ['name', 'output']
|
||||
greet() {
|
||||
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
|
||||
}
|
||||
}
|
67
assets/styles/app.scss
Normal file
67
assets/styles/app.scss
Normal file
@ -0,0 +1,67 @@
|
||||
body {
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
||||
// customize some Bootstrap variables
|
||||
$primary: darken(#428bca, 20%);
|
||||
|
||||
// the ~ allows you to reference things in node_modules
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
@import '~mdb-ui-kit/css/mdb.min.css';
|
||||
|
||||
// importing core styling file
|
||||
@import "~fontawesome-free/scss/fontawesome.scss";
|
||||
|
||||
// our project needs Classic Solid, Brands, and Sharp Solid
|
||||
@import "~fontawesome-free/scss/solid.scss";
|
||||
@import "~fontawesome-free/scss/brands.scss";
|
||||
@import "~fontawesome-free/scss/regular.scss";
|
||||
|
||||
/* for login page */
|
||||
.gradient-custom {
|
||||
/* fallback for old browsers */
|
||||
background: #6a11cb;
|
||||
|
||||
/* Chrome 10-25, Safari 5.1-6 */
|
||||
background: -webkit-linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1));
|
||||
|
||||
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||
background: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1))
|
||||
}
|
||||
|
||||
@media (min-width: 991.98px) {
|
||||
main {
|
||||
padding-left: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 58px 0 0; /* Height of navbar */
|
||||
box-shadow: 0 2px 5px 0 rgb(0 0 0 / 5%), 0 2px 10px 0 rgb(0 0 0 / 5%);
|
||||
width: 240px;
|
||||
z-index: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.sidebar .active {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px 0 rgb(0 0 0 / 16%), 0 2px 10px 0 rgb(0 0 0 / 12%);
|
||||
}
|
||||
|
||||
.sidebar-sticky {
|
||||
position: relative;
|
||||
top: 0;
|
||||
height: calc(100vh - 48px);
|
||||
padding-top: 0.5rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
|
||||
}
|
17
bin/console
Executable file
17
bin/console
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
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);
|
||||
};
|
19
bin/phpunit
Executable file
19
bin/phpunit
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||
}
|
108
composer.json
Normal file
108
composer.json
Normal file
@ -0,0 +1,108 @@
|
||||
{
|
||||
"type": "project",
|
||||
"license": "proprietary",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-intl": "*",
|
||||
"doctrine/doctrine-bundle": "^2.10",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.2",
|
||||
"doctrine/orm": "^2.16",
|
||||
"gedmo/doctrine-extensions": "^3.13",
|
||||
"phpdocumentor/reflection-docblock": "^5.3",
|
||||
"phpstan/phpdoc-parser": "^1.24",
|
||||
"symfony/asset": "6.3.*",
|
||||
"symfony/console": "6.3.*",
|
||||
"symfony/doctrine-messenger": "6.3.*",
|
||||
"symfony/dotenv": "6.3.*",
|
||||
"symfony/expression-language": "6.3.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "6.3.*",
|
||||
"symfony/framework-bundle": "6.3.*",
|
||||
"symfony/http-client": "6.3.*",
|
||||
"symfony/intl": "6.3.*",
|
||||
"symfony/lock": "6.3.*",
|
||||
"symfony/mailer": "6.3.*",
|
||||
"symfony/mime": "6.3.*",
|
||||
"symfony/monolog-bundle": "^3.0",
|
||||
"symfony/notifier": "6.3.*",
|
||||
"symfony/process": "6.3.*",
|
||||
"symfony/property-access": "6.3.*",
|
||||
"symfony/property-info": "6.3.*",
|
||||
"symfony/rate-limiter": "6.3.*",
|
||||
"symfony/runtime": "6.3.*",
|
||||
"symfony/security-bundle": "6.3.*",
|
||||
"symfony/serializer": "6.3.*",
|
||||
"symfony/stimulus-bundle": "^2.11",
|
||||
"symfony/string": "6.3.*",
|
||||
"symfony/translation": "6.3.*",
|
||||
"symfony/twig-bundle": "6.3.*",
|
||||
"symfony/validator": "6.3.*",
|
||||
"symfony/web-link": "6.3.*",
|
||||
"symfony/webpack-encore-bundle": "^2.0",
|
||||
"symfony/yaml": "6.3.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
},
|
||||
"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": "*"
|
||||
},
|
||||
"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": false,
|
||||
"require": "6.3.*"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"symfony/browser-kit": "6.3.*",
|
||||
"symfony/css-selector": "6.3.*",
|
||||
"symfony/debug-bundle": "6.3.*",
|
||||
"symfony/maker-bundle": "^1.0",
|
||||
"symfony/phpunit-bridge": "^6.3",
|
||||
"symfony/stopwatch": "6.3.*",
|
||||
"symfony/web-profiler-bundle": "6.3.*"
|
||||
}
|
||||
}
|
10042
composer.lock
generated
Normal file
10042
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
config/bundles.php
Normal file
16
config/bundles.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
];
|
5
config/config.yml
Normal file
5
config/config.yml
Normal file
@ -0,0 +1,5 @@
|
||||
stof_doctrine_extensions:
|
||||
orm:
|
||||
default:
|
||||
sluggable: true
|
||||
timestampable: true
|
19
config/packages/cache.yaml
Normal file
19
config/packages/cache.yaml
Normal file
@ -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
|
5
config/packages/debug.yaml
Normal file
5
config/packages/debug.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
when@dev:
|
||||
debug:
|
||||
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
|
||||
# See the "server:dump" command to start a new server.
|
||||
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
|
48
config/packages/doctrine.yaml
Normal file
48
config/packages/doctrine.yaml
Normal file
@ -0,0 +1,48 @@
|
||||
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: '15'
|
||||
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
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:
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
||||
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
|
6
config/packages/doctrine_migrations.yaml
Normal file
6
config/packages/doctrine_migrations.yaml
Normal file
@ -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
|
25
config/packages/framework.yaml
Normal file
25
config/packages/framework.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
# 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:
|
||||
handler_id: null
|
||||
cookie_secure: auto
|
||||
cookie_samesite: lax
|
||||
storage_factory_id: session.storage.factory.native
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
php_errors:
|
||||
log: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_factory_id: session.storage.factory.mock_file
|
2
config/packages/lock.yaml
Normal file
2
config/packages/lock.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
framework:
|
||||
lock: '%env(LOCK_DSN)%'
|
3
config/packages/mailer.yaml
Normal file
3
config/packages/mailer.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
framework:
|
||||
mailer:
|
||||
dsn: '%env(MAILER_DSN)%'
|
24
config/packages/messenger.yaml
Normal file
24
config/packages/messenger.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
framework:
|
||||
messenger:
|
||||
failure_transport: failed
|
||||
|
||||
transports:
|
||||
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||
async:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
options:
|
||||
use_notify: true
|
||||
check_delayed_interval: 60000
|
||||
retry_strategy:
|
||||
max_retries: 3
|
||||
multiplier: 2
|
||||
failed: 'doctrine://default?queue_name=failed'
|
||||
# sync: 'sync://'
|
||||
|
||||
routing:
|
||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
|
||||
Symfony\Component\Notifier\Message\ChatMessage: async
|
||||
Symfony\Component\Notifier\Message\SmsMessage: async
|
||||
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
61
config/packages/monolog.yaml
Normal file
61
config/packages/monolog.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
monolog:
|
||||
channels:
|
||||
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
channels: ["!event"]
|
||||
# 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
|
12
config/packages/notifier.yaml
Normal file
12
config/packages/notifier.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
framework:
|
||||
notifier:
|
||||
chatter_transports:
|
||||
texter_transports:
|
||||
channel_policy:
|
||||
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||
urgent: ['email']
|
||||
high: ['email']
|
||||
medium: ['email']
|
||||
low: ['email']
|
||||
admin_recipients:
|
||||
- { email: admin@example.com }
|
12
config/packages/routing.yaml
Normal file
12
config/packages/routing.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
framework:
|
||||
router:
|
||||
utf8: true
|
||||
|
||||
# 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
|
79
config/packages/security.yaml
Normal file
79
config/packages/security.yaml
Normal file
@ -0,0 +1,79 @@
|
||||
framework:
|
||||
rate_limiter:
|
||||
# define 2 rate limiters (one for username+IP, the other for IP)
|
||||
username_ip_login:
|
||||
policy: token_bucket
|
||||
limit: 5
|
||||
rate: { interval: '5 minutes' }
|
||||
|
||||
ip_login:
|
||||
policy: sliding_window
|
||||
limit: 40
|
||||
interval: '30 minutes'
|
||||
|
||||
services:
|
||||
# our custom login rate limiter
|
||||
app.login_rate_limiter:
|
||||
class: Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter
|
||||
arguments:
|
||||
# globalFactory is the limiter for IP
|
||||
$globalFactory: '@limiter.ip_login'
|
||||
# localFactory is the limiter for username+IP
|
||||
$localFactory: '@limiter.username_ip_login'
|
||||
|
||||
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
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_user_provider
|
||||
|
||||
form_login:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
|
||||
#logout:
|
||||
# path: app_logout
|
||||
# where to redirect after logout
|
||||
# target: app_any_route
|
||||
|
||||
# configure the maximum login attempts
|
||||
login_throttling:
|
||||
limiter: app.login_rate_limiter
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||
# switch_user: true
|
||||
|
||||
# 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
|
15
config/packages/translation.yaml
Normal file
15
config/packages/translation.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
framework:
|
||||
default_locale: en
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
fallbacks:
|
||||
- en
|
||||
# providers:
|
||||
# crowdin:
|
||||
# dsn: '%env(CROWDIN_DSN)%'
|
||||
# loco:
|
||||
# dsn: '%env(LOCO_DSN)%'
|
||||
# lokalise:
|
||||
# dsn: '%env(LOKALISE_DSN)%'
|
||||
# phrase:
|
||||
# dsn: '%env(PHRASE_DSN)%'
|
7
config/packages/twig.yaml
Normal file
7
config/packages/twig.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
twig:
|
||||
form_themes: ['bootstrap_5_layout.html.twig']
|
||||
default_path: '%kernel.project_dir%/templates'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
13
config/packages/validator.yaml
Normal file
13
config/packages/validator.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
framework:
|
||||
validation:
|
||||
email_validation_mode: html5
|
||||
|
||||
# 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
|
17
config/packages/web_profiler.yaml
Normal file
17
config/packages/web_profiler.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
only_exceptions: false
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
web_profiler:
|
||||
toolbar: false
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { collect: false }
|
45
config/packages/webpack_encore.yaml
Normal file
45
config/packages/webpack_encore.yaml
Normal file
@ -0,0 +1,45 @@
|
||||
webpack_encore:
|
||||
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
|
||||
output_path: '%kernel.project_dir%/public/build'
|
||||
# If multiple builds are defined (as shown below), you can disable the default build:
|
||||
# output_path: false
|
||||
|
||||
# Set attributes that will be rendered on all script and link tags
|
||||
script_attributes:
|
||||
defer: true
|
||||
# Uncomment (also under link_attributes) if using Turbo Drive
|
||||
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
|
||||
# 'data-turbo-track': reload
|
||||
# link_attributes:
|
||||
# Uncomment if using Turbo Drive
|
||||
# 'data-turbo-track': reload
|
||||
|
||||
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
|
||||
# crossorigin: 'anonymous'
|
||||
|
||||
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
|
||||
# preload: true
|
||||
|
||||
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
|
||||
# strict_mode: false
|
||||
|
||||
# If you have multiple builds:
|
||||
# builds:
|
||||
# frontend: '%kernel.project_dir%/public/frontend/build'
|
||||
|
||||
# pass the build name as the 3rd argument to the Twig functions
|
||||
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
|
||||
|
||||
framework:
|
||||
assets:
|
||||
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
|
||||
|
||||
#when@prod:
|
||||
# webpack_encore:
|
||||
# # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||
# # Available in version 1.2
|
||||
# cache: true
|
||||
|
||||
#when@test:
|
||||
# webpack_encore:
|
||||
# strict_mode: false
|
5
config/preload.php
Normal file
5
config/preload.php
Normal file
@ -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';
|
||||
}
|
5
config/routes.yaml
Normal file
5
config/routes.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
controllers:
|
||||
resource:
|
||||
path: ../src/Controller/
|
||||
namespace: App\Controller
|
||||
type: attribute
|
4
config/routes/framework.yaml
Normal file
4
config/routes/framework.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
prefix: /_error
|
8
config/routes/web_profiler.yaml
Normal file
8
config/routes/web_profiler.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
prefix: /_profiler
|
29
config/services.yaml
Normal file
29
config/services.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
# 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'
|
||||
|
||||
# please note that last definitions always *replace* previous ones
|
||||
gedmo.listener.timestampable:
|
||||
class: Gedmo\Timestampable\TimestampableListener
|
||||
tags:
|
||||
- { name: doctrine.event_subscriber, connection: default }
|
||||
calls:
|
||||
- [ setAnnotationReader, [ '@annotation_reader' ] ]
|
14
docker-compose.override.yml
Normal file
14
docker-compose.override.yml
Normal file
@ -0,0 +1,14 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
ports:
|
||||
- "5432"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
mailer:
|
||||
image: schickling/mailcatcher
|
||||
ports: ["1025", "1080"]
|
||||
###< symfony/mailer ###
|
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
image: postgres:${POSTGRES_VERSION:-15}-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-app}
|
||||
# You should definitely change the password in production
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-Liqueur-Battalion7-Woof-Spokesman}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-app}
|
||||
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
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
volumes:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database_data:
|
||||
###< doctrine/doctrine-bundle ###
|
0
migrations/.gitignore
vendored
Normal file
0
migrations/.gitignore
vendored
Normal file
37
migrations/Version20230909083131.php
Normal file
37
migrations/Version20230909083131.php
Normal file
@ -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 Version20230909083131 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 domain (id INT AUTO_INCREMENT NOT NULL, domain VARCHAR(255) NOT NULL, owner_token VARCHAR(64) NOT NULL, name VARCHAR(255) NOT NULL, enabled TINYINT(1) NOT NULL, default_sort_policy VARCHAR(32) NOT NULL, updated_at DATETIME DEFAULT NULL, created_at DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE email (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, unsubscribe_token VARCHAR(255) NOT NULL, last_notification_sent_at DATETIME DEFAULT NULL, pending_emails INT NOT NULL, send_reply_notifications TINYINT(1) NOT NULL, send_moderator_notifications TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE `user` (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE domain');
|
||||
$this->addSql('DROP TABLE email');
|
||||
$this->addSql('DROP TABLE `user`');
|
||||
$this->addSql('DROP TABLE messenger_messages');
|
||||
}
|
||||
}
|
31
migrations/Version20230909100148.php
Normal file
31
migrations/Version20230909100148.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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 Version20230909100148 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 user ADD display_name VARCHAR(64) NOT NULL, ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE `user` DROP display_name, DROP created_at, DROP updated_at');
|
||||
}
|
||||
}
|
49
migrations/Version20231014080536.php
Normal file
49
migrations/Version20231014080536.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?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 Version20231014080536 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 comment (id INT AUTO_INCREMENT NOT NULL, page_id INT NOT NULL, user_id INT DEFAULT NULL, parent_comment_id INT DEFAULT NULL, deleted_by_user_id INT DEFAULT NULL, markdown LONGTEXT NOT NULL, html LONGTEXT NOT NULL, score INT NOT NULL, state VARCHAR(32) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL, deleted_at DATETIME DEFAULT NULL, INDEX IDX_9474526CC4663E4 (page_id), INDEX IDX_9474526CA76ED395 (user_id), INDEX IDX_9474526CBF2AF943 (parent_comment_id), INDEX IDX_9474526CFCF2A97A (deleted_by_user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE domain_page (id INT AUTO_INCREMENT NOT NULL, domain_id INT NOT NULL, path LONGTEXT NOT NULL, locked TINYINT(1) NOT NULL, comment_count INT NOT NULL, title LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL, deleted_at DATETIME DEFAULT NULL, INDEX IDX_B9D69B47115F0EE5 (domain_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE page_view (id INT AUTO_INCREMENT NOT NULL, page_id INT NOT NULL, user_id INT DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_7939B754C4663E4 (page_id), INDEX IDX_7939B754A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CC4663E4 FOREIGN KEY (page_id) REFERENCES domain_page (id)');
|
||||
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CA76ED395 FOREIGN KEY (user_id) REFERENCES `user` (id)');
|
||||
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CBF2AF943 FOREIGN KEY (parent_comment_id) REFERENCES comment (id)');
|
||||
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CFCF2A97A FOREIGN KEY (deleted_by_user_id) REFERENCES `user` (id)');
|
||||
$this->addSql('ALTER TABLE domain_page ADD CONSTRAINT FK_B9D69B47115F0EE5 FOREIGN KEY (domain_id) REFERENCES domain (id)');
|
||||
$this->addSql('ALTER TABLE page_view ADD CONSTRAINT FK_7939B754C4663E4 FOREIGN KEY (page_id) REFERENCES domain_page (id)');
|
||||
$this->addSql('ALTER TABLE page_view ADD CONSTRAINT FK_7939B754A76ED395 FOREIGN KEY (user_id) REFERENCES `user` (id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CC4663E4');
|
||||
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CA76ED395');
|
||||
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CBF2AF943');
|
||||
$this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526CFCF2A97A');
|
||||
$this->addSql('ALTER TABLE domain_page DROP FOREIGN KEY FK_B9D69B47115F0EE5');
|
||||
$this->addSql('ALTER TABLE page_view DROP FOREIGN KEY FK_7939B754C4663E4');
|
||||
$this->addSql('ALTER TABLE page_view DROP FOREIGN KEY FK_7939B754A76ED395');
|
||||
$this->addSql('DROP TABLE comment');
|
||||
$this->addSql('DROP TABLE domain_page');
|
||||
$this->addSql('DROP TABLE page_view');
|
||||
}
|
||||
}
|
12006
package-lock.json
generated
Normal file
12006
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "MyComments",
|
||||
"packageManager": "yarn@3.6.3",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.17",
|
||||
"@babel/preset-env": "^7.22.15",
|
||||
"@hotwired/stimulus": "^3.0.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@symfony/webpack-encore": "^4.4.0",
|
||||
"babel-loader": "^9.1.3",
|
||||
"bootstrap": "^5.3.1",
|
||||
"core-js": "3.32.2",
|
||||
"jquery": "^3.7.1",
|
||||
"sass": "^1.66.1",
|
||||
"sass-loader": "^13.0.0",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-notifier": "^1.15.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@symfony/stimulus-bridge": "^3.2.2",
|
||||
"fontawesome-free": "^1.0.4",
|
||||
"mdb-ui-kit": "^6.4.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev-server": "encore dev-server",
|
||||
"dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build": "encore production --progress"
|
||||
}
|
||||
}
|
38
phpunit.xml.dist
Normal file
38
phpunit.xml.dist
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
convertDeprecationsToExceptions="false"
|
||||
>
|
||||
<php>
|
||||
<ini name="display_errors" value="1" />
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<server name="APP_ENV" value="test" force="true" />
|
||||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
|
||||
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Project Test Suite">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
|
||||
<listeners>
|
||||
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
|
||||
</listeners>
|
||||
|
||||
<extensions>
|
||||
</extensions>
|
||||
</phpunit>
|
9
public/index.php
Normal file
9
public/index.php
Normal file
@ -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']);
|
||||
};
|
262
src/Command/AddUserCommand.php
Normal file
262
src/Command/AddUserCommand.php
Normal file
@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
/**
|
||||
* A console command that creates users and stores them in the database.
|
||||
*
|
||||
* To use this command, open a terminal window, enter into your project
|
||||
* directory and execute the following:
|
||||
*
|
||||
* $ php bin/console app:add-user
|
||||
*
|
||||
* To output detailed information, increase the command verbosity:
|
||||
*
|
||||
* $ php bin/console app:add-user -vv
|
||||
*
|
||||
* See https://symfony.com/doc/current/console.html
|
||||
*
|
||||
* We use the default services.yaml configuration, so command classes are registered as services.
|
||||
* See https://symfony.com/doc/current/console/commands_as_services.html
|
||||
*
|
||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||
*/
|
||||
#[AsCommand(
|
||||
name: 'app:add-user',
|
||||
description: 'Creates users and stores them in the database'
|
||||
)]
|
||||
final class AddUserCommand extends Command
|
||||
{
|
||||
private SymfonyStyle $io;
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||
private readonly ValidatorInterface $validator,
|
||||
private readonly UserRepository $users
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setHelp($this->getCommandHelp())
|
||||
// commands can optionally define arguments and/or options (mandatory and optional)
|
||||
// see https://symfony.com/doc/current/components/console/console_arguments.html
|
||||
->addArgument('display-name', InputArgument::OPTIONAL, 'The display name of the new user')
|
||||
->addArgument('password', InputArgument::OPTIONAL, 'The plain password of the new user')
|
||||
->addArgument('email', InputArgument::OPTIONAL, 'The email of the new user')
|
||||
->addOption('admin', null, InputOption::VALUE_NONE, 'If set, the user is created as an administrator')
|
||||
->addOption('owner', null, InputOption::VALUE_NONE, 'If set, the user is created as an owner')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* This optional method is the first one executed for a command after configure()
|
||||
* and is useful to initialize properties based on the input arguments and options.
|
||||
*/
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
// SymfonyStyle is an optional feature that Symfony provides so you can
|
||||
// apply a consistent look to the commands of your application.
|
||||
// See https://symfony.com/doc/current/console/style.html
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is executed after initialize() and before execute(). Its purpose
|
||||
* is to check if some of the options/arguments are missing and interactively
|
||||
* ask the user for those values.
|
||||
*
|
||||
* This method is completely optional. If you are developing an internal console
|
||||
* command, you probably should not implement this method because it requires
|
||||
* quite a lot of work. However, if the command is meant to be used by external
|
||||
* users, this method is a nice way to fall back and prevent errors.
|
||||
*/
|
||||
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
if (null !== $input->getArgument('password') && null !== $input->getArgument('email') && null !== $input->getArgument('display-name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->title('Add User Command Interactive Wizard');
|
||||
$this->io->text([
|
||||
'If you prefer to not use this interactive wizard, provide the',
|
||||
'arguments required by this command as follows:',
|
||||
'',
|
||||
' $ php bin/console app:add-user display-name password email@example.com',
|
||||
'',
|
||||
'Now we\'ll ask you for the value of all the missing command arguments.',
|
||||
]);
|
||||
|
||||
// Ask for the display name if it's not defined
|
||||
$displayName = $input->getArgument('display-name');
|
||||
if (null !== $displayName) {
|
||||
$this->io->text(' > <info>Display Name</info>: '.$displayName);
|
||||
} else {
|
||||
$displayName = $this->io->ask('display Name', null, function($answer){
|
||||
$validation = $this->validator->validatePropertyValue(User::class, 'displayName', $answer);
|
||||
if($validation->count()) {
|
||||
foreach($validation as $validationError) {
|
||||
$this->io->error("{$validationError->getMessage()}");
|
||||
}
|
||||
throw new Exception("Invalid display name");
|
||||
}
|
||||
});
|
||||
$input->setArgument('display-name', $displayName);
|
||||
}
|
||||
|
||||
// Ask for the password if it's not defined
|
||||
/** @var string|null $password */
|
||||
$password = $input->getArgument('password');
|
||||
|
||||
if (null !== $password) {
|
||||
$this->io->text(' > <info>Password</info>: '.u('*')->repeat(u($password)->length()));
|
||||
} else {
|
||||
$password = $this->io->askHidden('Password (your type will be hidden)', function($answer){
|
||||
// from the CLI we don't really want to impose too many restrictions on the password
|
||||
if (empty($plainPassword)) {
|
||||
throw new InvalidArgumentException('The password can not be empty.');
|
||||
}
|
||||
|
||||
if (u($plainPassword)->trim()->length() < 6) {
|
||||
throw new InvalidArgumentException('The password must be at least 6 characters long.');
|
||||
}
|
||||
|
||||
return $answer;
|
||||
});
|
||||
$input->setArgument('password', $password);
|
||||
}
|
||||
|
||||
// Ask for the email if it's not defined
|
||||
$email = $input->getArgument('email');
|
||||
if (null !== $email) {
|
||||
$this->io->text(' > <info>Email</info>: '.$email);
|
||||
} else {
|
||||
$email = $this->io->ask('Email', null, $this->validator->validateEmail(...));
|
||||
$input->setArgument('email', $email);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is executed after interact() and initialize(). It usually
|
||||
* contains the logic to execute to complete this command task.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$stopwatch = new Stopwatch();
|
||||
$stopwatch->start('add-user-command');
|
||||
|
||||
/** @var string $plainPassword */
|
||||
$plainPassword = $input->getArgument('password');
|
||||
|
||||
/** @var string $email */
|
||||
$email = $input->getArgument('email');
|
||||
|
||||
/** @var string $displayName */
|
||||
$displayName = $input->getArgument('display-name');
|
||||
|
||||
$isAdmin = $input->getOption('admin');
|
||||
$isOwner = $input->getOption('owner');
|
||||
|
||||
// make sure to validate the user data is correct
|
||||
$this->validateUserData($plainPassword, $email, $displayName);
|
||||
|
||||
// create the user and hash its password
|
||||
$user = new User();
|
||||
$user->setDisplayName($displayName)
|
||||
->setEmail($email)
|
||||
->setRoles([$isAdmin ? User::ROLE_ADMIN : User::ROLE_USER])
|
||||
->addRole(User::ROLE_USER);
|
||||
|
||||
if($isAdmin) {
|
||||
$user->addRole(User::ROLE_ADMIN);
|
||||
}
|
||||
|
||||
if($isOwner) {
|
||||
$user->addRole(User::ROLE_OWNER);
|
||||
}
|
||||
|
||||
// See https://symfony.com/doc/5.4/security.html#registering-the-user-hashing-passwords
|
||||
$hashedPassword = $this->passwordHasher->hashPassword($user, $plainPassword);
|
||||
$user->setPassword($hashedPassword);
|
||||
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->io->success(sprintf('%s was successfully created: %s (%s)', $isAdmin ? 'Administrator user' : 'User', $user->getDisplayName(), $user->getEmail()));
|
||||
|
||||
$event = $stopwatch->stop('add-user-command');
|
||||
if ($output->isVerbose()) {
|
||||
$this->io->comment(sprintf('New user database id: %d / Elapsed time: %.2f ms / Consumed memory: %.2f MB', $user->getId(), $event->getDuration(), $event->getMemory() / (1024 ** 2)));
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function validateUserData(string $plainPassword, string $email, string $displayName): void
|
||||
{
|
||||
// validate password and email if is not this input means interactive.
|
||||
$this->validator->validatePassword($plainPassword);
|
||||
$this->validator->validateEmail($email);
|
||||
$this->validator->validateDisplayName($displayName);
|
||||
|
||||
// check if a user with the same email already exists.
|
||||
$existingEmail = $this->users->findOneBy(['email' => $email]);
|
||||
|
||||
if (null !== $existingEmail) {
|
||||
throw new RuntimeException(sprintf('There is already a user registered with the "%s" email.', $email));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The command help is usually included in the configure() method, but when
|
||||
* it's too long, it's better to define a separate method to maintain the
|
||||
* code readability.
|
||||
*/
|
||||
private function getCommandHelp(): string
|
||||
{
|
||||
return <<<'HELP'
|
||||
The <info>%command.name%</info> command creates new users and saves them in the database:
|
||||
|
||||
<info>php %command.full_name%</info> <comment>display-name password email</comment>
|
||||
|
||||
By default the command creates regular users. To create administrator users,
|
||||
add the <comment>--admin</comment> option:
|
||||
|
||||
<info>php %command.full_name%</info> display-name password email <comment>--admin</comment>
|
||||
|
||||
If you omit any of the three required arguments, the command will ask you to
|
||||
provide the missing values:
|
||||
|
||||
# command will ask you for the email
|
||||
<info>php %command.full_name%</info> <comment>display-name password</comment>
|
||||
|
||||
# command will ask you for the email and password
|
||||
<info>php %command.full_name%</info> <comment>display-name</comment>
|
||||
|
||||
# command will ask you for all arguments
|
||||
<info>php %command.full_name%</info>
|
||||
HELP;
|
||||
}
|
||||
}
|
0
src/Controller/.gitignore
vendored
Normal file
0
src/Controller/.gitignore
vendored
Normal file
20
src/Controller/DashboardController.php
Normal file
20
src/Controller/DashboardController.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
class DashboardController extends AbstractController
|
||||
{
|
||||
#[Route('/', name: 'app_dashboard')]
|
||||
public function index(): Response
|
||||
{
|
||||
return $this->render('dashboard/index.html.twig', [
|
||||
'controller_name' => 'DashboardController',
|
||||
]);
|
||||
}
|
||||
}
|
85
src/Controller/DomainController.php
Normal file
85
src/Controller/DomainController.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Domain;
|
||||
use App\Form\DomainType;
|
||||
use App\Repository\DomainRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use Symfony\Component\String\ByteString;
|
||||
|
||||
#[Route('/domain')]
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
class DomainController extends AbstractController
|
||||
{
|
||||
#[Route('/', name: 'app_domain_index', methods: ['GET'])]
|
||||
public function index(DomainRepository $domainRepository): Response
|
||||
{
|
||||
return $this->render('domain/index.html.twig', [
|
||||
'domains' => $domainRepository->findAll(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/new', name: 'app_domain_new', methods: ['GET', 'POST'])]
|
||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$domain = new Domain();
|
||||
$domain->setOwnerToken(ByteString::fromRandom(64)->toString());
|
||||
$form = $this->createForm(DomainType::class, $domain);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$entityManager->persist($domain);
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('app_domain_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
|
||||
return $this->render('domain/new.html.twig', [
|
||||
'domain' => $domain,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'app_domain_show', methods: ['GET'])]
|
||||
public function show(Domain $domain): Response
|
||||
{
|
||||
return $this->render('domain/show.html.twig', [
|
||||
'domain' => $domain,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}/edit', name: 'app_domain_edit', methods: ['GET', 'POST'])]
|
||||
public function edit(Request $request, Domain $domain, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$form = $this->createForm(DomainType::class, $domain);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('app_domain_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
|
||||
return $this->render('domain/edit.html.twig', [
|
||||
'domain' => $domain,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'app_domain_delete', methods: ['POST'])]
|
||||
public function delete(Request $request, Domain $domain, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if ($this->isCsrfTokenValid('delete'.$domain->getId(), $request->request->get('_token'))) {
|
||||
$entityManager->remove($domain);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('app_domain_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
}
|
83
src/Controller/PageController.php
Normal file
83
src/Controller/PageController.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\DomainPage;
|
||||
use App\Form\DomainPageType;
|
||||
use App\Repository\DomainPageRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
#[Route('/page')]
|
||||
#[IsGranted('IS_AUTHENTICATED_FULLY')]
|
||||
class PageController extends AbstractController
|
||||
{
|
||||
#[Route('/', name: 'app_page_index', methods: ['GET'])]
|
||||
public function index(DomainPageRepository $domainPageRepository): Response
|
||||
{
|
||||
return $this->render('page/index.html.twig', [
|
||||
'domain_pages' => $domainPageRepository->findAll(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/new', name: 'app_page_new', methods: ['GET', 'POST'])]
|
||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$domainPage = new DomainPage();
|
||||
$form = $this->createForm(DomainPageType::class, $domainPage);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$entityManager->persist($domainPage);
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('app_page_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
|
||||
return $this->render('page/new.html.twig', [
|
||||
'domain_page' => $domainPage,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'app_page_show', methods: ['GET'])]
|
||||
public function show(DomainPage $domainPage): Response
|
||||
{
|
||||
return $this->render('page/show.html.twig', [
|
||||
'domain_page' => $domainPage,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}/edit', name: 'app_page_edit', methods: ['GET', 'POST'])]
|
||||
public function edit(Request $request, DomainPage $domainPage, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$form = $this->createForm(DomainPageType::class, $domainPage);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('app_page_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
|
||||
return $this->render('page/edit.html.twig', [
|
||||
'domain_page' => $domainPage,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'app_page_delete', methods: ['POST'])]
|
||||
public function delete(Request $request, DomainPage $domainPage, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if ($this->isCsrfTokenValid('delete'.$domainPage->getId(), $request->request->get('_token'))) {
|
||||
$entityManager->remove($domainPage);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('app_page_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
}
|
42
src/Controller/SecurityController.php
Normal file
42
src/Controller/SecurityController.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route('/login', name: 'app_login')]
|
||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
// redirect already logged users to the dashboard
|
||||
if($this->getUser()) {
|
||||
return $this->redirectToRoute('app_dashboard');
|
||||
}
|
||||
|
||||
// get the login error if there is one
|
||||
$error = $authenticationUtils->getLastAuthenticationError();
|
||||
|
||||
// last username entered by the user
|
||||
$lastUsername = $authenticationUtils->getLastUsername();
|
||||
|
||||
return $this->render('login/index.html.twig', [
|
||||
'controller_name' => 'LoginController',
|
||||
'last_username' => $lastUsername,
|
||||
'error' => $error,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/logout', name: 'app_logout', methods: ['GET'])]
|
||||
public function logout(Security $security): Response
|
||||
{
|
||||
if($this->getUser()) {
|
||||
$security->logout(false);
|
||||
}
|
||||
return $this->redirectToRoute('app_login');
|
||||
}
|
||||
}
|
81
src/Controller/UserController.php
Normal file
81
src/Controller/UserController.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Form\UserType;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
#[Route('/user')]
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
#[Route('/', name: 'app_user_index', methods: ['GET'])]
|
||||
public function index(UserRepository $userRepository): Response
|
||||
{
|
||||
return $this->render('user/index.html.twig', [
|
||||
'users' => $userRepository->findAll(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/new', name: 'app_user_new', methods: ['GET', 'POST'])]
|
||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$user = new User();
|
||||
$form = $this->createForm(UserType::class, $user);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
|
||||
return $this->render('user/new.html.twig', [
|
||||
'user' => $user,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'app_user_show', methods: ['GET'])]
|
||||
public function show(User $user): Response
|
||||
{
|
||||
return $this->render('user/show.html.twig', [
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}/edit', name: 'app_user_edit', methods: ['GET', 'POST'])]
|
||||
public function edit(Request $request, User $user, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$form = $this->createForm(UserType::class, $user);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
|
||||
return $this->render('user/edit.html.twig', [
|
||||
'user' => $user,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'app_user_delete', methods: ['POST'])]
|
||||
public function delete(Request $request, User $user, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
|
||||
$entityManager->remove($user);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER);
|
||||
}
|
||||
}
|
0
src/Entity/.gitignore
vendored
Normal file
0
src/Entity/.gitignore
vendored
Normal file
229
src/Entity/Comment.php
Normal file
229
src/Entity/Comment.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\CommentRepository;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: CommentRepository::class)]
|
||||
class Comment
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'comments')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?DomainPage $page = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'comments')]
|
||||
private ?User $user = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $markdown = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $html = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'childComments')]
|
||||
private ?self $parentComment = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'parentComment', targetEntity: self::class)]
|
||||
private Collection $childComments;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $score = null;
|
||||
|
||||
#[ORM\Column(length: 32)]
|
||||
private ?string $state = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
private ?DateTimeInterface $updatedAt = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
private ?DateTimeInterface $deletedAt = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
private ?User $deletedByUser = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->childComments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(?User $user): static
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMarkdown(): ?string
|
||||
{
|
||||
return $this->markdown;
|
||||
}
|
||||
|
||||
public function setMarkdown(string $markdown): static
|
||||
{
|
||||
$this->markdown = $markdown;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHtml(): ?string
|
||||
{
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
public function setHtml(string $html): static
|
||||
{
|
||||
$this->html = $html;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParentComment(): ?self
|
||||
{
|
||||
return $this->parentComment;
|
||||
}
|
||||
|
||||
public function setParentComment(?self $parentComment): static
|
||||
{
|
||||
$this->parentComment = $parentComment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, self>
|
||||
*/
|
||||
public function getChildComments(): Collection
|
||||
{
|
||||
return $this->childComments;
|
||||
}
|
||||
|
||||
public function addChildComment(self $childComment): static
|
||||
{
|
||||
if (!$this->childComments->contains($childComment)) {
|
||||
$this->childComments->add($childComment);
|
||||
$childComment->setParentComment($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeChildComment(self $childComment): static
|
||||
{
|
||||
if ($this->childComments->removeElement($childComment)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($childComment->getParentComment() === $this) {
|
||||
$childComment->setParentComment(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getScore(): ?int
|
||||
{
|
||||
return $this->score;
|
||||
}
|
||||
|
||||
public function setScore(int $score): static
|
||||
{
|
||||
$this->score = $score;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getState(): ?string
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function setState(string $state): static
|
||||
{
|
||||
$this->state = $state;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(DateTimeImmutable $createdAt): static
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?DateTimeInterface $updatedAt): static
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDeletedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->deletedAt;
|
||||
}
|
||||
|
||||
public function setDeletedAt(?DateTimeInterface $deletedAt): static
|
||||
{
|
||||
$this->deletedAt = $deletedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDeletedByUser(): ?User
|
||||
{
|
||||
return $this->deletedByUser;
|
||||
}
|
||||
|
||||
public function setDeletedByUser(?User $deletedByUser): static
|
||||
{
|
||||
$this->deletedByUser = $deletedByUser;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPage(): ?DomainPage
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function setPage(?DomainPage $page): static
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
191
src/Entity/Domain.php
Normal file
191
src/Entity/Domain.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\DomainRepository;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation\Timestampable;
|
||||
|
||||
#[ORM\Entity(repositoryClass: DomainRepository::class)]
|
||||
class Domain
|
||||
{
|
||||
const SORT_POLICIES = [
|
||||
'Recent First' => 'created:desc',
|
||||
'Oldest First' => 'created:asc',
|
||||
'Rating Desc' => 'rating:desc',
|
||||
'Rating Asc' => 'rating:asc',
|
||||
];
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $domain = null;
|
||||
|
||||
#[ORM\Column(length: 64)]
|
||||
private ?string $ownerToken = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $enabled = null;
|
||||
|
||||
#[ORM\Column(length: 32)]
|
||||
private ?string $defaultSortPolicy = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
#[Timestampable(on: "update")]
|
||||
private ?DateTimeInterface $updatedAt = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
||||
#[Timestampable(on: "create")]
|
||||
private ?DateTimeInterface $createdAt = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'domain', targetEntity: DomainPage::class)]
|
||||
private Collection $domainPages;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->domainPages = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getName() ?? $this->getId();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getDomain(): ?string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
public function setDomain(string $domain): static
|
||||
{
|
||||
$this->domain = $domain;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOwnerToken(): ?string
|
||||
{
|
||||
return $this->ownerToken;
|
||||
}
|
||||
|
||||
public function setOwnerToken(string $ownerToken): static
|
||||
{
|
||||
$this->ownerToken = $ownerToken;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isEnabled(): ?bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function setEnabled(bool $enabled): static
|
||||
{
|
||||
$this->enabled = $enabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDefaultSortPolicy(): ?string
|
||||
{
|
||||
return $this->defaultSortPolicy;
|
||||
}
|
||||
|
||||
public function getDefaultSortPolicyName(): ?string
|
||||
{
|
||||
if(!$this->getDefaultSortPolicy()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_flip(Domain::SORT_POLICIES)[$this->getDefaultSortPolicy()] ?? null;
|
||||
}
|
||||
|
||||
public function setDefaultSortPolicy(string $defaultSortPolicy): static
|
||||
{
|
||||
$this->defaultSortPolicy = $defaultSortPolicy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?DateTimeInterface $updatedAt): static
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(DateTimeInterface $createdAt): static
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, DomainPage>
|
||||
*/
|
||||
public function getDomainPages(): Collection
|
||||
{
|
||||
return $this->domainPages;
|
||||
}
|
||||
|
||||
public function addDomainPage(DomainPage $domainPage): static
|
||||
{
|
||||
if (!$this->domainPages->contains($domainPage)) {
|
||||
$this->domainPages->add($domainPage);
|
||||
$domainPage->setDomain($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeDomainPage(DomainPage $domainPage): static
|
||||
{
|
||||
if ($this->domainPages->removeElement($domainPage)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($domainPage->getDomain() === $this) {
|
||||
$domainPage->setDomain(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
188
src/Entity/DomainPage.php
Normal file
188
src/Entity/DomainPage.php
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\DomainPageRepository;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
|
||||
#[ORM\Entity(repositoryClass: DomainPageRepository::class)]
|
||||
class DomainPage
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'domainPages')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?Domain $domain = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $path = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $locked = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $commentCount = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $title = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Gedmo\Timestampable(on: "create")]
|
||||
private ?DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
#[Gedmo\Timestampable(on: "update")]
|
||||
private ?DateTimeInterface $updatedAt = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
private ?DateTimeInterface $deletedAt = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'page', targetEntity: Comment::class)]
|
||||
private Collection $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comments = new ArrayCollection();
|
||||
$this->commentCount = 0;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getDomain(): ?Domain
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
public function setDomain(?Domain $domain): static
|
||||
{
|
||||
$this->domain = $domain;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPath(): ?string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function setPath(string $path): static
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isLocked(): ?bool
|
||||
{
|
||||
return $this->locked;
|
||||
}
|
||||
|
||||
public function setLocked(bool $locked): static
|
||||
{
|
||||
$this->locked = $locked;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommentCount(): ?int
|
||||
{
|
||||
return $this->commentCount;
|
||||
}
|
||||
|
||||
public function setCommentCount(int $commentCount): static
|
||||
{
|
||||
$this->commentCount = $commentCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): static
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(DateTimeImmutable $createdAt): static
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?DateTimeInterface $updatedAt): static
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDeletedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->deletedAt;
|
||||
}
|
||||
|
||||
public function setDeletedAt(?DateTimeInterface $deletedAt): static
|
||||
{
|
||||
$this->deletedAt = $deletedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Comment>
|
||||
*/
|
||||
public function getComments(): Collection
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function addComment(Comment $comment): static
|
||||
{
|
||||
if (!$this->comments->contains($comment)) {
|
||||
$this->comments->add($comment);
|
||||
$comment->setPage($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeComment(Comment $comment): static
|
||||
{
|
||||
if ($this->comments->removeElement($comment)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($comment->getPage() === $this) {
|
||||
$comment->setPage(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
111
src/Entity/Email.php
Normal file
111
src/Entity/Email.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\EmailRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: EmailRepository::class)]
|
||||
class Email
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $unsubscribeToken = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
private ?\DateTimeInterface $lastNotificationSentAt = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $pendingEmails = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $sendReplyNotifications = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $sendModeratorNotifications = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): static
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUnsubscribeToken(): ?string
|
||||
{
|
||||
return $this->unsubscribeToken;
|
||||
}
|
||||
|
||||
public function setUnsubscribeToken(string $unsubscribeToken): static
|
||||
{
|
||||
$this->unsubscribeToken = $unsubscribeToken;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastNotificationSentAt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->lastNotificationSentAt;
|
||||
}
|
||||
|
||||
public function setLastNotificationSentAt(?\DateTimeInterface $lastNotificationSentAt): static
|
||||
{
|
||||
$this->lastNotificationSentAt = $lastNotificationSentAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPendingEmails(): ?int
|
||||
{
|
||||
return $this->pendingEmails;
|
||||
}
|
||||
|
||||
public function setPendingEmails(int $pendingEmails): static
|
||||
{
|
||||
$this->pendingEmails = $pendingEmails;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isSendReplyNotifications(): ?bool
|
||||
{
|
||||
return $this->sendReplyNotifications;
|
||||
}
|
||||
|
||||
public function setSendReplyNotifications(bool $sendReplyNotifications): static
|
||||
{
|
||||
$this->sendReplyNotifications = $sendReplyNotifications;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isSendModeratorNotifications(): ?bool
|
||||
{
|
||||
return $this->sendModeratorNotifications;
|
||||
}
|
||||
|
||||
public function setSendModeratorNotifications(bool $sendModeratorNotifications): static
|
||||
{
|
||||
$this->sendModeratorNotifications = $sendModeratorNotifications;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
67
src/Entity/PageView.php
Normal file
67
src/Entity/PageView.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\PageViewRepository;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PageViewRepository::class)]
|
||||
class PageView
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?DomainPage $page = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
private ?User $user = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?DateTimeImmutable $createdAt = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getPage(): ?DomainPage
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function setPage(?DomainPage $page): static
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(?User $user): static
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(DateTimeImmutable $createdAt): static
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
217
src/Entity/User.php
Normal file
217
src/Entity/User.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\UserRepository;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation\Timestampable;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
#[ORM\Table(name: '`user`')]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
const ROLE_USER = 'ROLE_USER';
|
||||
const ROLE_OWNER = 'ROLE_OWNER';
|
||||
const ROLE_ADMIN = 'ROLE_ADMIN';
|
||||
const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN';
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 180, unique: true)]
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Email(mode: Assert\Email::VALIDATION_MODE_HTML5)]
|
||||
#[Assert\Length(max: 180)]
|
||||
#[Assert\NoSuspiciousCharacters()]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(length: 64)]
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Length(max: 64)]
|
||||
#[Assert\NoSuspiciousCharacters()]
|
||||
#[Assert\Regex(pattern: "/^[a-zA-Z0-9 _\-]+$/", message: "Display name can only contain characters [a-zA-Z0-9 _-]")]
|
||||
private ?string $displayName = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private array $roles = [];
|
||||
|
||||
/**
|
||||
* @var string The hashed password
|
||||
*/
|
||||
#[ORM\Column]
|
||||
private ?string $password = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
||||
#[Timestampable(on: "create")]
|
||||
private ?DateTimeInterface $createdAt = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
#[Timestampable(on: "update")]
|
||||
private ?DateTimeInterface $updatedAt = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'user', targetEntity: Comment::class)]
|
||||
private Collection $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): static
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDisplayName(): ?string
|
||||
{
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
public function setDisplayName(string $displayName): static
|
||||
{
|
||||
$this->displayName = $displayName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A visual identifier that represents this user.
|
||||
*
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return (string) $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function getRoles(): array
|
||||
{
|
||||
$roles = $this->roles;
|
||||
// guarantee every user at least has ROLE_USER
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
return array_unique($roles);
|
||||
}
|
||||
|
||||
public function setRoles(array $roles): static
|
||||
{
|
||||
$this->roles = $roles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addRole(string $role): static
|
||||
{
|
||||
if(!$this->roles) {
|
||||
$this->roles = [];
|
||||
}
|
||||
|
||||
if(!in_array($role, $this->roles)) {
|
||||
$this->roles[] = $role;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PasswordAuthenticatedUserInterface
|
||||
*/
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function setPassword(string $password): static
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
// $this->plainPassword = null;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(DateTimeInterface $createdAt): static
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?DateTimeInterface
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?DateTimeInterface $updatedAt): static
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Comment>
|
||||
*/
|
||||
public function getComments(): Collection
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function addComment(Comment $comment): static
|
||||
{
|
||||
if (!$this->comments->contains($comment)) {
|
||||
$this->comments->add($comment);
|
||||
$comment->setUser($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeComment(Comment $comment): static
|
||||
{
|
||||
if ($this->comments->removeElement($comment)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($comment->getUser() === $this) {
|
||||
$comment->setUser(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
42
src/Form/DomainPageType.php
Normal file
42
src/Form/DomainPageType.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Domain;
|
||||
use App\Entity\DomainPage;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class DomainPageType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('locked')
|
||||
->add('domain', EntityType::class, [
|
||||
'class' => Domain::class,
|
||||
])
|
||||
->add('path', TextType::class, [
|
||||
'attr' => [
|
||||
'placeholder' => '/example/path'
|
||||
]
|
||||
])
|
||||
->add('title', TextType::class, [
|
||||
'attr' => [
|
||||
'placeholder' => 'Example Article Name'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => DomainPage::class,
|
||||
]);
|
||||
}
|
||||
}
|
43
src/Form/DomainType.php
Normal file
43
src/Form/DomainType.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Domain;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class DomainType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('enabled', CheckboxType::class, [
|
||||
'attr' => ['checked' => 'checked', 'value' => '1']
|
||||
])
|
||||
->add('domain', TextType::class, [
|
||||
'attr' => [
|
||||
'placeholder' => 'example.com'
|
||||
]
|
||||
])
|
||||
->add('name', TextType::class, [
|
||||
'attr' => [
|
||||
'placeholder' => "John Doe's Blog"
|
||||
]
|
||||
])
|
||||
->add('defaultSortPolicy', ChoiceType::class, [
|
||||
'choices' => Domain::SORT_POLICIES,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Domain::class,
|
||||
]);
|
||||
}
|
||||
}
|
30
src/Form/UserType.php
Normal file
30
src/Form/UserType.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email')
|
||||
->add('displayName')
|
||||
->add('roles')
|
||||
->add('password')
|
||||
->add('createdAt')
|
||||
->add('updatedAt')
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
]);
|
||||
}
|
||||
}
|
11
src/Kernel.php
Normal file
11
src/Kernel.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||
|
||||
class Kernel extends BaseKernel
|
||||
{
|
||||
use MicroKernelTrait;
|
||||
}
|
0
src/Repository/.gitignore
vendored
Normal file
0
src/Repository/.gitignore
vendored
Normal file
48
src/Repository/CommentRepository.php
Normal file
48
src/Repository/CommentRepository.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Comment;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Comment>
|
||||
*
|
||||
* @method Comment|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Comment|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Comment[] findAll()
|
||||
* @method Comment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class CommentRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Comment::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Comment[] Returns an array of Comment objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('c')
|
||||
// ->andWhere('c.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('c.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Comment
|
||||
// {
|
||||
// return $this->createQueryBuilder('c')
|
||||
// ->andWhere('c.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
48
src/Repository/DomainPageRepository.php
Normal file
48
src/Repository/DomainPageRepository.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\DomainPage;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<DomainPage>
|
||||
*
|
||||
* @method DomainPage|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method DomainPage|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method DomainPage[] findAll()
|
||||
* @method DomainPage[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class DomainPageRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, DomainPage::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return DomainPage[] Returns an array of DomainPage objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('d')
|
||||
// ->andWhere('d.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('d.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?DomainPage
|
||||
// {
|
||||
// return $this->createQueryBuilder('d')
|
||||
// ->andWhere('d.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
48
src/Repository/DomainRepository.php
Normal file
48
src/Repository/DomainRepository.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Domain;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Domain>
|
||||
*
|
||||
* @method Domain|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Domain|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Domain[] findAll()
|
||||
* @method Domain[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class DomainRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Domain::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Domain[] Returns an array of Domain objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('d')
|
||||
// ->andWhere('d.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('d.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Domain
|
||||
// {
|
||||
// return $this->createQueryBuilder('d')
|
||||
// ->andWhere('d.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
48
src/Repository/EmailRepository.php
Normal file
48
src/Repository/EmailRepository.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Email;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Email>
|
||||
*
|
||||
* @method Email|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Email|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Email[] findAll()
|
||||
* @method Email[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class EmailRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Email::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Email[] Returns an array of Email objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('e')
|
||||
// ->andWhere('e.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('e.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Email
|
||||
// {
|
||||
// return $this->createQueryBuilder('e')
|
||||
// ->andWhere('e.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
48
src/Repository/PageViewRepository.php
Normal file
48
src/Repository/PageViewRepository.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\PageView;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<PageView>
|
||||
*
|
||||
* @method PageView|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method PageView|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method PageView[] findAll()
|
||||
* @method PageView[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class PageViewRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, PageView::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return PageView[] Returns an array of PageView objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('p')
|
||||
// ->andWhere('p.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('p.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?PageView
|
||||
// {
|
||||
// return $this->createQueryBuilder('p')
|
||||
// ->andWhere('p.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
67
src/Repository/UserRepository.php
Normal file
67
src/Repository/UserRepository.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<User>
|
||||
*
|
||||
* @implements PasswordUpgraderInterface<User>
|
||||
*
|
||||
* @method User|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method User|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method User[] findAll()
|
||||
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to upgrade (rehash) the user's password automatically over time.
|
||||
*/
|
||||
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
|
||||
}
|
||||
|
||||
$user->setPassword($newHashedPassword);
|
||||
$this->getEntityManager()->persist($user);
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return User[] Returns an array of User objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('u.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?User
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
73
src/Utils/UserValidator.php
Normal file
73
src/Utils/UserValidator.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
/**
|
||||
* This class is used to provide an example of integrating simple classes as
|
||||
* services into a Symfony application.
|
||||
* See https://symfony.com/doc/current/service_container.html#creating-configuring-services-in-the-container.
|
||||
*
|
||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
||||
*/
|
||||
final class UserValidator
|
||||
{
|
||||
public function validateDisplayName(?string $displayName): string
|
||||
{
|
||||
if (empty($displayName)) {
|
||||
throw new InvalidArgumentException('The display name can not be empty.');
|
||||
}
|
||||
|
||||
if (1 !== preg_match('/^[0-9a-zA-Z _\-]+$/', $displayName)) {
|
||||
throw new InvalidArgumentException('The display name must contain only latin characters and underscores.');
|
||||
}
|
||||
|
||||
return $displayName;
|
||||
}
|
||||
|
||||
public function validatePassword(?string $plainPassword): string
|
||||
{
|
||||
if (empty($plainPassword)) {
|
||||
throw new InvalidArgumentException('The password can not be empty.');
|
||||
}
|
||||
|
||||
if (u($plainPassword)->trim()->length() < 6) {
|
||||
throw new InvalidArgumentException('The password must be at least 6 characters long.');
|
||||
}
|
||||
|
||||
return $plainPassword;
|
||||
}
|
||||
|
||||
public function validateEmail(?string $email): string
|
||||
{
|
||||
if (empty($email)) {
|
||||
throw new InvalidArgumentException('The email can not be empty.');
|
||||
}
|
||||
|
||||
if (null === u($email)->indexOf('@')) {
|
||||
throw new InvalidArgumentException('The email should look like a real email.');
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
public function validateFullName(?string $fullName): string
|
||||
{
|
||||
if (empty($fullName)) {
|
||||
throw new InvalidArgumentException('The full name can not be empty.');
|
||||
}
|
||||
|
||||
return $fullName;
|
||||
}
|
||||
}
|
312
symfony.lock
Normal file
312
symfony.lock
Normal file
@ -0,0 +1,312 @@
|
||||
{
|
||||
"doctrine/annotations": {
|
||||
"version": "2.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.10",
|
||||
"ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05"
|
||||
}
|
||||
},
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "2.10",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.10",
|
||||
"ref": "e025a6cb69b195970543820b2f18ad21724473fa"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine.yaml",
|
||||
"src/Entity/.gitignore",
|
||||
"src/Repository/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/doctrine-migrations-bundle": {
|
||||
"version": "3.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.1",
|
||||
"ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine_migrations.yaml",
|
||||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"phpunit/phpunit": {
|
||||
"version": "9.6",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "9.6",
|
||||
"ref": "7364a21d87e658eb363c5020c072ecfdc12e2326"
|
||||
},
|
||||
"files": [
|
||||
".env.test",
|
||||
"phpunit.xml.dist",
|
||||
"tests/bootstrap.php"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.3",
|
||||
"ref": "da0c8be8157600ad34f10ff0c9cc91232522e047"
|
||||
},
|
||||
"files": [
|
||||
"bin/console"
|
||||
]
|
||||
},
|
||||
"symfony/debug-bundle": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.3",
|
||||
"ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/debug.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/flex": {
|
||||
"version": "2.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
|
||||
},
|
||||
"files": [
|
||||
".env"
|
||||
]
|
||||
},
|
||||
"symfony/framework-bundle": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.2",
|
||||
"ref": "af47254c5e4cd543e6af3e4508298ffebbdaddd3"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/cache.yaml",
|
||||
"config/packages/framework.yaml",
|
||||
"config/preload.php",
|
||||
"config/routes/framework.yaml",
|
||||
"config/services.yaml",
|
||||
"public/index.php",
|
||||
"src/Controller/.gitignore",
|
||||
"src/Kernel.php"
|
||||
]
|
||||
},
|
||||
"symfony/lock": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.2",
|
||||
"ref": "8e937ff2b4735d110af1770f242c1107fdab4c8e"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/lock.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/mailer": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "4.3",
|
||||
"ref": "2bf89438209656b85b9a49238c4467bff1b1f939"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/mailer.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/maker-bundle": {
|
||||
"version": "1.50",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||
}
|
||||
},
|
||||
"symfony/messenger": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.0",
|
||||
"ref": "ba1ac4e919baba5644d31b57a3284d6ba12d52ee"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/messenger.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/monolog-bundle": {
|
||||
"version": "3.8",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.7",
|
||||
"ref": "213676c4ec929f046dfde5ea8e97625b81bc0578"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/monolog.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/notifier": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.0",
|
||||
"ref": "178877daf79d2dbd62129dd03612cb1a2cb407cc"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/notifier.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/phpunit-bridge": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "01dfaa98c58f7a7b5a9b30e6edb7074af7ed9819"
|
||||
},
|
||||
"files": [
|
||||
".env.test",
|
||||
"bin/phpunit",
|
||||
"phpunit.xml.dist",
|
||||
"tests/bootstrap.php"
|
||||
]
|
||||
},
|
||||
"symfony/routing": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.2",
|
||||
"ref": "e0a11b4ccb8c9e70b574ff5ad3dfdcd41dec5aa6"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/routing.yaml",
|
||||
"config/routes.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/security-bundle": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.0",
|
||||
"ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/security.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/stimulus-bundle": {
|
||||
"version": "2.11",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.9",
|
||||
"ref": "05c45071c7ecacc1e48f94bc43c1f8d4405fb2b2"
|
||||
},
|
||||
"files": [
|
||||
"assets/bootstrap.js",
|
||||
"assets/controllers.json",
|
||||
"assets/controllers/domains_controller.js"
|
||||
]
|
||||
},
|
||||
"symfony/translation": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "64fe617084223633e1dedf9112935d8c95410d3e"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/translation.yaml",
|
||||
"translations/.gitignore"
|
||||
]
|
||||
},
|
||||
"symfony/twig-bundle": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "b7772eb20e92f3fb4d4fe756e7505b4ba2ca1a2c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/twig.yaml",
|
||||
"templates/base.html.twig"
|
||||
]
|
||||
},
|
||||
"symfony/validator": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.3",
|
||||
"ref": "c32cfd98f714894c4f128bb99aa2530c1227603c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/validator.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/web-profiler-bundle": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.1",
|
||||
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/web_profiler.yaml",
|
||||
"config/routes/web_profiler.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/webapp-pack": {
|
||||
"version": "1.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "aece95c8a188f6e6d04f01ccb8678d1764fd2642"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/messenger.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/webpack-encore-bundle": {
|
||||
"version": "2.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.0",
|
||||
"ref": "082d754b3bd54b3fc669f278f1eea955cfd23cf5"
|
||||
},
|
||||
"files": [
|
||||
"assets/app.js",
|
||||
"assets/styles/app.css",
|
||||
"config/packages/webpack_encore.yaml",
|
||||
"package.json",
|
||||
"webpack.config.js"
|
||||
]
|
||||
},
|
||||
"twig/extra-bundle": {
|
||||
"version": "v3.7.1"
|
||||
}
|
||||
}
|
217
templates/base.html.twig
Normal file
217
templates/base.html.twig
Normal file
@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
|
||||
{% block stylesheets %}
|
||||
{{ encore_entry_link_tags('app') }}
|
||||
{% endblock %}
|
||||
{% block extra_stylesheets %}{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ encore_entry_script_tags('app') }}
|
||||
{% endblock %}
|
||||
{% block extra_javascripts %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% if is_granted('ROLE_USER') %}
|
||||
{# Main Navigation #}
|
||||
<header>
|
||||
{# Sidebar #}
|
||||
<nav id="sidebarMenu" class="collapse d-lg-block sidebar collapse bg-body-tertiary">
|
||||
<div class="position-sticky">
|
||||
<div class="list-group list-group-flush mx-3 mt-4">
|
||||
<a href="#" class="list-group-item list-group-item-action py-2 ripple{{ app.current_route() == 'app_dashboard' ? ' active' : '' }}" aria-current="true">
|
||||
<i class="fas fa-tachometer-alt fa-fw me-3"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action py-2 ripple">
|
||||
<i class="fas fa-comments fa-fw me-3"></i>
|
||||
<span>Comments</span>
|
||||
</a>
|
||||
<a href="{{ path('app_domain_index') }}" class="list-group-item list-group-item-action py-2 ripple{{ app.request.pathInfo starts with path('app_domain_index') ? ' active' : ''}}">
|
||||
<i class="fas fa-globe fa-fw me-3"></i>
|
||||
<span>Domains</span>
|
||||
</a>
|
||||
<a href="{{ path('app_page_index') }}" class="list-group-item list-group-item-action py-2 ripple{{ app.request.pathInfo starts with path('app_page_index') ? ' active' : ''}}">
|
||||
<i class="fas fa-file fa-fw me-3"></i>
|
||||
<span>Pages</span>
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action py-2 ripple{{ app.request.pathInfo starts with path('app_user_index') ? ' active' : ''}}">
|
||||
<i class="fas fa-users fa-fw me-3"></i>
|
||||
<span>Users</span>
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action py-2 ripple">
|
||||
<i class="fas fa-chart-bar fa-fw me-3"></i>
|
||||
<span>Stats</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{# Sidebar #}
|
||||
|
||||
{# Navbar #}
|
||||
<nav id="main-navbar" class="navbar navbar-expand-lg navbar-light bg-body-secondary fixed-top">
|
||||
{# Container wrapper #}
|
||||
<div class="container-fluid">
|
||||
{# Toggle button #}
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-mdb-toggle="collapse"
|
||||
data-mdb-target="#sidebarMenu"
|
||||
aria-controls="sidebarMenu"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
|
||||
{# Brand #}
|
||||
<a class="navbar-brand" href="#">
|
||||
MyComments
|
||||
</a>
|
||||
{# Search form #}
|
||||
{# <form class="d-none d-md-flex input-group w-auto my-auto">#}
|
||||
{# <input#}
|
||||
{# autocomplete="off"#}
|
||||
{# type="search"#}
|
||||
{# class="form-control rounded"#}
|
||||
{# placeholder='Search (ctrl + "/" to focus)'#}
|
||||
{# style="min-width: 225px"#}
|
||||
{# />#}
|
||||
{# <span class="input-group-text border-0"#}
|
||||
{# ><i class="fas fa-search"></i#}
|
||||
{# ></span>#}
|
||||
{# </form>#}
|
||||
|
||||
{# Right links #}
|
||||
<ul class="navbar-nav ms-auto d-flex flex-row">
|
||||
{# Notification dropdown #}
|
||||
<li class="nav-item dropdown">
|
||||
<a
|
||||
class="nav-link me-3 px-3 me-lg-0 dropdown-toggle hidden-arrow"
|
||||
href="#"
|
||||
id="navbarDropdownMenuLink"
|
||||
role="button"
|
||||
data-mdb-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i class="fas fa-bell"></i>
|
||||
<span class="badge rounded-pill badge-notification bg-danger"
|
||||
>1</span
|
||||
>
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu dropdown-menu-end"
|
||||
aria-labelledby="navbarDropdownMenuLink"
|
||||
>
|
||||
<li><a class="dropdown-item" href="#">Some news</a></li>
|
||||
<li><a class="dropdown-item" href="#">Another news</a></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#">Something else here</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{# Icon #}
|
||||
{# <li class="nav-item me-3 me-lg-0">#}
|
||||
{# <a class="nav-link" href="#">#}
|
||||
{# <i class="fab fa-github"></i>#}
|
||||
{# </a>#}
|
||||
{# </li>#}
|
||||
|
||||
{# Icon dropdown #}
|
||||
{# <li class="nav-item dropdown">#}
|
||||
{# <a#}
|
||||
{# class="nav-link me-3 me-lg-0 dropdown-toggle hidden-arrow"#}
|
||||
{# href="#"#}
|
||||
{# id="navbarDropdown"#}
|
||||
{# role="button"#}
|
||||
{# data-mdb-toggle="dropdown"#}
|
||||
{# aria-expanded="false"#}
|
||||
{# >#}
|
||||
{# <i class="united kingdom flag m-0"></i>#}
|
||||
{# </a>#}
|
||||
{# <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#">#}
|
||||
{# <i class="united kingdom flag"></i>English <i class="fa fa-check text-success ms-2"></i>#}
|
||||
{# </a>#}
|
||||
{# </li>#}
|
||||
{# <li><hr class="dropdown-divider" /></li>#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#"><i class="poland flag"></i>Polski</a>#}
|
||||
{# </li>#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#"><i class="china flag"></i>中文</a>#}
|
||||
{# </li>#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#"><i class="japan flag"></i>日本語</a>#}
|
||||
{# </li>#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#"><i class="germany flag"></i>Deutsch</a>#}
|
||||
{# </li>#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#"><i class="france flag"></i>Français</a>#}
|
||||
{# </li>#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#"><i class="spain flag"></i>Español</a>#}
|
||||
{# </li>#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#"><i class="russia flag"></i>Русский</a>#}
|
||||
{# </li>#}
|
||||
{# <li>#}
|
||||
{# <a class="dropdown-item" href="#"><i class="portugal flag"></i>Português</a>#}
|
||||
{# </li>#}
|
||||
{# </ul>#}
|
||||
{# </li>#}
|
||||
|
||||
{# Avatar #}
|
||||
<li class="nav-item dropdown">
|
||||
<a
|
||||
class="nav-link dropdown-toggle hidden-arrow d-flex align-items-center"
|
||||
href="#"
|
||||
id="navbarDropdownMenuLink"
|
||||
role="button"
|
||||
data-mdb-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<img
|
||||
src="https://mdbootstrap.com/img/Photos/Avatars/img (31).jpg"
|
||||
class="rounded-circle"
|
||||
height="24"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
<ul
|
||||
class="dropdown-menu dropdown-menu-end"
|
||||
aria-labelledby="navbarDropdownMenuLink"
|
||||
>
|
||||
<li><a class="dropdown-item" href="#">My profile</a></li>
|
||||
<li><a class="dropdown-item" href="#">Settings</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url('app_logout') }}">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{# Container wrapper #}
|
||||
</nav>
|
||||
{# Navbar #}
|
||||
</header>
|
||||
{# Main Navigation #}
|
||||
{% endif %}
|
||||
|
||||
{% block container %}
|
||||
<main style="margin-top: 58px">
|
||||
<div class="container pt-4">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
8
templates/dashboard/index.html.twig
Normal file
8
templates/dashboard/index.html.twig
Normal file
@ -0,0 +1,8 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Hello DashboardController!{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h4>Dashboard</h4>
|
||||
<hr />
|
||||
{% endblock %}
|
4
templates/domain/_delete_form.html.twig
Normal file
4
templates/domain/_delete_form.html.twig
Normal file
@ -0,0 +1,4 @@
|
||||
<form method="post" action="{{ path('app_domain_delete', {'id': domain.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ domain.id) }}">
|
||||
<button class="btn btn-danger m-1">Delete</button>
|
||||
</form>
|
4
templates/domain/_form.html.twig
Normal file
4
templates/domain/_form.html.twig
Normal file
@ -0,0 +1,4 @@
|
||||
{{ form_start(form, { 'attr' : { 'class': 'mb-3' } }) }}
|
||||
{{ form_widget(form) }}
|
||||
<button class="btn btn-primary float-start m-1">{{ button_label|default('Save') }}</button>
|
||||
{{ form_end(form) }}
|
13
templates/domain/edit.html.twig
Normal file
13
templates/domain/edit.html.twig
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Edit Domain{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Edit Domain</h1>
|
||||
|
||||
{{ include('domain/_form.html.twig', {'button_label': 'Update'}) }}
|
||||
|
||||
<a href="{{ path('app_domain_index') }}" class="btn btn-secondary float-end m-1">Cancel</a>
|
||||
|
||||
{{ include('domain/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
43
templates/domain/index.html.twig
Normal file
43
templates/domain/index.html.twig
Normal file
@ -0,0 +1,43 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Domain List{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Domain List</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Name</th>
|
||||
<th>Enabled</th>
|
||||
<th>DefaultSortPolicy</th>
|
||||
<th>UpdatedAt</th>
|
||||
<th>CreatedAt</th>
|
||||
<th>actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for domain in domains %}
|
||||
<tr>
|
||||
<td>{{ domain.domain }}</td>
|
||||
<td>{{ domain.name }}</td>
|
||||
<td>{{ domain.enabled ? 'Yes' : 'No' }}</td>
|
||||
<td>{{ domain.getDefaultSortPolicyName }}</td>
|
||||
<td>{{ domain.updatedAt ? domain.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
<td>{{ domain.createdAt ? domain.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
<td>
|
||||
<a href="{{ path('app_domain_show', {'id': domain.id}) }}">show</a>
|
||||
<a href="{{ path('app_domain_edit', {'id': domain.id}) }}">edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="9">no records found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('app_domain_new') }}">Create new</a>
|
||||
{% endblock %}
|
11
templates/domain/new.html.twig
Normal file
11
templates/domain/new.html.twig
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}New Domain{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Create new Domain</h1>
|
||||
|
||||
{{ include('domain/_form.html.twig') }}
|
||||
|
||||
<a href="{{ path('app_domain_index') }}" class="btn btn-secondary float-end m-1">Cancel</a>
|
||||
{% endblock %}
|
50
templates/domain/show.html.twig
Normal file
50
templates/domain/show.html.twig
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Domain{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Domain</h1>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>{{ domain.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<td>{{ domain.domain }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>OwnerToken</th>
|
||||
<td>{{ domain.ownerToken }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td>{{ domain.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Enabled</th>
|
||||
<td>{{ domain.enabled ? 'Yes' : 'No' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>DefaultSortPolicy</th>
|
||||
<td>{{ domain.getDefaultSortPolicyName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>UpdatedAt</th>
|
||||
<td>{{ domain.updatedAt ? domain.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>CreatedAt</th>
|
||||
<td>{{ domain.createdAt ? domain.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('app_domain_index') }}">back to list</a>
|
||||
|
||||
<a href="{{ path('app_domain_edit', {'id': domain.id}) }}">edit</a>
|
||||
|
||||
{{ include('domain/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
53
templates/login/index.html.twig
Normal file
53
templates/login/index.html.twig
Normal file
@ -0,0 +1,53 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block container %}
|
||||
|
||||
<section class="vh-100 gradient-custom">
|
||||
<div class="container py-5 h-100">
|
||||
<div class="row d-flex justify-content-center align-items-center h-100">
|
||||
<div class="col-12 col-md-8 col-lg-6 col-xl-5">
|
||||
<div class="card bg-dark text-white" style="border-radius: 1rem;">
|
||||
<div class="card-body p-5 text-center">
|
||||
|
||||
<div class="mb-md-5 mt-md-4 pb-5">
|
||||
<form action="{{ path('app_login') }}" method="post">
|
||||
|
||||
<h2 class="fw-bold mb-5">MyComments Login</h2>
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{ error.messageKey|trans(error.messageData, 'security') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-outline form-white mb-4">
|
||||
<input type="email" id="typeEmailX" name="_username" class="form-control form-control-lg" value="{{ last_username }}" />
|
||||
<label class="form-label" for="typeEmailX">Email</label>
|
||||
</div>
|
||||
|
||||
<div class="form-outline form-white mb-4">
|
||||
<input type="password" id="typePasswordX" name="_password" class="form-control form-control-lg" />
|
||||
<label class="form-label" for="typePasswordX">Password</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-light btn-lg px-5" type="submit">Login</button>
|
||||
|
||||
<div class="d-flex justify-content-center text-center mt-4 pt-1">
|
||||
<a href="#" class="text-white"><i class="fab fa-google fa-lg mx-1"></i></a>
|
||||
<a href="#" class="text-white"><i class="fab fa-github fa-lg mx-1"></i></a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="mb-0"><a href="#!" class="text-white-50 fw-bold">Forgot your password?</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
4
templates/page/_delete_form.html.twig
Normal file
4
templates/page/_delete_form.html.twig
Normal file
@ -0,0 +1,4 @@
|
||||
<form method="post" action="{{ path('app_page_delete', {'id': domain_page.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ domain_page.id) }}">
|
||||
<button class="btn">Delete</button>
|
||||
</form>
|
4
templates/page/_form.html.twig
Normal file
4
templates/page/_form.html.twig
Normal file
@ -0,0 +1,4 @@
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<button class="btn btn-primary float-start m-1">{{ button_label|default('Save') }}</button>
|
||||
{{ form_end(form) }}
|
13
templates/page/edit.html.twig
Normal file
13
templates/page/edit.html.twig
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Edit DomainPage{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Edit DomainPage</h1>
|
||||
|
||||
{{ include('page/_form.html.twig', {'button_label': 'Update'}) }}
|
||||
|
||||
<a href="{{ path('app_page_index') }}">back to list</a>
|
||||
|
||||
{{ include('page/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
47
templates/page/index.html.twig
Normal file
47
templates/page/index.html.twig
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Pages List{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Pages List</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Path</th>
|
||||
<th>Locked</th>
|
||||
<th>CommentCount</th>
|
||||
<th>Title</th>
|
||||
<th>CreatedAt</th>
|
||||
<th>UpdatedAt</th>
|
||||
<th>DeletedAt</th>
|
||||
<th>actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for domain_page in domain_pages %}
|
||||
<tr>
|
||||
<td><a href="{{ path('app_domain_show', {'id': domain_page.domain.id}) }}">{{ domain_page.domain.domain }}</a></td>
|
||||
<td>{{ domain_page.path }}</td>
|
||||
<td>{{ domain_page.locked ? 'Yes' : 'No' }}</td>
|
||||
<td>{{ domain_page.commentCount }}</td>
|
||||
<td>{{ domain_page.title }}</td>
|
||||
<td>{{ domain_page.createdAt ? domain_page.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
<td>{{ domain_page.updatedAt ? domain_page.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
<td>{{ domain_page.deletedAt ? domain_page.deletedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
<td>
|
||||
<a href="{{ path('app_page_show', {'id': domain_page.id}) }}">show</a>
|
||||
<a href="{{ path('app_page_edit', {'id': domain_page.id}) }}">edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="9">no records found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('app_page_new') }}">Create new</a>
|
||||
{% endblock %}
|
11
templates/page/new.html.twig
Normal file
11
templates/page/new.html.twig
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}New DomainPage{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Create new DomainPage</h1>
|
||||
|
||||
{{ include('page/_form.html.twig') }}
|
||||
|
||||
<a href="{{ path('app_page_index') }}" class="btn btn-secondary float-end m-1">Cancel</a>
|
||||
{% endblock %}
|
50
templates/page/show.html.twig
Normal file
50
templates/page/show.html.twig
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}DomainPage{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>DomainPage</h1>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>{{ domain_page.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Path</th>
|
||||
<td>{{ domain_page.path }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Locked</th>
|
||||
<td>{{ domain_page.locked ? 'Yes' : 'No' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>CommentCount</th>
|
||||
<td>{{ domain_page.commentCount }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<td>{{ domain_page.title }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>CreatedAt</th>
|
||||
<td>{{ domain_page.createdAt ? domain_page.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>UpdatedAt</th>
|
||||
<td>{{ domain_page.updatedAt ? domain_page.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>DeletedAt</th>
|
||||
<td>{{ domain_page.deletedAt ? domain_page.deletedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('app_page_index') }}">back to list</a>
|
||||
|
||||
<a href="{{ path('app_page_edit', {'id': domain_page.id}) }}">edit</a>
|
||||
|
||||
{{ include('page/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
4
templates/user/_delete_form.html.twig
Normal file
4
templates/user/_delete_form.html.twig
Normal file
@ -0,0 +1,4 @@
|
||||
<form method="post" action="{{ path('app_user_delete', {'id': user.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ user.id) }}">
|
||||
<button class="btn">Delete</button>
|
||||
</form>
|
4
templates/user/_form.html.twig
Normal file
4
templates/user/_form.html.twig
Normal file
@ -0,0 +1,4 @@
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||
{{ form_end(form) }}
|
13
templates/user/edit.html.twig
Normal file
13
templates/user/edit.html.twig
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Edit User{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Edit User</h1>
|
||||
|
||||
{{ include('user/_form.html.twig', {'button_label': 'Update'}) }}
|
||||
|
||||
<a href="{{ path('app_user_index') }}">back to list</a>
|
||||
|
||||
{{ include('user/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
45
templates/user/index.html.twig
Normal file
45
templates/user/index.html.twig
Normal file
@ -0,0 +1,45 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}User index{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>User index</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Email</th>
|
||||
<th>DisplayName</th>
|
||||
<th>Roles</th>
|
||||
<th>Password</th>
|
||||
<th>CreatedAt</th>
|
||||
<th>UpdatedAt</th>
|
||||
<th>actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{{ user.displayName }}</td>
|
||||
<td>{{ user.roles ? user.roles|json_encode : '' }}</td>
|
||||
<td>{{ user.password }}</td>
|
||||
<td>{{ user.createdAt ? user.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
<td>{{ user.updatedAt ? user.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
<td>
|
||||
<a href="{{ path('app_user_show', {'id': user.id}) }}">show</a>
|
||||
<a href="{{ path('app_user_edit', {'id': user.id}) }}">edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="8">no records found</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('app_user_new') }}">Create new</a>
|
||||
{% endblock %}
|
11
templates/user/new.html.twig
Normal file
11
templates/user/new.html.twig
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}New User{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Create new User</h1>
|
||||
|
||||
{{ include('user/_form.html.twig') }}
|
||||
|
||||
<a href="{{ path('app_user_index') }}">back to list</a>
|
||||
{% endblock %}
|
46
templates/user/show.html.twig
Normal file
46
templates/user/show.html.twig
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}User{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>User</h1>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>{{ user.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td>{{ user.email }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>DisplayName</th>
|
||||
<td>{{ user.displayName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Roles</th>
|
||||
<td>{{ user.roles ? user.roles|json_encode : '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Password</th>
|
||||
<td>{{ user.password }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>CreatedAt</th>
|
||||
<td>{{ user.createdAt ? user.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>UpdatedAt</th>
|
||||
<td>{{ user.updatedAt ? user.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="{{ path('app_user_index') }}">back to list</a>
|
||||
|
||||
<a href="{{ path('app_user_edit', {'id': user.id}) }}">edit</a>
|
||||
|
||||
{{ include('user/_delete_form.html.twig') }}
|
||||
{% endblock %}
|
137
tests/Controller/DomainControllerTest.php
Normal file
137
tests/Controller/DomainControllerTest.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Test\Controller;
|
||||
|
||||
use App\Entity\Domain;
|
||||
use App\Repository\DomainRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class DomainControllerTest extends WebTestCase
|
||||
{
|
||||
private KernelBrowser $client;
|
||||
private DomainRepository $repository;
|
||||
private string $path = '/domain/';
|
||||
private EntityManagerInterface $manager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->client = static::createClient();
|
||||
$this->repository = static::getContainer()->get('doctrine')->getRepository(Domain::class);
|
||||
|
||||
foreach ($this->repository->findAll() as $object) {
|
||||
$this->manager->remove($object);
|
||||
}
|
||||
}
|
||||
|
||||
public function testIndex(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', $this->path);
|
||||
|
||||
self::assertResponseStatusCodeSame(200);
|
||||
self::assertPageTitleContains('Domain index');
|
||||
|
||||
// Use the $crawler to perform additional assertions e.g.
|
||||
// self::assertSame('Some text on the page', $crawler->filter('.p')->first());
|
||||
}
|
||||
|
||||
public function testNew(): void
|
||||
{
|
||||
$originalNumObjectsInRepository = count($this->repository->findAll());
|
||||
|
||||
$this->markTestIncomplete();
|
||||
$this->client->request('GET', sprintf('%snew', $this->path));
|
||||
|
||||
self::assertResponseStatusCodeSame(200);
|
||||
|
||||
$this->client->submitForm('Save', [
|
||||
'domain[domain]' => 'Testing',
|
||||
'domain[name]' => 'Testing',
|
||||
'domain[enabled]' => 'Testing',
|
||||
'domain[defaultSortPolicy]' => 'Testing',
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/domain/');
|
||||
|
||||
self::assertSame($originalNumObjectsInRepository + 1, count($this->repository->findAll()));
|
||||
}
|
||||
|
||||
public function testShow(): void
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
$fixture = new Domain();
|
||||
$fixture->setDomain('localhost');
|
||||
$fixture->setName('My Title');
|
||||
$fixture->setEnabled(true);
|
||||
$fixture->setDefaultSortPolicy(Domain::SORT_POLICIES[0]);
|
||||
|
||||
$this->manager->persist($fixture);
|
||||
$this->manager->flush();
|
||||
|
||||
$this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId()));
|
||||
|
||||
self::assertResponseStatusCodeSame(200);
|
||||
self::assertPageTitleContains('Domain');
|
||||
|
||||
// Use assertions to check that the properties are properly displayed.
|
||||
}
|
||||
|
||||
public function testEdit(): void
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
$fixture = new Domain();
|
||||
$fixture->setDomain('localhost');
|
||||
$fixture->setName('My Title');
|
||||
$fixture->setEnabled(true);
|
||||
$fixture->setDefaultSortPolicy(Domain::SORT_POLICIES[0]);
|
||||
|
||||
$this->manager->persist($fixture);
|
||||
$this->manager->flush();
|
||||
|
||||
$this->client->request('GET', sprintf('%s%s/edit', $this->path, $fixture->getId()));
|
||||
|
||||
$this->client->submitForm('Update', [
|
||||
'domain[domain]' => 'example.com',
|
||||
'domain[name]' => 'Something New',
|
||||
'domain[enabled]' => false,
|
||||
'domain[defaultSortPolicy]' => Domain::SORT_POLICIES[1],
|
||||
]);
|
||||
|
||||
self::assertResponseRedirects('/domain/');
|
||||
|
||||
$fixture = $this->repository->findAll();
|
||||
|
||||
self::assertSame('example.com', $fixture[0]->getDomain());
|
||||
self::assertSame('Something New', $fixture[0]->getName());
|
||||
self::assertSame(false, $fixture[0]->getEnabled());
|
||||
self::assertSame(Domain::SORT_POLICIES[1], $fixture[0]->getDefaultSortPolicy());
|
||||
}
|
||||
|
||||
public function testRemove(): void
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
|
||||
$originalNumObjectsInRepository = count($this->repository->findAll());
|
||||
|
||||
$fixture = new Domain();
|
||||
$fixture->setDomain('My Title');
|
||||
$fixture->setOwnerToken('My Title');
|
||||
$fixture->setName('My Title');
|
||||
$fixture->setEnabled('My Title');
|
||||
$fixture->setDefaultSortPolicy('My Title');
|
||||
$fixture->setUpdatedAt('My Title');
|
||||
$fixture->setCreatedAt('My Title');
|
||||
|
||||
$this->manager->persist($fixture);
|
||||
$this->manager->flush();
|
||||
|
||||
self::assertSame($originalNumObjectsInRepository + 1, count($this->repository->findAll()));
|
||||
|
||||
$this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId()));
|
||||
$this->client->submitForm('Delete');
|
||||
|
||||
self::assertSame($originalNumObjectsInRepository, count($this->repository->findAll()));
|
||||
self::assertResponseRedirects('/domain/');
|
||||
}
|
||||
}
|
11
tests/bootstrap.php
Normal file
11
tests/bootstrap.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
if (file_exists(dirname(__DIR__).'/config/bootstrap.php')) {
|
||||
require dirname(__DIR__).'/config/bootstrap.php';
|
||||
} elseif (method_exists(Dotenv::class, 'bootEnv')) {
|
||||
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user