WIP: feature_paste #1
30
.env
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# 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=your_secret_please_set_on_local_env
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
###> doctrine/doctrine-bundle ###
|
||||||
|
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||||
|
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||||
|
#
|
||||||
|
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||||
|
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||||
|
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||||
|
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8"
|
||||||
|
###< doctrine/doctrine-bundle ###
|
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
/.env.local
|
||||||
|
/.env.local.php
|
||||||
|
/.env.*.local
|
||||||
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
|
/public/bundles/
|
||||||
|
/var/
|
||||||
|
/vendor/
|
||||||
|
###< symfony/framework-bundle ###
|
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);
|
||||||
|
};
|
75
composer.json
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"type": "project",
|
||||||
|
"license": "proprietary",
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"doctrine/doctrine-bundle": "^2.10",
|
||||||
|
"doctrine/doctrine-migrations-bundle": "^3.2",
|
||||||
|
"doctrine/orm": "^2.15",
|
||||||
|
"scrivo/highlight.php": "v9.18.1.10",
|
||||||
|
|||||||
|
"symfony/console": "6.3.*",
|
||||||
|
"symfony/dotenv": "6.3.*",
|
||||||
|
"symfony/flex": "^2",
|
||||||
|
"symfony/form": "6.3.*",
|
||||||
|
"symfony/framework-bundle": "6.3.*",
|
||||||
|
"symfony/runtime": "6.3.*",
|
||||||
|
"symfony/twig-bundle": "6.3.*",
|
||||||
|
"symfony/validator": "6.3.*",
|
||||||
|
"symfony/yaml": "6.3.*"
|
||||||
|
},
|
||||||
|
"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": {
|
||||||
|
"symfony/maker-bundle": "^1.50"
|
||||||
|
}
|
||||||
|
}
|
5045
composer.lock
generated
Normal file
9
config/bundles.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?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\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
|
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||||
|
];
|
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
|
46
config/packages/doctrine.yaml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
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'
|
||||||
|
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
|
@ -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
|
@ -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
|
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
|
6
config/packages/twig.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
twig:
|
||||||
|
default_path: '%kernel.project_dir%/templates'
|
||||||
|
|
||||||
|
when@test:
|
||||||
|
twig:
|
||||||
|
strict_variables: true
|
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
|
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
|
@ -0,0 +1,5 @@
|
||||||
|
controllers:
|
||||||
skobkin
commented
I'd suggest we move to route list in YAML here before merging it. But it's not a main priority for now. Just let this thread be until we ready with everything else. I'd suggest we move to route list in YAML here before merging it. But it's not a main priority for now. Just let this thread be until we ready with everything else.
|
|||||||
|
resource:
|
||||||
|
path: ../src/Controller/
|
||||||
|
namespace: App\Controller
|
||||||
|
type: attribute
|
4
config/routes/framework.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
when@dev:
|
||||||
|
_errors:
|
||||||
|
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||||
|
prefix: /_error
|
24
config/services.yaml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# This file is the entry point to configure your own services.
|
||||||
|
# Files in the packages/ subdirectory configure your dependencies.
|
||||||
|
|
||||||
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
services:
|
||||||
|
# default configuration for services in *this* file
|
||||||
|
_defaults:
|
||||||
|
autowire: true # Automatically injects dependencies in your services.
|
||||||
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
|
|
||||||
|
# makes classes in src/ available to be used as services
|
||||||
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
|
App\:
|
||||||
|
resource: '../src/'
|
||||||
|
exclude:
|
||||||
|
- '../src/DependencyInjection/'
|
||||||
|
- '../src/Entity/'
|
||||||
|
- '../src/Kernel.php'
|
||||||
|
|
||||||
|
# add more service definitions when explicit configuration is needed
|
||||||
|
# please note that last definitions always *replace* previous ones
|
25
migrations/Version20230720115905.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
|
||||||
|
final class Version20230720115905 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(Schema $schema): void
|
||||||
skobkin marked this conversation as resolved
skobkin
commented
Do you need it here if it's empty? Do you need it here if it's empty?
|
|||||||
|
{
|
||||||
|
$this->addSql('CREATE SEQUENCE paste_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE TABLE paste (id INT NOT NULL, text TEXT NOT NULL, language VARCHAR(25), description TEXT, filename VARCHAR(128), author VARCHAR(128), publish_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, expiration_date TIMESTAMP(0) WITHOUT TIME ZONE, ip VARCHAR(39) NOT NULL, secret VARCHAR(40), PRIMARY KEY(id))');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE SCHEMA public');
|
||||||
skobkin
commented
This shouldn't be here. This shouldn't be here.
|
|||||||
|
$this->addSql('DROP SEQUENCE paste_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP TABLE paste');
|
||||||
|
}
|
||||||
|
}
|
74
public/css/ocean.css
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/* Ocean Dark Theme */
|
||||||
|
/* https://github.com/gavsiu */
|
||||||
|
/* Original theme - https://github.com/chriskempson/base16 */
|
||||||
|
|
||||||
|
/* Ocean Comment */
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #65737e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ocean Red */
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #bf616a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ocean Orange */
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-builtin-name,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-params,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-link {
|
||||||
|
color: #d08770;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ocean Yellow */
|
||||||
|
.hljs-attribute {
|
||||||
|
color: #ebcb8b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ocean Green */
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-addition {
|
||||||
|
color: #a3be8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ocean Blue */
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-section {
|
||||||
|
color: #8fa1b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ocean Purple */
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #b48ead;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
background: #2b303b;
|
||||||
|
color: #c0c5ce;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
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']);
|
||||||
|
};
|
52
src/Controller/PasteController.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
skobkin marked this conversation as resolved
skobkin
commented
I can't leave you a comment on Why do you need it? I can't leave you a comment on `src/Controller/.gitignore`, so I'll ask here.
Why do you need it?
|
|||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\DTO\PasteFormData;
|
||||||
|
use App\Entity\CodeHighlighter;
|
||||||
|
use App\Entity\Paste;
|
||||||
|
use App\Form\Type\PasteForm;
|
||||||
|
use App\Repository\PasteRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
|
||||||
|
class PasteController extends AbstractController
|
||||||
skobkin
commented
I'd suggest explicitly defining which request methods we're processing here. P.S. We'll move this to YAML in the end. I'd suggest explicitly defining which request methods we're processing here.
P.S. We'll move this to YAML in the end.
|
|||||||
|
{
|
||||||
|
#[Route('/', name: 'homepage')]
|
||||||
|
public function new(Request $request, PasteRepository $pasteRepository): Response
|
||||||
|
{
|
||||||
|
$pasteData = new PasteFormData();
|
||||||
|
$form = $this->createForm(PasteForm::class, $pasteData);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$pasteData = $form->getData();
|
||||||
|
|
||||||
|
$paste = Paste::fromFormDataAndIp($pasteData, $request->getClientIp());
|
||||||
|
$pasteRepository->save($paste, true);
|
||||||
|
|
||||||
|
return $this->redirectToRoute('showpaste', ['id' => $paste->id, 'secret' => $paste->secret]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('paste.html.twig', [
|
||||||
|
'form' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/{secret}', name: 'showpaste')]
|
||||||
skobkin
commented
`show_paste` at least.
|
|||||||
|
public function showPaste(PasteRepository $pasteRepository, string $id, ?string $secret = NULL): Response
|
||||||
|
{
|
||||||
|
$paste = $pasteRepository->findOneBy(['id' => $id, 'secret' => $secret]);
|
||||||
|
$pasteData = new PasteFormData($paste);
|
||||||
|
$form = $this->createForm(PasteForm::class, $pasteData);
|
||||||
|
|
||||||
|
return $this->render('show_paste.html.twig', [
|
||||||
|
'form' => $form,
|
||||||
|
'highlighted_text' => CodeHighlighter::highlight($pasteData->language, $pasteData->text)
|
||||||
skobkin
commented
I'd suggest you implementing Twig extension wih filter and function to use highlighter in the template. I'd suggest you implementing Twig extension wih filter and function to use highlighter in the template.
|
|||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
40
src/DTO/PasteFormData.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace App\DTO;
|
||||||
|
|
||||||
|
use App\Entity\Paste;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
|
||||||
|
class PasteFormData
|
||||||
|
{
|
||||||
|
#[Assert\NotBlank]
|
||||||
|
public string $text;
|
||||||
|
public bool $private;
|
||||||
|
public ?string $language = null;
|
||||||
|
public ?string $description = null;
|
||||||
|
public ?string $filename = null;
|
||||||
|
public ?string $author = null;
|
||||||
|
public ?\DateTimeImmutable $expirationDate;
|
||||||
|
|
||||||
skobkin marked this conversation as resolved
skobkin
commented
Why Why `string` and `Assert\NotBlank`?
|
|||||||
|
public function __construct(?Paste $paste = null)
|
||||||
|
{
|
||||||
skobkin marked this conversation as resolved
skobkin
commented
Is this validation being processed BEFORE storing the data in the DTO? Is this validation being processed BEFORE storing the data in the DTO?
If not, it's pointless as with `bool` field earlier.
|
|||||||
|
if ($paste === null) {
|
||||||
|
return;
|
||||||
skobkin marked this conversation as resolved
skobkin
commented
Or you can make constructor Or you can make constructor `private`, use property promotion and add `fromPaste()` method to create it from the entity.
|
|||||||
|
}
|
||||||
|
|
||||||
|
$this->fromPaste($paste);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fromPaste(Paste $paste)
|
||||||
|
{
|
||||||
|
$this->text = $paste->text;
|
||||||
|
$this->private = $paste->secret !== null;
|
||||||
|
$this->language = $paste->language;
|
||||||
|
$this->description = $paste->description;
|
||||||
|
$this->filename = $paste->filename;
|
||||||
|
$this->author = $paste->author !== null ? $paste->author : 'anonymous';
|
||||||
|
$this->expirationDate = $paste->expirationDate;
|
||||||
|
}
|
||||||
|
}
|
26
src/Entity/CodeHighlighter.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
skobkin
commented
Why Why `Entity`?
Are you storing it in the database? Does it have an identity?
|
|||||||
|
use \Highlight\Highlighter;
|
||||||
|
|
||||||
|
|
||||||
|
class CodeHighlighter
|
||||||
|
{
|
||||||
|
public static function highlight(?string $language, string $code): string
|
||||||
|
{
|
||||||
|
$hl = new Highlighter();
|
||||||
skobkin
commented
Why not inject into constructor? Why not inject into constructor?
|
|||||||
|
|
||||||
|
try {
|
||||||
|
// Highlight some code.
|
||||||
|
$highlighted = $hl->highlight($language, $code);
|
||||||
|
$highlighted_text = "<pre><code class=\"hljs {$highlighted->language}\">".$highlighted->value.'</code></pre>';
|
||||||
skobkin
commented
Why do you use HTML here and not in the template? Check the comment I left in Why do you use HTML here and not in the template? Check the comment I left in `PasterController::showPaste()`.
skobkin
commented
Also naming here is not good. You have
Also naming here is not good. You have `$highlighted` and `$highlighted_text`.
- `$highlighted_text` is not in `$camelCase`
- Both variables contain text, both contain code. If it shouldn't be removed, I'd say to think a bit more on naming.
|
|||||||
|
}
|
||||||
|
catch (\DomainException $e) {
|
||||||
|
// This is thrown if the specified language does not exist
|
||||||
|
$highlighted_text = '<pre><code>'.htmlentities($code).'</code></pre>';
|
||||||
skobkin
commented
Again why HTML here? Again why HTML here?
|
|||||||
|
}
|
||||||
|
|
||||||
|
return $highlighted_text;
|
||||||
|
}
|
||||||
|
}
|
52
src/Entity/Paste.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
use App\DTO\PasteFormData;
|
||||||
|
use App\Repository\PasteRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: PasteRepository::class)]
|
||||||
skobkin
commented
I'd suggest explicitly defining Especially since you're defining I'd suggest explicitly defining `Table` attribute here.
Especially since you're defining `Column` attributes below ⬇️
skobkin
commented
I'd also suggest to make this entity readonly. It'll give some performance benefits. You can read about that here. I'd also suggest to make this entity readonly. It'll give some performance benefits.
You can read about that [here](https://www.doctrine-project.org/projects/doctrine-orm/en/2.15/reference/attributes-reference.html#attrref_entity).
|
|||||||
|
class Paste
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
public readonly int $id;
|
||||||
skobkin
commented
Why not also constructor promotion though? Why not also constructor promotion though?
|
|||||||
|
|
||||||
|
private function __construct(
|
||||||
|
#[ORM\Column(type: 'text', nullable: false)]
|
||||||
|
public readonly string $text,
|
||||||
|
#[ORM\Column(type: 'string', length: 25, nullable: true)]
|
||||||
|
public readonly ?string $language,
|
||||||
|
#[ORM\Column(type: 'text', nullable: true)]
|
||||||
|
public readonly ?string $description,
|
||||||
|
#[ORM\Column(type: 'string', length: 128, nullable: true)]
|
||||||
|
public readonly ?string $filename,
|
||||||
skobkin
commented
Why 128? Why 128?
|
|||||||
|
#[ORM\Column(type: 'string', length: 128, nullable: true)]
|
||||||
|
public readonly ?string $author,
|
||||||
|
#[ORM\Column(type: 'datetime_immutable', nullable: false)]
|
||||||
|
public readonly \DateTimeImmutable $publishDate,
|
||||||
|
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||||
|
public readonly ?\DateTimeImmutable $expirationDate,
|
||||||
|
#[ORM\Column(type: 'string', length: 39, nullable: false)]
|
||||||
|
public readonly string $ip,
|
||||||
|
#[ORM\Column(type: 'string', length: 40, nullable: true)]
|
||||||
|
public readonly ?string $secret,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function fromFormDataAndIp(PasteFormData $pasteFormData, $ip): Paste
|
||||||
skobkin
commented
You can use You can use `self` as return type hint too.
|
|||||||
|
{
|
||||||
|
return new self(
|
||||||
|
$pasteFormData->text,
|
||||||
|
$pasteFormData->language,
|
||||||
|
$pasteFormData->description,
|
||||||
|
$pasteFormData->filename,
|
||||||
|
$pasteFormData->author,
|
||||||
|
new \DateTimeImmutable(),
|
||||||
|
$pasteFormData->expirationDate,
|
||||||
|
$ip,
|
||||||
|
$pasteFormData->private ? \hash('sha1', \random_bytes(25)) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
43
src/Form/Type/PasteForm.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace App\Form\Type;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
|
||||||
|
|
||||||
|
class PasteForm extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('language', ChoiceType::class, [
|
||||||
|
'choices' => [
|
||||||
|
'Python' => 'python',
|
||||||
|
'PHP' => 'php',
|
||||||
|
'Plain text' => NULL,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->add('description', TextType::class, ['required' => false])
|
||||||
|
->add('text', TextareaType::class)
|
||||||
|
->add('author', TextType::class, ['attr' => ['maxlength' => 128], 'required' => false])
|
||||||
|
->add('filename', TextType::class, ['required' => false, 'attr' => ['maxlength' =>128]])
|
||||||
skobkin
commented
Why 128? Why 128?
|
|||||||
|
->add('expirationDate', DateTimeType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'date_widget' => 'single_text',
|
||||||
|
'input' => 'datetime_immutable',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->add('private', CheckboxType::class, ['required' => false])
|
||||||
|
->add('save', SubmitType::class)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
26
src/Repository/PasteRepository.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
skobkin marked this conversation as resolved
skobkin
commented
Can't leave a comment for Do you need it? Can't leave a comment for `src/Repository/.gitignore`, so I'll ask here.
Do you need it?
|
|||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Paste;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
class PasteRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Paste::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(Paste $paste, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$entityManager = $this->getEntityManager();
|
||||||
|
$entityManager->persist($paste);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
symfony.lock
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
{
|
||||||
|
"doctrine/doctrine-bundle": {
|
||||||
|
"version": "2.10",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "2.10",
|
||||||
|
"ref": "f0d8c9a4da17815830aac0d63e153a940ae176bb"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"symfony/console": {
|
||||||
|
"version": "6.3",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "5.3",
|
||||||
|
"ref": "da0c8be8157600ad34f10ff0c9cc91232522e047"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin/console"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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/maker-bundle": {
|
||||||
|
"version": "1.50",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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/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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
15
templates/base.html.twig
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{% block title %}Copypaste{% 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>">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
skobkin
commented
It's better not to rely on external CDN's when you can. It's better not to rely on external CDN's when you can.
You can just save needed resources in the repository since we're not making our front-end too complex to make dynamic build.
|
|||||||
|
{% block stylesheets %}
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||||
skobkin
commented
Same here. Same here.
|
|||||||
|
</body>
|
||||||
|
</html>
|
6
templates/paste.html.twig
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends "base.html.twig" %}
|
||||||
skobkin
commented
Why double quotes again? Why double quotes again?
skobkin
commented
BTW, if you're including it in the BTW, if you're including it in the `show_paste.html.twig`, then you don't need to extend `base.html.twig` here.
|
|||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ form(form) }}
|
||||||
|
{% endblock %}
|
||||||
|
|
24
templates/show_paste.html.twig
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends "base.html.twig" %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link rel="stylesheet" href="/css/ocean.css"></link>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||||
skobkin
commented
Do not forget to name classes and ID's properly. Do not forget to name classes and ID's properly.
|
|||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="show-tab" data-bs-toggle="tab" data-bs-target="#show" type="button" role="tab" aria-controls="show" aria-selected="true">Show</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#edit" type="button" role="tab" aria-controls="edit" aria-selected="false">Edit</button>
|
||||||
skobkin
commented
`profile-tab`?
|
|||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="myTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="show" role="tabpanel" aria-labelledby="show-tab">
|
||||||
|
{{ highlighted_text|raw }}
|
||||||
skobkin
commented
As I said above, better implement Twig extension and use it here. As I said above, better implement Twig extension and use it here.
|
|||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="edit" role="tabpanel" aria-labelledby="edit-tab">
|
||||||
|
{% include 'paste.html.twig' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Why so specific?