WIP: feature_paste #1

Draft
Miroslavsckaya wants to merge 24 commits from feature_paste into master
21 changed files with 3059 additions and 4 deletions
Showing only changes of commit 7e8de026e1 - Show all commits

10
.env
View file

@ -18,3 +18,13 @@
APP_ENV=dev
APP_SECRET=90c78b17302e6ff2e14213f129e3f5f4
###< 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 ###

View file

@ -7,15 +7,19 @@
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/doctrine-bundle": "^2.10",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.15",
"symfony/console": "6.3.*",
Review

Why so specific?

Why so specific?
"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.*"
},
"require-dev": {
},
"config": {
"allow-plugins": {
"php-http/discovery": true,
@ -63,5 +67,8 @@
"allow-contrib": false,
"require": "6.3.*"
}
},
"require-dev": {
"symfony/maker-bundle": "^1.50"
}
}

2550
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,4 +2,8 @@
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],
];

View 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

View 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

View file

@ -0,0 +1,6 @@
twig:
default_path: '%kernel.project_dir%/templates'
when@test:
twig:
strict_variables: true

View 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

0
migrations/.gitignore vendored Normal file
View file

View file

@ -0,0 +1,34 @@
<?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 Version20230720115905 extends AbstractMigration
skobkin marked this conversation as resolved
Review

Do you need it here if it's empty?

Do you need it here if it's empty?
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
Review

This shouldn't be here.

This shouldn't be here.
// this up() migration is auto-generated, please modify it to your needs
$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, private BOOLEAN NOT NULL, language VARCHAR(25) NOT NULL, description TEXT NOT NULL, filename VARCHAR(128) NOT NULL, author VARCHAR(128) NOT NULL, publish_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, expiration_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, ip VARCHAR(15) NOT NULL, secret VARCHAR(40) NOT NULL, PRIMARY KEY(id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('DROP SEQUENCE paste_id_seq CASCADE');
$this->addSql('DROP TABLE paste');
}
}

View file

@ -0,0 +1,38 @@
<?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 Version20230720125632 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 paste ALTER language DROP NOT NULL');
$this->addSql('ALTER TABLE paste ALTER description DROP NOT NULL');
$this->addSql('ALTER TABLE paste ALTER filename DROP NOT NULL');
$this->addSql('ALTER TABLE paste ALTER expiration_date DROP NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE paste ALTER language SET NOT NULL');
$this->addSql('ALTER TABLE paste ALTER description SET NOT NULL');
$this->addSql('ALTER TABLE paste ALTER filename SET NOT NULL');
$this->addSql('ALTER TABLE paste ALTER expiration_date SET NOT NULL');
}
}

View file

@ -0,0 +1,32 @@
<?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 Version20230720130259 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 paste ALTER secret DROP NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE paste ALTER secret SET NOT NULL');
}
}

View file

@ -0,0 +1,54 @@
<?php
skobkin marked this conversation as resolved
Review

I can't leave you a comment on src/Controller/.gitignore, so I'll ask here.

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?
namespace App\Controller;
use App\Form\Type\PasteForm;
use App\Entity\Paste;
use App\Repository\PasteRepository;
use Doctrine\ORM\EntityManagerInterface;
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
{
#[Route('/')]
Review

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.
public function new(Request $request, EntityManagerInterface $entityManager): Response {
$paste = new Paste();
$form = $this->createForm(PasteForm::class, $paste);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$paste = $form->getData();
$paste->setIp($request->getClientIp());
$paste->setPublishDate(new \DateTime());
if ($paste->isPrivate()) {
$paste->setSecret(hash("sha1", random_bytes(25)));
}
$entityManager->persist($paste);
$entityManager->flush();
return $this->redirectToRoute($request->attributes->get('_route'));
}
return $this->render('paste.html.twig', [
'form' => $form,
Review

show_paste at least.

`show_paste` at least.
]);
}
#[Route('/{id}/{secret}')]
public function show_paste(PasteRepository $pasteRepository, Request $request, string $id, ?string $secret=NULL): Response {
$paste = $pasteRepository->findOneBy(["id" => $id, "secret" => $secret]);
$form = $this->createForm(PasteForm::class, $paste);
return $this->render('paste.html.twig', [
Review

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.
'form' => $form,
]);
}
}

0
src/Entity/.gitignore vendored Normal file
View file

139
src/Entity/Paste.php Normal file
View file

