Minor changes
This commit is contained in:
@@ -103,10 +103,20 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var payload = await response.json();
|
var contentType = response.headers.get('content-type') || '';
|
||||||
|
var raw = await response.text();
|
||||||
|
var payload = {};
|
||||||
|
|
||||||
|
if (contentType.indexOf('application/json') !== -1) {
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(raw);
|
||||||
|
} catch (parseError) {
|
||||||
|
payload = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(payload.error || 'Le téléversement a échoué.');
|
throw new Error(payload.error || ('HTTP ' + response.status + (raw ? ' - ' + raw.slice(0, 180) : '')));
|
||||||
}
|
}
|
||||||
|
|
||||||
setFeedback('Image téléversée. Rafraîchissement…', false);
|
setFeedback('Image téléversée. Rafraîchissement…', false);
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ services:
|
|||||||
dockerfile: docker/php/Dockerfile
|
dockerfile: docker/php/Dockerfile
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
# Répertoire de travail de l'entrypoint : reçoit public/ compilé,
|
# Répertoire public partagé : reçoit les assets versionnés copiés par
|
||||||
# puis partagé avec Nginx via le mount ci-dessous.
|
# l'entrypoint, puis sert de racine statique à Nginx.
|
||||||
- ./data:/data
|
- ./data/public:/data/public
|
||||||
|
|
||||||
# Base SQLite et migrations : persistés entre redéploiements.
|
# Base SQLite et migrations : persistés entre redéploiements.
|
||||||
- ./data/database:/var/www/app/database
|
- ./data/database:/var/www/app/database
|
||||||
@@ -33,8 +33,8 @@ services:
|
|||||||
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-*}
|
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-*}
|
||||||
|
|
||||||
# bash /dev/tcp est disponible sur l'image Debian php:8.4-fpm sans dépendance
|
# bash /dev/tcp est disponible sur l'image Debian php:8.4-fpm sans dépendance
|
||||||
# supplémentaire. start_period laisse le temps à entrypoint.sh de terminer
|
# supplémentaire. start_period laisse le temps à entrypoint.sh de préparer
|
||||||
# (sync public/, permissions, caches) avant que les échecs ne comptent.
|
# les répertoires runtime et de synchroniser les assets publics.
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/9000'"]
|
test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/9000'"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
@@ -63,7 +63,7 @@ services:
|
|||||||
# Même environnement/fichiers que le runtime PHP pour provisionner exactement
|
# Même environnement/fichiers que le runtime PHP pour provisionner exactement
|
||||||
# la même base SQLite persistée.
|
# la même base SQLite persistée.
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data/public:/data/public
|
||||||
- ./data/database:/var/www/app/database
|
- ./data/database:/var/www/app/database
|
||||||
- ./data/var:/var/www/app/var
|
- ./data/var:/var/www/app/var
|
||||||
- ./data/public/media:/var/www/app/public/media
|
- ./data/public/media:/var/www/app/public/media
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
|
client_max_body_size 8M;
|
||||||
|
|
||||||
root /var/www/app/public;
|
root /var/www/app/public;
|
||||||
index index.php;
|
index index.php;
|
||||||
|
|
||||||
|
|||||||
18
docker/php/entrypoint.sh
Normal file → Executable file
18
docker/php/entrypoint.sh
Normal file → Executable file
@@ -2,19 +2,11 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
# Prépare les répertoires runtime/persistants.
|
# Prépare les répertoires runtime/persistants.
|
||||||
mkdir -p \
|
mkdir -p /data/public /data/public/media /var/www/app/database /var/www/app/public/media /var/www/app/var/cache/twig /var/www/app/var/cache/htmlpurifier /var/www/app/var/cache/di /var/www/app/var/logs
|
||||||
/data/public \
|
|
||||||
/var/www/app/database \
|
|
||||||
/var/www/app/public/media \
|
|
||||||
/var/www/app/var/cache/twig \
|
|
||||||
/var/www/app/var/cache/htmlpurifier \
|
|
||||||
/var/www/app/var/cache/di \
|
|
||||||
/var/www/app/var/logs
|
|
||||||
|
|
||||||
# Synchronise les fichiers publics versionnés (index.php, assets compilés, etc.)
|
# Synchronise uniquement les fichiers publics versionnés (index.php, assets
|
||||||
# vers le volume partagé. Le répertoire media est exclu car il contient les
|
# compilés, favicon, etc.) vers le répertoire partagé avec Nginx. Le dossier
|
||||||
# uploads utilisateurs et est géré séparément.
|
# media est préservé car il contient les uploads persistants.
|
||||||
mkdir -p /data/public/media
|
|
||||||
find /data/public -mindepth 1 -maxdepth 1 ! -name media -exec rm -rf {} +
|
find /data/public -mindepth 1 -maxdepth 1 ! -name media -exec rm -rf {} +
|
||||||
|
|
||||||
for item in /var/www/app/public/*; do
|
for item in /var/www/app/public/*; do
|
||||||
@@ -26,7 +18,7 @@ done
|
|||||||
|
|
||||||
# Permissions sur les répertoires persistants. Doit s'exécuter en root avant
|
# Permissions sur les répertoires persistants. Doit s'exécuter en root avant
|
||||||
# le démarrage de PHP-FPM.
|
# le démarrage de PHP-FPM.
|
||||||
chown -R www-data:www-data /data /var/www/app/database /var/www/app/var /var/www/app/public/media
|
chown -R www-data:www-data /data/public /var/www/app/database /var/www/app/var /var/www/app/public/media
|
||||||
|
|
||||||
# Invalide les caches compilés à chaque déploiement.
|
# Invalide les caches compilés à chaque déploiement.
|
||||||
rm -rf /var/www/app/var/cache/twig/*
|
rm -rf /var/www/app/var/cache/twig/*
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ error_log = /dev/stderr
|
|||||||
; La valeur doit être synchronisée avec session_name() dans public/index.php si modifiée.
|
; La valeur doit être synchronisée avec session_name() dans public/index.php si modifiée.
|
||||||
session.name = sid
|
session.name = sid
|
||||||
|
|
||||||
; En production, journaliser les erreurs sans les injecter dans les réponses HTTP
|
; Ne pas injecter les warnings/notices dans les réponses HTTP de prod
|
||||||
display_errors = Off
|
display_errors = Off
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use App\Post\Domain\Repository\PostRepositoryInterface;
|
|||||||
use Netig\Netslim\Kernel\Pagination\Application\PaginatedResult;
|
use Netig\Netslim\Kernel\Pagination\Application\PaginatedResult;
|
||||||
use Netig\Netslim\Kernel\Support\Exception\NotFoundException;
|
use Netig\Netslim\Kernel\Support\Exception\NotFoundException;
|
||||||
|
|
||||||
final class PostApplicationService implements PostServiceInterface
|
class PostApplicationService
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PostRepositoryInterface $postRepository,
|
private readonly PostRepositoryInterface $postRepository,
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Post\Application;
|
|
||||||
|
|
||||||
use App\Post\Domain\Entity\Post;
|
|
||||||
use Netig\Netslim\Kernel\Pagination\Application\PaginatedResult;
|
|
||||||
use Netig\Netslim\Kernel\Support\Exception\NotFoundException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contrat applicatif du domaine Post.
|
|
||||||
*/
|
|
||||||
interface PostServiceInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Retourne l'ensemble des articles, éventuellement filtrés par catégorie.
|
|
||||||
*
|
|
||||||
* @return list<Post>
|
|
||||||
*/
|
|
||||||
public function findAll(?int $categoryId = null): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne une page d'articles, éventuellement filtrés par catégorie.
|
|
||||||
*
|
|
||||||
* @return PaginatedResult<Post>
|
|
||||||
*/
|
|
||||||
public function findPaginated(int $page, int $perPage, ?int $categoryId = null): PaginatedResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne les articles les plus récents.
|
|
||||||
*
|
|
||||||
* @return list<Post>
|
|
||||||
*/
|
|
||||||
public function findRecent(int $limit = 20): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne les articles d'un auteur, éventuellement filtrés par catégorie.
|
|
||||||
*
|
|
||||||
* @return list<Post>
|
|
||||||
*/
|
|
||||||
public function findByUserId(int $userId, ?int $categoryId = null): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne une page d'articles pour un auteur, éventuellement filtrés par catégorie.
|
|
||||||
*
|
|
||||||
* @return PaginatedResult<Post>
|
|
||||||
*/
|
|
||||||
public function findByUserIdPaginated(int $userId, int $page, int $perPage, ?int $categoryId = null): PaginatedResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne un article par slug.
|
|
||||||
*
|
|
||||||
* @throws NotFoundException Si aucun article ne correspond au slug fourni.
|
|
||||||
*/
|
|
||||||
public function findBySlug(string $slug): Post;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne un article par identifiant.
|
|
||||||
*
|
|
||||||
* @throws NotFoundException Si aucun article ne correspond à l'identifiant fourni.
|
|
||||||
*/
|
|
||||||
public function findById(int $id): Post;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recherche des articles selon une requête textuelle et des filtres optionnels.
|
|
||||||
*
|
|
||||||
* @return list<Post>
|
|
||||||
*/
|
|
||||||
public function search(string $query, ?int $categoryId = null, ?int $authorId = null): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retourne une page de résultats de recherche.
|
|
||||||
*
|
|
||||||
* @return PaginatedResult<Post>
|
|
||||||
*/
|
|
||||||
public function searchPaginated(string $query, int $page, int $perPage, ?int $categoryId = null, ?int $authorId = null): PaginatedResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crée un article et retourne son identifiant persistant.
|
|
||||||
*/
|
|
||||||
public function create(string $title, string $content, int $authorId, ?int $categoryId = null): int;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Met à jour un article existant.
|
|
||||||
*
|
|
||||||
* @throws NotFoundException Si l'article n'existe pas.
|
|
||||||
*/
|
|
||||||
public function update(int $id, string $title, string $content, string $slug = '', ?int $categoryId = null): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supprime un article existant.
|
|
||||||
*
|
|
||||||
* @throws NotFoundException Si l'article n'existe pas.
|
|
||||||
*/
|
|
||||||
public function delete(int $id): void;
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Post\Application\PostApplicationService;
|
use App\Post\Application\PostApplicationService;
|
||||||
use App\Post\Application\PostServiceInterface;
|
|
||||||
use App\Post\Application\UseCase\CreatePost;
|
use App\Post\Application\UseCase\CreatePost;
|
||||||
use App\Post\Application\UseCase\DeletePost;
|
use App\Post\Application\UseCase\DeletePost;
|
||||||
use App\Post\Application\UseCase\UpdatePost;
|
use App\Post\Application\UseCase\UpdatePost;
|
||||||
@@ -25,7 +24,6 @@ use Netig\Netslim\Media\Contracts\MediaUsageReaderInterface;
|
|||||||
use Netig\Netslim\Taxonomy\Contracts\TaxonUsageCheckerInterface;
|
use Netig\Netslim\Taxonomy\Contracts\TaxonUsageCheckerInterface;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
PostServiceInterface::class => autowire(PostApplicationService::class),
|
|
||||||
PostRepositoryInterface::class => autowire(PdoPostRepository::class),
|
PostRepositoryInterface::class => autowire(PdoPostRepository::class),
|
||||||
PostMediaUsageRepositoryInterface::class => autowire(PdoPostMediaUsageRepository::class),
|
PostMediaUsageRepositoryInterface::class => autowire(PdoPostMediaUsageRepository::class),
|
||||||
PostMediaReferenceExtractorInterface::class => autowire(HtmlPostMediaReferenceExtractor::class),
|
PostMediaReferenceExtractorInterface::class => autowire(HtmlPostMediaReferenceExtractor::class),
|
||||||
@@ -35,7 +33,7 @@ return [
|
|||||||
UpdatePost::class => autowire(),
|
UpdatePost::class => autowire(),
|
||||||
DeletePost::class => autowire(),
|
DeletePost::class => autowire(),
|
||||||
MediaUsageReaderInterface::class => autowire(PostMediaUsageReader::class),
|
MediaUsageReaderInterface::class => autowire(PostMediaUsageReader::class),
|
||||||
RssController::class => factory(function (PostServiceInterface $postService): RssController {
|
RssController::class => factory(function (PostApplicationService $postService): RssController {
|
||||||
return new RssController(
|
return new RssController(
|
||||||
$postService,
|
$postService,
|
||||||
rtrim($_ENV['APP_URL'] ?? 'http://localhost', '/'),
|
rtrim($_ENV['APP_URL'] ?? 'http://localhost', '/'),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Post\UI\Http;
|
namespace App\Post\UI\Http;
|
||||||
|
|
||||||
use App\Post\Application\PostServiceInterface;
|
use App\Post\Application\PostApplicationService;
|
||||||
use App\Post\Domain\Entity\Post;
|
use App\Post\Domain\Entity\Post;
|
||||||
use App\Post\UI\Http\Request\PostFormRequest;
|
use App\Post\UI\Http\Request\PostFormRequest;
|
||||||
use Netig\Netslim\AuditLog\Contracts\AuditLoggerInterface;
|
use Netig\Netslim\AuditLog\Contracts\AuditLoggerInterface;
|
||||||
@@ -29,7 +29,7 @@ final class PostController
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Twig $view,
|
private readonly Twig $view,
|
||||||
private readonly PostServiceInterface $postService,
|
private readonly PostApplicationService $postService,
|
||||||
private readonly TaxonomyReaderInterface $taxonomyReader,
|
private readonly TaxonomyReaderInterface $taxonomyReader,
|
||||||
private readonly SettingsReaderInterface $settings,
|
private readonly SettingsReaderInterface $settings,
|
||||||
private readonly AuthorizationServiceInterface $authorization,
|
private readonly AuthorizationServiceInterface $authorization,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Post\UI\Http;
|
namespace App\Post\UI\Http;
|
||||||
|
|
||||||
use App\Post\Application\PostServiceInterface;
|
use App\Post\Application\PostApplicationService;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class RssController
|
|||||||
private const FEED_LIMIT = 20;
|
private const FEED_LIMIT = 20;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PostServiceInterface $postService,
|
private readonly PostApplicationService $postService,
|
||||||
private readonly string $appUrl,
|
private readonly string $appUrl,
|
||||||
private readonly string $appName,
|
private readonly string $appName,
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Tests\Post;
|
namespace Tests\Post;
|
||||||
|
|
||||||
use App\Post\Application\PostServiceInterface;
|
use App\Post\Application\PostApplicationService;
|
||||||
use App\Post\Domain\Entity\Post;
|
use App\Post\Domain\Entity\Post;
|
||||||
use App\Post\UI\Http\PostController;
|
use App\Post\UI\Http\PostController;
|
||||||
use Netig\Netslim\AuditLog\Contracts\AuditLoggerInterface;
|
use Netig\Netslim\AuditLog\Contracts\AuditLoggerInterface;
|
||||||
@@ -39,8 +39,8 @@ final class PostControllerTest extends ControllerTestBase
|
|||||||
/** @var \Slim\Views\Twig&MockObject */
|
/** @var \Slim\Views\Twig&MockObject */
|
||||||
private \Slim\Views\Twig $view;
|
private \Slim\Views\Twig $view;
|
||||||
|
|
||||||
/** @var PostServiceInterface&MockObject */
|
/** @var PostApplicationService&MockObject */
|
||||||
private PostServiceInterface $postService;
|
private PostApplicationService $postService;
|
||||||
|
|
||||||
/** @var TaxonomyReaderInterface&MockObject */
|
/** @var TaxonomyReaderInterface&MockObject */
|
||||||
private TaxonomyReaderInterface $taxonomyReader;
|
private TaxonomyReaderInterface $taxonomyReader;
|
||||||
@@ -65,7 +65,7 @@ final class PostControllerTest extends ControllerTestBase
|
|||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->view = $this->makeTwigMock();
|
$this->view = $this->makeTwigMock();
|
||||||
$this->postService = $this->createMock(PostServiceInterface::class);
|
$this->postService = $this->createMock(PostApplicationService::class);
|
||||||
$this->taxonomyReader = $this->createMock(TaxonomyReaderInterface::class);
|
$this->taxonomyReader = $this->createMock(TaxonomyReaderInterface::class);
|
||||||
$this->settings = $this->createMock(SettingsReaderInterface::class);
|
$this->settings = $this->createMock(SettingsReaderInterface::class);
|
||||||
$this->authorization = $this->createMock(AuthorizationServiceInterface::class);
|
$this->authorization = $this->createMock(AuthorizationServiceInterface::class);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Tests\Post;
|
namespace Tests\Post;
|
||||||
|
|
||||||
use App\Post\Application\PostServiceInterface;
|
use App\Post\Application\PostApplicationService;
|
||||||
use App\Post\Domain\Entity\Post;
|
use App\Post\Domain\Entity\Post;
|
||||||
use App\Post\UI\Http\RssController;
|
use App\Post\UI\Http\RssController;
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
@@ -23,8 +23,8 @@ use Tests\ControllerTestBase;
|
|||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
final class RssControllerTest extends ControllerTestBase
|
final class RssControllerTest extends ControllerTestBase
|
||||||
{
|
{
|
||||||
/** @var PostServiceInterface&MockObject */
|
/** @var PostApplicationService&MockObject */
|
||||||
private PostServiceInterface $postService;
|
private PostApplicationService $postService;
|
||||||
|
|
||||||
private RssController $controller;
|
private RssController $controller;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ final class RssControllerTest extends ControllerTestBase
|
|||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->postService = $this->createMock(PostServiceInterface::class);
|
$this->postService = $this->createMock(PostApplicationService::class);
|
||||||
|
|
||||||
$this->controller = new RssController(
|
$this->controller = new RssController(
|
||||||
$this->postService,
|
$this->postService,
|
||||||
|
|||||||
Reference in New Issue
Block a user