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) {
|
||||
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);
|
||||
|
||||
@@ -6,9 +6,9 @@ services:
|
||||
dockerfile: docker/php/Dockerfile
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# Répertoire de travail de l'entrypoint : reçoit public/ compilé,
|
||||
# puis partagé avec Nginx via le mount ci-dessous.
|
||||
- ./data:/data
|
||||
# Répertoire public partagé : reçoit les assets versionnés copiés par
|
||||
# l'entrypoint, puis sert de racine statique à Nginx.
|
||||
- ./data/public:/data/public
|
||||
|
||||
# Base SQLite et migrations : persistés entre redéploiements.
|
||||
- ./data/database:/var/www/app/database
|
||||
@@ -33,8 +33,8 @@ services:
|
||||
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-*}
|
||||
|
||||
# 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
|
||||
# (sync public/, permissions, caches) avant que les échecs ne comptent.
|
||||
# supplémentaire. start_period laisse le temps à entrypoint.sh de préparer
|
||||
# les répertoires runtime et de synchroniser les assets publics.
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/localhost/9000'"]
|
||||
interval: 10s
|
||||
@@ -63,7 +63,7 @@ services:
|
||||
# Même environnement/fichiers que le runtime PHP pour provisionner exactement
|
||||
# la même base SQLite persistée.
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./data/public:/data/public
|
||||
- ./data/database:/var/www/app/database
|
||||
- ./data/var:/var/www/app/var
|
||||
- ./data/public/media:/var/www/app/public/media
|
||||
|
||||
@@ -2,6 +2,8 @@ server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 8M;
|
||||
|
||||
root /var/www/app/public;
|
||||
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
|
||||
|
||||
# Prépare les répertoires runtime/persistants.
|
||||
mkdir -p \
|
||||
/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
|
||||
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
|
||||
|
||||
# Synchronise les fichiers publics versionnés (index.php, assets compilés, etc.)
|
||||
# vers le volume partagé. Le répertoire media est exclu car il contient les
|
||||
# uploads utilisateurs et est géré séparément.
|
||||
mkdir -p /data/public/media
|
||||
# Synchronise uniquement les fichiers publics versionnés (index.php, assets
|
||||
# compilés, favicon, etc.) vers le répertoire partagé avec Nginx. Le dossier
|
||||
# media est préservé car il contient les uploads persistants.
|
||||
find /data/public -mindepth 1 -maxdepth 1 ! -name media -exec rm -rf {} +
|
||||
|
||||
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
|
||||
# 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.
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\Post\Domain\Repository\PostRepositoryInterface;
|
||||
use Netig\Netslim\Kernel\Pagination\Application\PaginatedResult;
|
||||
use Netig\Netslim\Kernel\Support\Exception\NotFoundException;
|
||||
|
||||
final class PostApplicationService implements PostServiceInterface
|
||||
class PostApplicationService
|
||||
{
|
||||
public function __construct(
|
||||
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);
|
||||
|
||||
use App\Post\Application\PostApplicationService;
|
||||
use App\Post\Application\PostServiceInterface;
|
||||
use App\Post\Application\UseCase\CreatePost;
|
||||
use App\Post\Application\UseCase\DeletePost;
|
||||
use App\Post\Application\UseCase\UpdatePost;
|
||||
@@ -25,7 +24,6 @@ use Netig\Netslim\Media\Contracts\MediaUsageReaderInterface;
|
||||
use Netig\Netslim\Taxonomy\Contracts\TaxonUsageCheckerInterface;
|
||||
|
||||
return [
|
||||
PostServiceInterface::class => autowire(PostApplicationService::class),
|
||||
PostRepositoryInterface::class => autowire(PdoPostRepository::class),
|
||||
PostMediaUsageRepositoryInterface::class => autowire(PdoPostMediaUsageRepository::class),
|
||||
PostMediaReferenceExtractorInterface::class => autowire(HtmlPostMediaReferenceExtractor::class),
|
||||
@@ -35,7 +33,7 @@ return [
|
||||
UpdatePost::class => autowire(),
|
||||
DeletePost::class => autowire(),
|
||||
MediaUsageReaderInterface::class => autowire(PostMediaUsageReader::class),
|
||||
RssController::class => factory(function (PostServiceInterface $postService): RssController {
|
||||
RssController::class => factory(function (PostApplicationService $postService): RssController {
|
||||
return new RssController(
|
||||
$postService,
|
||||
rtrim($_ENV['APP_URL'] ?? 'http://localhost', '/'),
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Post\UI\Http;
|
||||
|
||||
use App\Post\Application\PostServiceInterface;
|
||||
use App\Post\Application\PostApplicationService;
|
||||
use App\Post\Domain\Entity\Post;
|
||||
use App\Post\UI\Http\Request\PostFormRequest;
|
||||
use Netig\Netslim\AuditLog\Contracts\AuditLoggerInterface;
|
||||
@@ -29,7 +29,7 @@ final class PostController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Twig $view,
|
||||
private readonly PostServiceInterface $postService,
|
||||
private readonly PostApplicationService $postService,
|
||||
private readonly TaxonomyReaderInterface $taxonomyReader,
|
||||
private readonly SettingsReaderInterface $settings,
|
||||
private readonly AuthorizationServiceInterface $authorization,
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
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\ServerRequestInterface as Request;
|
||||
|
||||
@@ -16,7 +16,7 @@ class RssController
|
||||
private const FEED_LIMIT = 20;
|
||||
|
||||
public function __construct(
|
||||
private readonly PostServiceInterface $postService,
|
||||
private readonly PostApplicationService $postService,
|
||||
private readonly string $appUrl,
|
||||
private readonly string $appName,
|
||||
) {}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Post;
|
||||
|
||||
use App\Post\Application\PostServiceInterface;
|
||||
use App\Post\Application\PostApplicationService;
|
||||
use App\Post\Domain\Entity\Post;
|
||||
use App\Post\UI\Http\PostController;
|
||||
use Netig\Netslim\AuditLog\Contracts\AuditLoggerInterface;
|
||||
@@ -39,8 +39,8 @@ final class PostControllerTest extends ControllerTestBase
|
||||
/** @var \Slim\Views\Twig&MockObject */
|
||||
private \Slim\Views\Twig $view;
|
||||
|
||||
/** @var PostServiceInterface&MockObject */
|
||||
private PostServiceInterface $postService;
|
||||
/** @var PostApplicationService&MockObject */
|
||||
private PostApplicationService $postService;
|
||||
|
||||
/** @var TaxonomyReaderInterface&MockObject */
|
||||
private TaxonomyReaderInterface $taxonomyReader;
|
||||
@@ -65,7 +65,7 @@ final class PostControllerTest extends ControllerTestBase
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->view = $this->makeTwigMock();
|
||||
$this->postService = $this->createMock(PostServiceInterface::class);
|
||||
$this->postService = $this->createMock(PostApplicationService::class);
|
||||
$this->taxonomyReader = $this->createMock(TaxonomyReaderInterface::class);
|
||||
$this->settings = $this->createMock(SettingsReaderInterface::class);
|
||||
$this->authorization = $this->createMock(AuthorizationServiceInterface::class);
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Post;
|
||||
|
||||
use App\Post\Application\PostServiceInterface;
|
||||
use App\Post\Application\PostApplicationService;
|
||||
use App\Post\Domain\Entity\Post;
|
||||
use App\Post\UI\Http\RssController;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
@@ -23,8 +23,8 @@ use Tests\ControllerTestBase;
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
final class RssControllerTest extends ControllerTestBase
|
||||
{
|
||||
/** @var PostServiceInterface&MockObject */
|
||||
private PostServiceInterface $postService;
|
||||
/** @var PostApplicationService&MockObject */
|
||||
private PostApplicationService $postService;
|
||||
|
||||
private RssController $controller;
|
||||
|
||||
@@ -33,7 +33,7 @@ final class RssControllerTest extends ControllerTestBase
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->postService = $this->createMock(PostServiceInterface::class);
|
||||
$this->postService = $this->createMock(PostApplicationService::class);
|
||||
|
||||
$this->controller = new RssController(
|
||||
$this->postService,
|
||||
|
||||
Reference in New Issue
Block a user