@ -0,0 +1,139 @@
<?php
namespace App\Entity;
use App\Repository\PasteRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: PasteRepository::class)]
Review

I'd suggest explicitly defining Table attribute here.

Especially since you're defining Column attributes below ⬇️

I'd suggest explicitly defining `Table` attribute here. Especially since you're defining `Column` attributes below ⬇️
Review

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(nullable: false)]
private int $id;
Review

Why not also constructor promotion though?

Why not also constructor promotion though?
#[ORM\Column(type: "text", nullable: false)]
#[Assert\NotBlank]
private string $text;
#[ORM\Column(type: "boolean", nullable: false)]
#[Assert\Type(\boolean::class)]
private bool $private;
#[ORM\Column(length: 25, nullable: true)]
Review

Why 128?

Why 128?
private ?string $language;
#[ORM\Column(type: "text", nullable: true)]
private ?string $description;
#[ORM\Column(length: 128, nullable: true)]
private ?string $filename;
skobkin marked this conversation as resolved
Review

IPv6?

IPv6?
#[ORM\Column(length: 128, nullable: false)]
#[Assert\NotBlank]
private string $author = "anonymous";
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: false)]
Review

You can use self as return type hint too.

You can use `self` as return type hint too.
#[Assert\Type(\DateTime::class)]
private \DateTime $publishDate;
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
#[Assert\Type(\DateTime::class)]
private ?\DateTime $expirationDate;
#[ORM\Column(length: 15, nullable: false)]
private string $ip;
#[ORM\Column(length: 40, nullable: true)]
private ?string $secret;
public function getId(): int {
return $this->id;
}
public function setId(int $id): void {
$this->id = $id;
}
public function getText(): string {
return $this->text;
}
public function setText(string $text): void {
$this->text = $text;
}
public function getLanguage(): ?string {
return $this->language;
}
public function setLanguage(?string $language): void {
$this->language = $language;
}
public function getDescription(): ?string {
return $this->description;
}
public function setDescription(?string $description): void {
$this->description = $description;
}
public function getFilename(): ?string {
return $this->filename;
}
public function setFilename(?string $filename): void {
$this->filename = $filename;
}
public function getAuthor(): string {
return $this->author;
}
public function setAuthor(string $author): void {
$this->author = $author;
}
public function getPublishDate(): \DateTime {
return $this->publishDate;
}
public function setPublishDate(\DateTime $date): void {
$this->publishDate = $date;
}
public function getExpirationDate(): ?\DateTime {
return $this->expirationDate;
}
public function setExpirationDate(?\DateTime $date): void {
$this->expirationDate = $date;
}
public function getIp(): string {
return $this->ip;
}
public function setIP(string $ip): void {
$this->ip = $ip;
}
public function getSecret(): ?string {
return $this->secret;
}
public function setSecret(?string $secret): void {
$this->secret = $secret;
}
public function isPrivate(): bool {
return $this->private;
}
public function setPrivate(bool $private): void {
$this->private = $private;
}
}

View file

@ -0,0 +1,28 @@
<?php
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]])
->add("filename", TextType::class, ["required" => false, "attr" => ["maxlength" =>128]])
->add("expirationDate", DateTimeType::class, ["required" => false, "date_widget" => "single_text", "input" => "datetime"])
->add("private", CheckboxType::class, ["required" => false])
->add("save", SubmitType::class)
;
}
}

0
src/Repository/.gitignore vendored Normal file
View file

View file

@ -0,0 +1,14 @@
<?php
skobkin marked this conversation as resolved
Review

Can't leave a comment for src/Repository/.gitignore, so I'll ask here.

Do you need it?

Can't leave a comment for `src/Repository/.gitignore`, so I'll ask here. Do you need it?
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);
}
}

View file

@ -1,4 +1,31 @@
{
"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": {
@ -42,6 +69,15 @@
"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": {
@ -54,5 +90,30 @@
"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"
]
}
}

16
templates/base.html.twig Normal file
View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<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 %}
Review

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.

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.
{% endblock %}
{% block javascripts %}
{% endblock %}
</head>
<body>
Review

Same here.

Same here.
{% block body %}{% endblock %}
</body>
</html>

View file

@ -0,0 +1 @@
{{ form(form) }}
Review

Why double quotes again?

Why double quotes again?
Review

BTW, if you're including it in the show_paste.html.twig, then you don't need to extend base.html.twig here.

BTW, if you're including it in the `show_paste.html.twig`, then you don't need to extend `base.html.twig` here.