@ -1,3 +0,0 @@ | |||
body { | |||
background-color: skyblue; | |||
} |
@ -0,0 +1,8 @@ | |||
version: '3' | |||
services: | |||
###> doctrine/doctrine-bundle ### | |||
database: | |||
ports: | |||
- "5432" | |||
###< doctrine/doctrine-bundle ### |
@ -0,0 +1,26 @@ | |||
version: '3' | |||
services: | |||
###> doctrine/doctrine-bundle ### | |||
database: | |||
image: postgres:${POSTGRES_VERSION:-16}-alpine | |||
environment: | |||
POSTGRES_DB: ${POSTGRES_DB:-app} | |||
# You should definitely change the password in production | |||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} | |||
POSTGRES_USER: ${POSTGRES_USER:-app} | |||
healthcheck: | |||
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"] | |||
timeout: 5s | |||
retries: 5 | |||
start_period: 60s | |||
volumes: | |||
- database_data:/var/lib/postgresql/data:rw | |||
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! | |||
# - ./docker/db/data:/var/lib/postgresql/data:rw | |||
###< doctrine/doctrine-bundle ### | |||
volumes: | |||
###> doctrine/doctrine-bundle ### | |||
database_data: | |||
###< doctrine/doctrine-bundle ### |
@ -0,0 +1,52 @@ | |||
doctrine: | |||
dbal: | |||
url: '%env(resolve:DATABASE_URL)%' | |||
# IMPORTANT: You MUST configure your server version, | |||
# either here or in the DATABASE_URL env var (see .env file) | |||
#server_version: '16' | |||
profiling_collect_backtrace: '%kernel.debug%' | |||
use_savepoints: true | |||
orm: | |||
auto_generate_proxy_classes: true | |||
enable_lazy_ghost_objects: true | |||
report_fields_where_declared: true | |||
validate_xml_mapping: true | |||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware | |||
auto_mapping: true | |||
mappings: | |||
App: | |||
type: attribute | |||
is_bundle: false | |||
dir: '%kernel.project_dir%/src/Entity' | |||
prefix: 'App\Entity' | |||
alias: App | |||
controller_resolver: | |||
auto_mapping: false | |||
when@test: | |||
doctrine: | |||
dbal: | |||
# "TEST_TOKEN" is typically set by ParaTest | |||
dbname_suffix: '_test%env(default::TEST_TOKEN)%' | |||
when@prod: | |||
doctrine: | |||
orm: | |||
auto_generate_proxy_classes: false | |||
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' | |||
query_cache_driver: | |||
type: pool | |||
pool: doctrine.system_cache_pool | |||
result_cache_driver: | |||
type: pool | |||
pool: doctrine.result_cache_pool | |||
framework: | |||
cache: | |||
pools: | |||
doctrine.result_cache_pool: | |||
adapter: cache.app | |||
doctrine.system_cache_pool: | |||
adapter: cache.system |
@ -0,0 +1,6 @@ | |||
doctrine_migrations: | |||
migrations_paths: | |||
# namespace is arbitrary but should be different from App\Migrations | |||
# as migrations classes should NOT be autoloaded | |||
'DoctrineMigrations': '%kernel.project_dir%/migrations' | |||
enable_profiler: false |
@ -0,0 +1,7 @@ | |||
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html | |||
# See the official DoctrineExtensions documentation for more details: https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc | |||
stof_doctrine_extensions: | |||
default_locale: fr_FR | |||
orm: | |||
default: | |||
sluggable: true |
@ -1,7 +1,11 @@ | |||
twig: | |||
file_name_pattern: '*.twig' | |||
form_themes: ['bootstrap_5_layout.html.twig'] | |||
globals: | |||
title: Gestionnaire de tâches simple | |||
menu: | |||
header: | |||
- { route: 'app_project', label: 'Projets' } | |||
when@test: | |||
twig: | |||
@ -0,0 +1,11 @@ | |||
framework: | |||
validation: | |||
# Enables validator auto-mapping support. | |||
# For instance, basic validation constraints will be inferred from Doctrine's metadata. | |||
#auto_mapping: | |||
# App\Entity\: [] | |||
when@test: | |||
framework: | |||
validation: | |||
not_compromised_password: false |
@ -0,0 +1,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 } |
@ -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 |
@ -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 Version20240720132139 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 project (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, description CLOB DEFAULT NULL)'); | |||
} | |||
public function down(Schema $schema): void | |||
{ | |||
// this down() migration is auto-generated, please modify it to your needs | |||
$this->addSql('DROP TABLE project'); | |||
} | |||
} |
@ -0,0 +1,40 @@ | |||
<?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 Version20240720143743 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 TEMPORARY TABLE __temp__project AS SELECT id, name, description FROM project'); | |||
$this->addSql('DROP TABLE project'); | |||
$this->addSql('CREATE TABLE project (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, description CLOB DEFAULT NULL, slug VARCHAR(255) NOT NULL)'); | |||
$this->addSql('INSERT INTO project (id, name, description) SELECT id, name, description FROM __temp__project'); | |||
$this->addSql('DROP TABLE __temp__project'); | |||
$this->addSql('CREATE UNIQUE INDEX UNIQ_2FB3D0EE989D9B62 ON project (slug)'); | |||
} | |||
public function down(Schema $schema): void | |||
{ | |||
// this down() migration is auto-generated, please modify it to your needs | |||
$this->addSql('CREATE TEMPORARY TABLE __temp__project AS SELECT id, name, description FROM project'); | |||
$this->addSql('DROP TABLE project'); | |||
$this->addSql('CREATE TABLE project (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, description CLOB DEFAULT NULL)'); | |||
$this->addSql('INSERT INTO project (id, name, description) SELECT id, name, description FROM __temp__project'); | |||
$this->addSql('DROP TABLE __temp__project'); | |||
} | |||
} |
@ -0,0 +1,173 @@ | |||
<?php | |||
namespace App\Controller; | |||
use App\Entity\Project; | |||
use App\Form\ProjectType; | |||
use Doctrine\ORM\EntityManagerInterface; | |||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |||
use Symfony\Component\Form\Extension\Core\Type\SubmitType; | |||
use Symfony\Component\HttpFoundation\Request; | |||
use Symfony\Component\HttpFoundation\Response; | |||
use Symfony\Component\Routing\Attribute\Route; | |||
#[Route('/project')] | |||
class ProjectController extends AbstractController | |||
{ | |||
#[Route('/', name: 'app_project')] | |||
public function index(EntityManagerInterface $entityManager): Response | |||
{ | |||
$repository = $entityManager->getRepository(Project::class); | |||
$projects = $repository->findAll(); | |||
$this->addFlash( | |||
'info', | |||
sprintf('%s projet%s trouvé%s', count($projects), (count($projects) > 1 ? 's' : ''), (count($projects) > 1 ? 's' : '')) | |||
); | |||
return $this->render('project/index.html.twig', [ | |||
'projects' => $projects, | |||
]); | |||
} | |||
#[Route('/create', name: 'app_project_create')] | |||
public function create(Request $request, EntityManagerInterface $entityManager): Response | |||
{ | |||
$project = new Project(); | |||
$createForm = $this->createForm(ProjectType::class, $project); | |||
$createForm->add('submit', SubmitType::class, [ | |||
'label' => 'Créer', | |||
]); | |||
$createForm->handleRequest($request); | |||
if ($createForm->isSubmitted() and $createForm->isValid()) { | |||
$project = $createForm->getData(); | |||
try { | |||
$entityManager->persist($project); | |||
$entityManager->flush(); | |||
$this->addFlash( | |||
'success', | |||
'Projet créé !' | |||
); | |||
return $this->redirectToRoute('app_project'); | |||
} catch (\Exception $exception) { | |||
$this->addFlash( | |||
'danger', | |||
'Impossible de créer le projet !' | |||
); | |||
} | |||
} | |||
return $this->render('project/create.html.twig', [ | |||
'create_form' => $createForm, | |||
]); | |||
} | |||
#[Route('/show/{slug}', name: 'app_project_show')] | |||
public function show(EntityManagerInterface $entityManager, $slug): Response | |||
{ | |||
$repository = $entityManager->getRepository(Project::class); | |||
$project = $repository->findOneBySlug($slug); | |||
if (!$project) { | |||
$this->addFlash( | |||
'warning', | |||
'Projet non trouvé !' | |||
); | |||
return $this->redirectToRoute('app_project'); | |||
} | |||
return $this->render('project/show.html.twig', [ | |||
'project' => $project, | |||
]); | |||
} | |||
#[Route('/update/{slug}', name: 'app_project_update')] | |||
public function update(Request $request, EntityManagerInterface $entityManager, $slug): Response | |||
{ | |||
$repository = $entityManager->getRepository(Project::class); | |||
$project = $repository->findOneBySlug($slug); | |||
if (!$project) { | |||
$this->addFlash( | |||
'warning', | |||
'Projet non trouvé !' | |||
); | |||
return $this->redirectToRoute('app_project'); | |||
} | |||
$updateForm = $this->createForm(ProjectType::class, $project); | |||
$updateForm->add('submit', SubmitType::class, [ | |||
'label' => 'Modifier', | |||
]); | |||
$updateForm->handleRequest($request); | |||
if ($updateForm->isSubmitted() and $updateForm->isValid()) { | |||
$project = $updateForm->getData(); | |||
try { | |||
$entityManager->persist($project); | |||
$entityManager->flush(); | |||
$this->addFlash( | |||
'success', | |||
'Projet modifié !' | |||
); | |||
return $this->redirectToRoute('app_project'); | |||
} catch (\Exception $exception) { | |||
$this->addFlash( | |||
'danger', | |||
'Impossible de mopdifier le projet !' | |||
); | |||
} | |||
} | |||
return $this->render('project/update.html.twig', [ | |||
'project' => $project, | |||
'update_form' => $updateForm, | |||
]); | |||
} | |||
#[Route('/remove/{slug}', name: 'app_project_remove')] | |||
public function remove(EntityManagerInterface $entityManager, $slug): Response | |||
{ | |||
$repository = $entityManager->getRepository(Project::class); | |||
$project = $repository->findOneBySlug($slug); | |||
if (!$project) { | |||
$this->addFlash( | |||
'warning', | |||
'Projet non trouvé !' | |||
); | |||
return $this->redirectToRoute('app_project'); | |||
} | |||
try { | |||
$entityManager->remove($project); | |||
$entityManager->flush(); | |||
$this->addFlash( | |||
'success', | |||
'Projet supprimé !' | |||
); | |||
return $this->redirectToRoute('app_project'); | |||
} catch (\Exception $exception) { | |||
$this->addFlash( | |||
'danger', | |||
'Impossible de supprimer le projet !' | |||
); | |||
} | |||
return $this->redirectToRoute('app_project'); | |||
} | |||
} |
@ -0,0 +1,62 @@ | |||
<?php | |||
namespace App\Entity; | |||
use App\Repository\ProjectRepository; | |||
use Doctrine\DBAL\Types\Types; | |||
use Doctrine\ORM\Mapping as ORM; | |||
use Gedmo\Mapping\Annotation as Gedmo; | |||
#[ORM\Entity(repositoryClass: ProjectRepository::class)] | |||
class Project | |||
{ | |||
#[ORM\Id] | |||
#[ORM\GeneratedValue] | |||
#[ORM\Column] | |||
private ?int $id = null; | |||
#[ORM\Column(length: 255)] | |||
private ?string $name = null; | |||
#[ORM\Column(type: Types::TEXT, nullable: true)] | |||
private ?string $description = null; | |||
#[ORM\Column(length: 255, unique: true)] | |||
#[Gedmo\Slug(fields: ['name'])] | |||
private ?string $slug = null; | |||
public function getId(): ?int | |||
{ | |||
return $this->id; | |||
} | |||
public function getName(): ?string | |||
{ | |||
return $this->name; | |||
} | |||
public function setName(string $name): static | |||
{ | |||
$this->name = $name; | |||
return $this; | |||
} | |||
public function getDescription(): ?string | |||
{ | |||
return $this->description; | |||
} | |||
public function setDescription(?string $description): static | |||
{ | |||
$this->description = $description; | |||
return $this; | |||
} | |||
public function getSlug(): ?string | |||
{ | |||
return $this->slug; | |||
} | |||
} |
@ -0,0 +1,31 @@ | |||
<?php | |||
namespace App\Form; | |||
use App\Entity\Project; | |||
use Symfony\Component\Form\AbstractType; | |||
use Symfony\Component\Form\FormBuilderInterface; | |||
use Symfony\Component\OptionsResolver\OptionsResolver; | |||
class ProjectType extends AbstractType | |||
{ | |||
public function buildForm(FormBuilderInterface $builder, array $options): void | |||
{ | |||
$builder | |||
->add('name', null, [ | |||
'label' => 'Nom', | |||
]) | |||
->add('description', null, [ | |||
'label' => 'Description', | |||
'help' => 'On peut mettre du Markdown ici…', | |||
]) | |||
; | |||
} | |||
public function configureOptions(OptionsResolver $resolver): void | |||
{ | |||
$resolver->setDefaults([ | |||
'data_class' => Project::class, | |||
]); | |||
} | |||
} |
@ -0,0 +1,43 @@ | |||
<?php | |||
namespace App\Repository; | |||
use App\Entity\Project; | |||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | |||
use Doctrine\Persistence\ManagerRegistry; | |||
/** | |||
* @extends ServiceEntityRepository<Project> | |||
*/ | |||
class ProjectRepository extends ServiceEntityRepository | |||
{ | |||
public function __construct(ManagerRegistry $registry) | |||
{ | |||
parent::__construct($registry, Project::class); | |||
} | |||
// /** | |||
// * @return Project[] Returns an array of Project 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): ?Project | |||
// { | |||
// return $this->createQueryBuilder('p') | |||
// ->andWhere('p.exampleField = :val') | |||
// ->setParameter('val', $value) | |||
// ->getQuery() | |||
// ->getOneOrNullResult() | |||
// ; | |||
// } | |||
} |
@ -0,0 +1,7 @@ | |||
<div class="container"> | |||
<div class="row bg-body-tertiary border-top p-2"> | |||
<div class="col"> | |||
<p class="text-muted mb-0"></p> | |||
</div> | |||
</div> | |||
</div> |
@ -1,37 +1,18 @@ | |||
<header> | |||
<nav class="navbar navbar-expand-lg bg-body-tertiary"> | |||
<div class="container-fluid"> | |||
<a class="navbar-brand" href="#">Navbar</a> | |||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> | |||
<a class="navbar-brand" href="{{ path('app_home') }}">{{ title }}</a> | |||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"> | |||
<span class="navbar-toggler-icon"></span> | |||
</button> | |||
<div class="collapse navbar-collapse" id="navbarSupportedContent"> | |||
<div class="collapse navbar-collapse" id="navbar"> | |||
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> | |||
{% for item in menu.header %} | |||
<li class="nav-item"> | |||
<a class="nav-link active" aria-current="page" href="#">Home</a> | |||
</li> | |||
<li class="nav-item"> | |||
<a class="nav-link" href="#">Link</a> | |||
</li> | |||
<li class="nav-item dropdown"> | |||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> | |||
Dropdown | |||
</a> | |||
<ul class="dropdown-menu"> | |||
<li><a class="dropdown-item" href="#">Action</a></li> | |||
<li><a class="dropdown-item" href="#">Another action</a></li> | |||
<li><hr class="dropdown-divider"></li> | |||
<li><a class="dropdown-item" href="#">Something else here</a></li> | |||
</ul> | |||
</li> | |||
<li class="nav-item"> | |||
<a class="nav-link disabled" aria-disabled="true">Disabled</a> | |||
<a {% if app.request.attributes.get('_route') == item.route %}class="nav-link active" aria-current="page"{% else %}class="nav-link"{% endif %} href="{{ path(item.route) }}">{{ item.label }}</a> | |||
</li> | |||
{% endfor %} | |||
</ul> | |||
<form class="d-flex" role="search"> | |||
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"> | |||
<button class="btn btn-outline-success" type="submit">Search</button> | |||
</form> | |||
</div> | |||
</div> | |||
</nav> | |||
@ -1 +0,0 @@ | |||
{% extends 'base.html.twig' %} |
@ -1,5 +1,11 @@ | |||
{% extends 'home/base.html.twig' %} | |||
{% extends 'base.html.twig' %} | |||
{% block main %} | |||
home | |||
<div class="container"> | |||
<div class="row"> | |||
<div class="col"> | |||
ici la page d’accueil | |||
</div> | |||
</div> | |||
</div> | |||
{% endblock %} |
@ -0,0 +1,12 @@ | |||
{% extends 'base.html.twig' %} | |||
{% block breadcrumb %} | |||
<li class="breadcrumb-item"><a href="{{ path('app_project') }}">Projets</a></li> | |||
<li class="breadcrumb-item"><a href="{{ path('app_project_create') }}">Créer un nouveau projet</a></li> | |||
{% endblock %} | |||
{% block page_title %}Créer un nouveau projet{% endblock %} | |||
{% block page_content %} | |||
{{ form(create_form) }} | |||
{% endblock %} |
@ -0,0 +1,31 @@ | |||
{% extends 'base.html.twig' %} | |||
{% block breadcrumb %} | |||
<li class="breadcrumb-item"><a href="{{ path('app_project') }}">Projets</a></li> | |||
{% endblock %} | |||
{% block page_title %}Projets{% endblock %} | |||
{% block page_content %} | |||
<div class="row"> | |||
<div class="col mb-3"> | |||
<a href="{{ path('app_project_create') }}" class="btn btn-primary">Créer un projet</a> | |||
</div> | |||
</div> | |||
{% if projects is not empty %} | |||
<div class="row"> | |||
{% for project in projects %} | |||
<div class="col col-md-4 mb-3"> | |||
<div class="card h-100"> | |||
<div class="card-body"> | |||
<h2 class="card-title">{{ project.name }}</h2> | |||
{% if project.description %}<p class="card-text">{{ project.description|markdown_to_html }}</p>{% endif %} | |||
<a href="{{ path('app_project_show', {'slug': project.slug}) }}" class="btn btn-primary">Voir</a> | |||
</div> | |||
</div> | |||
</div> | |||
{% endfor %} | |||
</div> | |||
{% endif %} | |||
{% endblock %} |
@ -0,0 +1,21 @@ | |||
{% extends 'base.html.twig' %} | |||
{% block breadcrumb %} | |||
<li class="breadcrumb-item"><a href="{{ path('app_project') }}">Projets</a></li> | |||
<li class="breadcrumb-item"><a href="{{ path('app_project_show', {'slug': project.slug}) }}">Projet {{ project.name }}</a></li> | |||
{% endblock %} | |||
{% block page_title %}Projet {{ project.name }}{% endblock %} | |||
{% block page_content %} | |||
<div class="row"> | |||
<div class="col mb-3"> | |||
<a href="{{ path('app_project_update', {'slug': project.slug}) }}" class="btn btn-primary">Modifier le projet</a> | |||
<a href="{{ path('app_project_remove', {'slug': project.slug}) }}" class="btn btn-primary">Supprimer le projet</a> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col mb-3 lead">{{ project.description|markdown_to_html }}</div> | |||
</div> | |||
{% endblock %} |
@ -0,0 +1,12 @@ | |||
{% extends 'base.html.twig' %} | |||
{% block breadcrumb %} | |||
<li class="breadcrumb-item"><a href="{{ path('app_project') }}">Projets</a></li> | |||
<li class="breadcrumb-item"><a href="{{ path('app_project_create') }}">Modifier le projet {{ project.name }}</a></li> | |||
{% endblock %} | |||
{% block page_title %}Modifier le projet {{ project.name }}{% endblock %} | |||
{% block page_content %} | |||
{{ form(update_form) }} | |||
{% endblock %} |