Initial commit
This commit is contained in:
commit
2b89c4acd9
15 changed files with 3180 additions and 0 deletions
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(curl:*)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
817
IMPLEMENTATION_GUIDE.md
Normal file
817
IMPLEMENTATION_GUIDE.md
Normal file
|
|
@ -0,0 +1,817 @@
|
||||||
|
# Guide d'implémentation - Service Web2Print
|
||||||
|
|
||||||
|
Guide étape par étape pour déployer le service de conversion HTML vers PDF sur le VPS Infomaniak.
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
- Accès SSH au VPS
|
||||||
|
- Accès à l'interface Infomaniak
|
||||||
|
- Domaine `web2print.studio-variable.com` pointant vers le VPS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 : Préparation du VPS
|
||||||
|
|
||||||
|
### 1.1 Connexion et vérification
|
||||||
|
```bash
|
||||||
|
# Vérifier Node.js et npm
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
# Vérifier Apache et PHP
|
||||||
|
apache2 -v
|
||||||
|
php -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Installation de Node.js (si nécessaire)
|
||||||
|
```bash
|
||||||
|
# Si Node.js n'est pas installé ou version < 14
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install curl -y
|
||||||
|
|
||||||
|
# Installer Node.js via NodeSource (version LTS recommandée)
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
||||||
|
sudo apt install -y nodejs
|
||||||
|
|
||||||
|
# Vérifier l'installation
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Installation de Paged.js CLI
|
||||||
|
```bash
|
||||||
|
# Installer pagedjs-cli globalement
|
||||||
|
sudo npm install -g pagedjs-cli
|
||||||
|
|
||||||
|
# Vérifier l'installation (pagedjs-cli n'a pas d'option --version)
|
||||||
|
npm list -g pagedjs-cli
|
||||||
|
which pagedjs-cli
|
||||||
|
|
||||||
|
# Vérifier le chemin d'installation (noter pour la config PHP)
|
||||||
|
which pagedjs-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Tester Paged.js CLI
|
||||||
|
```bash
|
||||||
|
# Créer un fichier HTML de test
|
||||||
|
cat > /tmp/test.html << 'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
@page { size: A4; margin: 2cm; }
|
||||||
|
body { font-family: Arial, sans-serif; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Test Paged.js CLI</h1>
|
||||||
|
<p>Ceci est un test de génération PDF avec Paged.js.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Générer un PDF de test
|
||||||
|
pagedjs-cli /tmp/test.html -o /tmp/test.pdf
|
||||||
|
|
||||||
|
# Vérifier que le PDF est créé
|
||||||
|
ls -lh /tmp/test.pdf
|
||||||
|
file /tmp/test.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 : Structure du projet
|
||||||
|
|
||||||
|
### 2.1 Créer la structure des dossiers
|
||||||
|
```bash
|
||||||
|
# Créer le dossier du projet
|
||||||
|
sudo mkdir -p /var/www/web2print
|
||||||
|
cd /var/www/web2print
|
||||||
|
|
||||||
|
# Créer la structure
|
||||||
|
sudo mkdir -p {public,config,logs,tmp}
|
||||||
|
sudo mkdir -p src/{Controllers,Services,Middleware}
|
||||||
|
|
||||||
|
# Structure finale :
|
||||||
|
# /var/www/web2print/
|
||||||
|
# ├── public/
|
||||||
|
# │ └── index.php
|
||||||
|
# ├── src/
|
||||||
|
# │ ├── Controllers/
|
||||||
|
# │ ├── Services/
|
||||||
|
# │ └── Middleware/
|
||||||
|
# ├── config/
|
||||||
|
# │ └── config.php
|
||||||
|
# ├── logs/
|
||||||
|
# └── tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Définir les permissions
|
||||||
|
```bash
|
||||||
|
# Propriétaire : www-data (utilisateur Apache)
|
||||||
|
sudo chown -R www-data:www-data /var/www/web2print
|
||||||
|
|
||||||
|
# Permissions
|
||||||
|
sudo chmod -R 755 /var/www/web2print
|
||||||
|
sudo chmod -R 775 /var/www/web2print/logs
|
||||||
|
sudo chmod -R 775 /var/www/web2print/tmp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 : Configuration Apache
|
||||||
|
|
||||||
|
### 3.1 Configurer le VirtualHost
|
||||||
|
```bash
|
||||||
|
# Créer le fichier de configuration
|
||||||
|
sudo nano /etc/apache2/sites-available/web2print.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu du fichier `web2print.conf` :
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName web2print.studio-variable.com
|
||||||
|
DocumentRoot /var/www/web2print/public
|
||||||
|
|
||||||
|
<Directory /var/www/web2print/public>
|
||||||
|
Options -Indexes +FollowSymLinks
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
ErrorLog /var/www/web2print/logs/apache-error.log
|
||||||
|
CustomLog /var/www/web2print/logs/apache-access.log combined
|
||||||
|
|
||||||
|
# Sécurité
|
||||||
|
<FilesMatch "\.(env|config\.php)$">
|
||||||
|
Require all denied
|
||||||
|
</FilesMatch>
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Activer le site et modules nécessaires
|
||||||
|
```bash
|
||||||
|
# Activer le module rewrite
|
||||||
|
sudo a2enmod rewrite
|
||||||
|
|
||||||
|
# Activer le site
|
||||||
|
sudo a2ensite web2print.conf
|
||||||
|
|
||||||
|
# Vérifier la configuration
|
||||||
|
sudo apache2ctl configtest
|
||||||
|
|
||||||
|
# Redémarrer Apache
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Configuration SSL avec Let's Encrypt (recommandé)
|
||||||
|
```bash
|
||||||
|
# Installer Certbot
|
||||||
|
sudo apt install certbot python3-certbot-apache -y
|
||||||
|
|
||||||
|
# Obtenir et installer le certificat
|
||||||
|
sudo certbot --apache -d web2print.studio-variable.com
|
||||||
|
|
||||||
|
# Le renouvellement automatique est configuré par défaut
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 : Configuration DNS (Interface Infomaniak)
|
||||||
|
|
||||||
|
### 4.1 Dans l'interface Infomaniak
|
||||||
|
1. Aller dans la gestion des domaines
|
||||||
|
2. Sélectionner le domaine `studio-variable.com`
|
||||||
|
3. Accéder aux zones DNS
|
||||||
|
4. Ajouter un enregistrement A :
|
||||||
|
- **Nom** : `web2print`
|
||||||
|
- **Type** : `A`
|
||||||
|
- **Valeur** : `[IP du VPS]`
|
||||||
|
- **TTL** : `3600`
|
||||||
|
|
||||||
|
### 4.2 Vérifier la propagation
|
||||||
|
```bash
|
||||||
|
# Attendre quelques minutes puis tester
|
||||||
|
dig web2print.studio-variable.com
|
||||||
|
curl -I http://web2print.studio-variable.com
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 : Implémentation du code PHP
|
||||||
|
|
||||||
|
### 5.1 Fichier de configuration
|
||||||
|
```bash
|
||||||
|
sudo nano /var/www/web2print/config/config.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
// Clés API autorisées
|
||||||
|
'api_keys' => [
|
||||||
|
'votre-cle-api-secrete-generee-aleatoirement',
|
||||||
|
// Ajouter d'autres clés si nécessaire
|
||||||
|
],
|
||||||
|
|
||||||
|
// Chemins
|
||||||
|
'tmp_dir' => '/var/www/web2print/tmp',
|
||||||
|
'log_file' => '/var/www/web2print/logs/app.log',
|
||||||
|
|
||||||
|
// Paged.js CLI
|
||||||
|
'pagedjs_bin' => '/usr/local/bin/pagedjs-cli', // Vérifier avec: which pagedjs-cli
|
||||||
|
'pagedjs_timeout' => 60, // secondes
|
||||||
|
|
||||||
|
// Limites
|
||||||
|
'max_html_size' => 5 * 1024 * 1024, // 5 MB
|
||||||
|
'max_execution_time' => 90,
|
||||||
|
|
||||||
|
// Options PDF par défaut
|
||||||
|
'pdf_defaults' => [
|
||||||
|
'format' => 'A4',
|
||||||
|
'margin' => '2cm',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Middleware d'authentification
|
||||||
|
```bash
|
||||||
|
sudo nano /var/www/web2print/src/Middleware/AuthMiddleware.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Web2Print\Middleware;
|
||||||
|
|
||||||
|
class AuthMiddleware
|
||||||
|
{
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
public function __construct(array $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authenticate(): bool
|
||||||
|
{
|
||||||
|
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||||
|
|
||||||
|
if (empty($apiKey)) {
|
||||||
|
$this->sendError(401, 'API key missing');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($apiKey, $this->config['api_keys'], true)) {
|
||||||
|
$this->sendError(403, 'Invalid API key');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendError(int $code, string $message): void
|
||||||
|
{
|
||||||
|
http_response_code($code);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['error' => $message]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Service de génération PDF
|
||||||
|
```bash
|
||||||
|
sudo nano /var/www/web2print/src/Services/PdfGenerator.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Web2Print\Services;
|
||||||
|
|
||||||
|
class PdfGenerator
|
||||||
|
{
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
public function __construct(array $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(string $html, ?string $css = null, array $options = []): string
|
||||||
|
{
|
||||||
|
// Créer un fichier HTML temporaire
|
||||||
|
$htmlFile = $this->createTempFile($html, $css);
|
||||||
|
$pdfFile = tempnam($this->config['tmp_dir'], 'pdf_') . '.pdf';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Construire la commande Paged.js CLI
|
||||||
|
$command = $this->buildCommand($htmlFile, $pdfFile, $options);
|
||||||
|
|
||||||
|
// Exécuter Paged.js CLI
|
||||||
|
$output = [];
|
||||||
|
$returnCode = 0;
|
||||||
|
exec($command . ' 2>&1', $output, $returnCode);
|
||||||
|
|
||||||
|
if ($returnCode !== 0) {
|
||||||
|
$this->log('Paged.js CLI error: ' . implode("\n", $output));
|
||||||
|
throw new \Exception('PDF generation failed: ' . implode("\n", $output));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lire le PDF généré
|
||||||
|
if (!file_exists($pdfFile)) {
|
||||||
|
throw new \Exception('PDF file not created');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdfContent = file_get_contents($pdfFile);
|
||||||
|
|
||||||
|
return $pdfContent;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// Nettoyer les fichiers temporaires
|
||||||
|
@unlink($htmlFile);
|
||||||
|
@unlink($pdfFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createTempFile(string $html, ?string $css): string
|
||||||
|
{
|
||||||
|
$fullHtml = $html;
|
||||||
|
|
||||||
|
// Injecter le CSS dans le HTML si fourni
|
||||||
|
if ($css) {
|
||||||
|
$styleTag = "<style>{$css}</style>";
|
||||||
|
if (stripos($html, '</head>') !== false) {
|
||||||
|
$fullHtml = str_ireplace('</head>', $styleTag . '</head>', $html);
|
||||||
|
} else {
|
||||||
|
// Si pas de <head>, créer une structure HTML complète
|
||||||
|
$fullHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'>{$styleTag}</head><body>{$html}</body></html>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// S'assurer que le HTML a une structure complète
|
||||||
|
if (stripos($fullHtml, '<!DOCTYPE') === false) {
|
||||||
|
$fullHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>{$fullHtml}</body></html>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$tempFile = tempnam($this->config['tmp_dir'], 'html_') . '.html';
|
||||||
|
file_put_contents($tempFile, $fullHtml);
|
||||||
|
|
||||||
|
return $tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCommand(string $htmlFile, string $pdfFile, array $options): string
|
||||||
|
{
|
||||||
|
$cmd = escapeshellcmd($this->config['pagedjs_bin']);
|
||||||
|
$cmd .= ' ' . escapeshellarg($htmlFile);
|
||||||
|
$cmd .= ' -o ' . escapeshellarg($pdfFile);
|
||||||
|
|
||||||
|
// Options supplémentaires pour Paged.js CLI
|
||||||
|
// Note: Paged.js utilise les règles CSS @page pour le format
|
||||||
|
// Les options sont limitées dans la CLI
|
||||||
|
|
||||||
|
// Timeout (millisecondes)
|
||||||
|
if (!empty($options['timeout'])) {
|
||||||
|
$cmd .= ' --timeout ' . (int)$options['timeout'];
|
||||||
|
} else {
|
||||||
|
$cmd .= ' --timeout ' . ($this->config['pagedjs_timeout'] * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function log(string $message): void
|
||||||
|
{
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$logMessage = "[{$timestamp}] {$message}\n";
|
||||||
|
file_put_contents($this->config['log_file'], $logMessage, FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Contrôleur principal
|
||||||
|
```bash
|
||||||
|
sudo nano /var/www/web2print/src/Controllers/GenerateController.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Web2Print\Controllers;
|
||||||
|
|
||||||
|
use Web2Print\Services\PdfGenerator;
|
||||||
|
|
||||||
|
class GenerateController
|
||||||
|
{
|
||||||
|
private PdfGenerator $generator;
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
public function __construct(PdfGenerator $generator, array $config)
|
||||||
|
{
|
||||||
|
$this->generator = $generator;
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// Vérifier la méthode HTTP
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
$this->sendError(405, 'Method not allowed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lire le body JSON
|
||||||
|
$input = file_get_contents('php://input');
|
||||||
|
|
||||||
|
if (strlen($input) > $this->config['max_html_size']) {
|
||||||
|
$this->sendError(413, 'Request too large');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($input, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
$this->sendError(400, 'Invalid JSON');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valider les données
|
||||||
|
if (empty($data['html'])) {
|
||||||
|
$this->sendError(400, 'HTML content required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Générer le PDF
|
||||||
|
$pdf = $this->generator->generate(
|
||||||
|
$data['html'],
|
||||||
|
$data['css'] ?? null,
|
||||||
|
$data['options'] ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retourner le PDF
|
||||||
|
header('Content-Type: application/pdf');
|
||||||
|
header('Content-Length: ' . strlen($pdf));
|
||||||
|
echo $pdf;
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->sendError(500, 'PDF generation error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendError(int $code, string $message): void
|
||||||
|
{
|
||||||
|
http_response_code($code);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['error' => $message]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.5 Point d'entrée (index.php)
|
||||||
|
```bash
|
||||||
|
sudo nano /var/www/web2print/public/index.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Autoloader simple
|
||||||
|
spl_autoload_register(function ($class) {
|
||||||
|
$prefix = 'Web2Print\\';
|
||||||
|
$baseDir = __DIR__ . '/../src/';
|
||||||
|
|
||||||
|
$len = strlen($prefix);
|
||||||
|
if (strncmp($prefix, $class, $len) !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativeClass = substr($class, $len);
|
||||||
|
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||||
|
|
||||||
|
if (file_exists($file)) {
|
||||||
|
require $file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Charger la configuration
|
||||||
|
$config = require __DIR__ . '/../config/config.php';
|
||||||
|
|
||||||
|
// Définir le temps d'exécution max
|
||||||
|
set_time_limit($config['max_execution_time']);
|
||||||
|
|
||||||
|
// Headers CORS (si nécessaire)
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||||
|
|
||||||
|
// Gérer preflight OPTIONS
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentification
|
||||||
|
$auth = new \Web2Print\Middleware\AuthMiddleware($config);
|
||||||
|
if (!$auth->authenticate()) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router simple
|
||||||
|
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||||
|
|
||||||
|
if ($uri === '/generate' || $uri === '/') {
|
||||||
|
$generator = new \Web2Print\Services\PdfGenerator($config);
|
||||||
|
$controller = new \Web2Print\Controllers\GenerateController($generator, $config);
|
||||||
|
$controller->handle();
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['error' => 'Not found']);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.6 Fichier .htaccess
|
||||||
|
```bash
|
||||||
|
sudo nano /var/www/web2print/public/.htaccess
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
```apache
|
||||||
|
# Activer le moteur de réécriture
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Rediriger toutes les requêtes vers index.php
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||||
|
|
||||||
|
# Sécurité
|
||||||
|
<Files ".env">
|
||||||
|
Require all denied
|
||||||
|
</Files>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6 : Génération de la clé API
|
||||||
|
|
||||||
|
### 6.1 Générer une clé sécurisée
|
||||||
|
```bash
|
||||||
|
# Sur le VPS
|
||||||
|
openssl rand -hex 32
|
||||||
|
# Exemple de sortie : a7f8e9d4c2b1a3f5e7d9c4b2a1f8e6d5c3b9a7f4e2d8c6b4a2f9e7d5c3b1a8f6
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Ajouter la clé dans la configuration
|
||||||
|
```bash
|
||||||
|
sudo nano /var/www/web2print/config/config.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Remplacer `'votre-cle-api-secrete-generee-aleatoirement'` par la clé générée.
|
||||||
|
|
||||||
|
### 6.3 Sécuriser le fichier de configuration
|
||||||
|
```bash
|
||||||
|
sudo chmod 640 /var/www/web2print/config/config.php
|
||||||
|
sudo chown www-data:www-data /var/www/web2print/config/config.php
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7 : Tests
|
||||||
|
|
||||||
|
### 7.1 Vérifier le chemin de Paged.js CLI
|
||||||
|
```bash
|
||||||
|
which pagedjs-cli
|
||||||
|
# Mettre à jour 'pagedjs_bin' dans config.php si nécessaire
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Test avec curl - HTML simple
|
||||||
|
```bash
|
||||||
|
# Créer un fichier de test avec CSS Paged Media
|
||||||
|
cat > /tmp/test-request.json << 'EOF'
|
||||||
|
{
|
||||||
|
"html": "<!DOCTYPE html><html><head><meta charset='utf-8'><style>@page { size: A4; margin: 2cm; } body { font-family: Arial; } h1 { color: #333; }</style></head><body><h1>Test PDF Paged.js</h1><p>Ceci est un test de génération PDF avec Paged.js CLI.</p></body></html>"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Tester l'API (remplacer YOUR_API_KEY)
|
||||||
|
curl -X POST https://web2print.studio-variable.com/generate \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
-d @/tmp/test-request.json \
|
||||||
|
--output /tmp/result.pdf
|
||||||
|
|
||||||
|
# Vérifier le PDF généré
|
||||||
|
file /tmp/result.pdf
|
||||||
|
ls -lh /tmp/result.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2b Test avec CSS séparé
|
||||||
|
```bash
|
||||||
|
# Test avec HTML et CSS séparés
|
||||||
|
cat > /tmp/test-request-2.json << 'EOF'
|
||||||
|
{
|
||||||
|
"html": "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body><h1>Test avec CSS séparé</h1><p>Le CSS est injecté séparément.</p></body></html>",
|
||||||
|
"css": "@page { size: A4; margin: 2cm; } body { font-family: Georgia, serif; color: #444; } h1 { color: #d63031; border-bottom: 2px solid #d63031; padding-bottom: 10px; }"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
curl -X POST https://web2print.studio-variable.com/generate \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
-d @/tmp/test-request-2.json \
|
||||||
|
--output /tmp/result-2.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Test sans clé API (doit échouer)
|
||||||
|
```bash
|
||||||
|
curl -X POST https://web2print.studio-variable.com/generate \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"html": "<h1>Test</h1>"}'
|
||||||
|
# Doit retourner : {"error":"API key missing"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 Test avec mauvaise clé (doit échouer)
|
||||||
|
```bash
|
||||||
|
curl -X POST https://web2print.studio-variable.com/generate \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: wrong-key" \
|
||||||
|
-d '{"html": "<h1>Test</h1>"}'
|
||||||
|
# Doit retourner : {"error":"Invalid API key"}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8 : Monitoring et maintenance
|
||||||
|
|
||||||
|
### 8.1 Rotation des logs
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/logrotate.d/web2print
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
```
|
||||||
|
/var/www/web2print/logs/*.log {
|
||||||
|
daily
|
||||||
|
rotate 14
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
notifempty
|
||||||
|
create 0640 www-data www-data
|
||||||
|
sharedscripts
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Nettoyage des fichiers temporaires
|
||||||
|
```bash
|
||||||
|
# Créer un script de nettoyage
|
||||||
|
sudo nano /var/www/web2print/cleanup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Contenu :
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Nettoyer les fichiers temporaires de plus de 1 heure
|
||||||
|
find /var/www/web2print/tmp -type f -mmin +60 -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rendre exécutable
|
||||||
|
sudo chmod +x /var/www/web2print/cleanup.sh
|
||||||
|
|
||||||
|
# Ajouter au cron (toutes les heures)
|
||||||
|
sudo crontab -e
|
||||||
|
# Ajouter : 0 * * * * /var/www/web2print/cleanup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Surveiller les logs
|
||||||
|
```bash
|
||||||
|
# Logs Apache
|
||||||
|
sudo tail -f /var/www/web2print/logs/apache-error.log
|
||||||
|
|
||||||
|
# Logs applicatifs
|
||||||
|
sudo tail -f /var/www/web2print/logs/app.log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 9 : Checklist de déploiement
|
||||||
|
|
||||||
|
- [ ] Node.js et npm installés
|
||||||
|
- [ ] Paged.js CLI installé et fonctionnel
|
||||||
|
- [ ] Structure de dossiers créée avec bonnes permissions
|
||||||
|
- [ ] VirtualHost Apache configuré
|
||||||
|
- [ ] SSL/TLS configuré (Let's Encrypt)
|
||||||
|
- [ ] DNS configuré dans Infomaniak
|
||||||
|
- [ ] Tous les fichiers PHP créés
|
||||||
|
- [ ] Chemin `pagedjs_bin` vérifié dans config.php
|
||||||
|
- [ ] Clé API générée et configurée
|
||||||
|
- [ ] Tests d'API réussis (HTML simple + CSS séparé)
|
||||||
|
- [ ] Rotation des logs configurée
|
||||||
|
- [ ] Nettoyage automatique des fichiers temporaires configuré
|
||||||
|
- [ ] Documentation de la clé API sauvegardée en lieu sûr
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 10 : Optimisations futures (optionnel)
|
||||||
|
|
||||||
|
### 10.1 Rate limiting
|
||||||
|
Ajouter un système de limitation de requêtes par clé API pour éviter les abus.
|
||||||
|
|
||||||
|
### 10.2 Cache
|
||||||
|
Mettre en cache les PDFs générés si le même HTML est demandé plusieurs fois.
|
||||||
|
|
||||||
|
### 10.3 File d'attente
|
||||||
|
Pour des volumes plus importants, implémenter une file d'attente avec des workers.
|
||||||
|
|
||||||
|
### 10.4 Monitoring avancé
|
||||||
|
- Intégrer un système de monitoring (Prometheus, Grafana)
|
||||||
|
- Alertes sur échecs de génération
|
||||||
|
- Métriques de performance
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ressources et documentation
|
||||||
|
|
||||||
|
- **Paged.js** : https://pagedjs.org/
|
||||||
|
- **Paged.js CLI** : https://gitlab.coko.foundation/pagedjs/pagedjs-cli
|
||||||
|
- **CSS Paged Media** : https://www.w3.org/TR/css-page-3/
|
||||||
|
- **PHP exec()** : https://www.php.net/manual/fr/function.exec.php
|
||||||
|
- **Apache VirtualHost** : https://httpd.apache.org/docs/2.4/vhosts/
|
||||||
|
- **Let's Encrypt** : https://letsencrypt.org/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support et troubleshooting
|
||||||
|
|
||||||
|
### Erreur : "pagedjs-cli: command not found"
|
||||||
|
```bash
|
||||||
|
# Vérifier l'installation
|
||||||
|
which pagedjs-cli
|
||||||
|
npm list -g pagedjs-cli
|
||||||
|
|
||||||
|
# Réinstaller si nécessaire
|
||||||
|
sudo npm install -g pagedjs-cli --force
|
||||||
|
|
||||||
|
# Si problème de PATH, vérifier où npm installe les binaires
|
||||||
|
npm config get prefix
|
||||||
|
# Les binaires sont dans: <prefix>/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreur : "Permission denied" lors de la génération
|
||||||
|
```bash
|
||||||
|
# Vérifier les permissions
|
||||||
|
sudo chown -R www-data:www-data /var/www/web2print
|
||||||
|
sudo chmod -R 775 /var/www/web2print/tmp
|
||||||
|
sudo chmod -R 775 /var/www/web2print/logs
|
||||||
|
```
|
||||||
|
|
||||||
|
### PDF vide ou erreur de génération
|
||||||
|
- Vérifier les logs : `/var/www/web2print/logs/app.log`
|
||||||
|
- Tester Paged.js CLI manuellement :
|
||||||
|
```bash
|
||||||
|
echo '<html><body>Test</body></html>' > /tmp/test.html
|
||||||
|
pagedjs-cli /tmp/test.html -o /tmp/test.pdf
|
||||||
|
```
|
||||||
|
- Vérifier que Node.js est à jour (minimum v14)
|
||||||
|
- S'assurer que le HTML contient `<!DOCTYPE html>` et une structure complète
|
||||||
|
|
||||||
|
### Erreur : "Timeout" lors de génération de gros documents
|
||||||
|
- Augmenter `pagedjs_timeout` dans `config.php`
|
||||||
|
- Augmenter `max_execution_time` dans `config.php`
|
||||||
|
- Augmenter le timeout Apache si nécessaire :
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/apache2/apache2.conf
|
||||||
|
# Ajouter/modifier : Timeout 300
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Les styles CSS ne sont pas appliqués
|
||||||
|
- Vérifier que le CSS contient les règles `@page` pour Paged.js
|
||||||
|
- S'assurer que les ressources externes (fonts, images) sont accessibles
|
||||||
|
- Tester le HTML directement dans un navigateur avec Paged.js
|
||||||
|
- Exemple de CSS Paged Media basique :
|
||||||
|
```css
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 2cm;
|
||||||
|
}
|
||||||
|
@page :first {
|
||||||
|
margin-top: 3cm;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Différences de rendu entre front et back
|
||||||
|
- Utiliser exactement la même version de Paged.js (front) et Paged.js CLI (back)
|
||||||
|
- Vérifier la version installée : `npm list -g pagedjs-cli`
|
||||||
|
- Les fonts doivent être identiques (utiliser web fonts ou fonts système communes)
|
||||||
140
deploy/DEPLOY.md
Normal file
140
deploy/DEPLOY.md
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Déploiement du service Web2Print
|
||||||
|
|
||||||
|
## 1. Transférer les fichiers sur le serveur
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Depuis votre machine locale, transférer le dossier deploy vers le serveur
|
||||||
|
scp -r -i ~/.ssh/id_ed25519 deploy/* debian@83.228.210.201:/tmp/web2print-deploy/
|
||||||
|
|
||||||
|
# OU utiliser rsync (plus rapide)
|
||||||
|
rsync -avz -e "ssh -i ~/.ssh/id_ed25519" deploy/ debian@83.228.210.201:/tmp/web2print-deploy/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Sur le serveur, copier les fichiers vers /var/www/web2print
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Se connecter au serveur
|
||||||
|
ssh -i ~/.ssh/id_ed25519 debian@83.228.210.201
|
||||||
|
|
||||||
|
# Copier les fichiers
|
||||||
|
sudo cp -r /tmp/web2print-deploy/config /var/www/web2print/
|
||||||
|
sudo cp -r /tmp/web2print-deploy/src /var/www/web2print/
|
||||||
|
sudo cp -r /tmp/web2print-deploy/public /var/www/web2print/
|
||||||
|
|
||||||
|
# Définir les bonnes permissions
|
||||||
|
sudo chown -R www-data:www-data /var/www/web2print
|
||||||
|
sudo chmod -R 755 /var/www/web2print
|
||||||
|
sudo chmod -R 775 /var/www/web2print/logs
|
||||||
|
sudo chmod -R 775 /var/www/web2print/tmp
|
||||||
|
|
||||||
|
# Sécuriser le fichier de configuration
|
||||||
|
sudo chmod 640 /var/www/web2print/config/config.php
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Générer une clé API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Générer une clé sécurisée
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
|
# Copier la clé générée et éditer le fichier de configuration
|
||||||
|
sudo nano /var/www/web2print/config/config.php
|
||||||
|
|
||||||
|
# Remplacer 'REMPLACER_PAR_VOTRE_CLE_API_GENEREE' par la clé générée
|
||||||
|
# Sauvegarder (Ctrl+O) et quitter (Ctrl+X)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Vérifier le chemin de pagedjs-cli
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérifier le chemin
|
||||||
|
which pagedjs-cli
|
||||||
|
|
||||||
|
# Si différent de /usr/bin/pagedjs-cli, mettre à jour dans config.php
|
||||||
|
sudo nano /var/www/web2print/config/config.php
|
||||||
|
# Modifier la ligne 'pagedjs_bin' avec le bon chemin
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Configurer la variable d'environnement pour Puppeteer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Éditer le fichier envvars d'Apache
|
||||||
|
sudo nano /etc/apache2/envvars
|
||||||
|
|
||||||
|
# Ajouter à la fin :
|
||||||
|
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||||
|
|
||||||
|
# Sauvegarder et redémarrer Apache
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Tester le service
|
||||||
|
|
||||||
|
### Test depuis le serveur
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test simple (remplacer YOUR_API_KEY par votre clé)
|
||||||
|
cat > /tmp/test-request.json << 'EOF'
|
||||||
|
{
|
||||||
|
"html": "<!DOCTYPE html><html><head><meta charset='utf-8'><style>@page { size: A4; margin: 2cm; } body { font-family: Arial; } h1 { color: #333; }</style></head><body><h1>Test PDF Paged.js</h1><p>Ceci est un test.</p></body></html>"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
curl -X POST http://localhost/generate \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
-d @/tmp/test-request.json \
|
||||||
|
--output /tmp/result.pdf
|
||||||
|
|
||||||
|
# Vérifier le PDF
|
||||||
|
file /tmp/result.pdf
|
||||||
|
ls -lh /tmp/result.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test depuis votre machine locale
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test en ligne de commande (tout sur une seule ligne, remplacer YOUR_API_KEY par votre clé)
|
||||||
|
curl -X POST http://web2print.studio-variable.com/generate -H "Content-Type: application/json" -H "X-API-Key: 25377ab6e2153b159d1d5fa22501228810b6aec9d63346bd0614045dc167061c" -d '{"html":"<!DOCTYPE html><html><head><meta charset=\"utf-8\"><style>@page { size: A4; margin: 2cm; } body { font-family: Arial; } h1 { color: #333; }</style></head><body><h1>Test PDF</h1><p>Test depuis local</p></body></html>"}' --output test-result.pdf
|
||||||
|
|
||||||
|
# Vérifier le PDF reçu
|
||||||
|
file test-result.pdf
|
||||||
|
open test-result.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Nettoyer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Supprimer le dossier temporaire
|
||||||
|
rm -rf /tmp/web2print-deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Structure finale sur le serveur
|
||||||
|
|
||||||
|
```
|
||||||
|
/var/www/web2print/
|
||||||
|
├── config/
|
||||||
|
│ └── config.php
|
||||||
|
├── src/
|
||||||
|
│ ├── Controllers/
|
||||||
|
│ │ └── GenerateController.php
|
||||||
|
│ ├── Services/
|
||||||
|
│ │ └── PdfGenerator.php
|
||||||
|
│ └── Middleware/
|
||||||
|
│ └── AuthMiddleware.php
|
||||||
|
├── public/
|
||||||
|
│ ├── index.php
|
||||||
|
│ └── .htaccess
|
||||||
|
├── logs/
|
||||||
|
└── tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Si l'API ne fonctionne pas :
|
||||||
|
|
||||||
|
1. Vérifier les logs Apache : `sudo tail -f /var/www/web2print/logs/apache-error.log`
|
||||||
|
2. Vérifier les logs applicatifs : `sudo tail -f /var/www/web2print/logs/app.log`
|
||||||
|
3. Vérifier les permissions : `ls -la /var/www/web2print`
|
||||||
|
4. Tester pagedjs-cli manuellement avec la variable d'environnement
|
||||||
|
5. Vérifier que le VirtualHost Apache est bien configuré
|
||||||
27
deploy/config/config.php
Normal file
27
deploy/config/config.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
// Clés API autorisées
|
||||||
|
'api_keys' => [
|
||||||
|
'REMPLACER_PAR_VOTRE_CLE_API_GENEREE',
|
||||||
|
// Ajouter d'autres clés si nécessaire
|
||||||
|
],
|
||||||
|
|
||||||
|
// Chemins
|
||||||
|
'tmp_dir' => '/var/www/web2print/tmp',
|
||||||
|
'log_file' => '/var/www/web2print/logs/app.log',
|
||||||
|
|
||||||
|
// Paged.js CLI
|
||||||
|
'pagedjs_bin' => '/usr/bin/pagedjs-cli', // Vérifier avec: which pagedjs-cli
|
||||||
|
'pagedjs_timeout' => 60, // secondes
|
||||||
|
|
||||||
|
// Limites
|
||||||
|
'max_html_size' => 5 * 1024 * 1024, // 5 MB
|
||||||
|
'max_execution_time' => 90,
|
||||||
|
|
||||||
|
// Options PDF par défaut
|
||||||
|
'pdf_defaults' => [
|
||||||
|
'format' => 'A4',
|
||||||
|
'margin' => '2cm',
|
||||||
|
],
|
||||||
|
];
|
||||||
12
deploy/public/.htaccess
Normal file
12
deploy/public/.htaccess
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Activer le moteur de réécriture
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Rediriger toutes les requêtes vers index.php
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||||
|
|
||||||
|
# Sécurité
|
||||||
|
<Files ".env">
|
||||||
|
Require all denied
|
||||||
|
</Files>
|
||||||
251
deploy/public/assets/css/docs.css
Normal file
251
deploy/public/assets/css/docs.css
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
:root {
|
||||||
|
--color-bg: #ffffff;
|
||||||
|
--color-surface: #f8f9fa;
|
||||||
|
--color-border: #e1e4e8;
|
||||||
|
--color-text: #24292e;
|
||||||
|
--color-text-muted: #586069;
|
||||||
|
--color-primary: #0366d6;
|
||||||
|
--color-primary-dark: #0256c4;
|
||||||
|
--color-success: #28a745;
|
||||||
|
--color-code-bg: #f6f8fa;
|
||||||
|
--spacing-xs: 0.25rem;
|
||||||
|
--spacing-sm: 0.5rem;
|
||||||
|
--spacing-md: 1rem;
|
||||||
|
--spacing-lg: 1.5rem;
|
||||||
|
--spacing-xl: 2rem;
|
||||||
|
--spacing-2xl: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: var(--spacing-2xl) 0 var(--spacing-lg);
|
||||||
|
padding-bottom: var(--spacing-sm);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: var(--spacing-xl) 0 var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: var(--spacing-2xl) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
background: var(--color-surface);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro p {
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint {
|
||||||
|
margin-bottom: var(--spacing-2xl);
|
||||||
|
padding-bottom: var(--spacing-2xl);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.method {
|
||||||
|
display: inline-block;
|
||||||
|
padding: var(--spacing-xs) var(--spacing-md);
|
||||||
|
background: var(--color-success);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||||
|
font-size: 0.875em;
|
||||||
|
background: var(--color-code-bg);
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #0d1117;
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-lg);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button.active {
|
||||||
|
color: var(--color-primary);
|
||||||
|
border-bottom-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.required {
|
||||||
|
background: #fff5f5;
|
||||||
|
color: #d73a49;
|
||||||
|
border-color: #d73a49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.optional {
|
||||||
|
background: #f1f8ff;
|
||||||
|
color: var(--color-primary);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: var(--spacing-2xl);
|
||||||
|
padding: var(--spacing-xl) 0;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-info {
|
||||||
|
background: #fff5d1;
|
||||||
|
border: 1px solid #e4d500;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-info strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
10
deploy/public/assets/css/highlight-github-dark.min.css
vendored
Normal file
10
deploy/public/assets/css/highlight-github-dark.min.css
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: GitHub Dark
|
||||||
|
Description: Dark theme as seen on github.com
|
||||||
|
Author: github.com
|
||||||
|
Maintainer: @Hirse
|
||||||
|
Updated: 2021-05-15
|
||||||
|
|
||||||
|
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||||
|
Current colors taken from GitHub's CSS
|
||||||
|
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}
|
||||||
1213
deploy/public/assets/js/highlight.min.js
vendored
Normal file
1213
deploy/public/assets/js/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
320
deploy/public/docs.html
Normal file
320
deploy/public/docs.html
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Web2Print API Documentation</title>
|
||||||
|
<link rel="stylesheet" href="/assets/css/docs.css">
|
||||||
|
<link rel="stylesheet" href="/assets/css/highlight-github-dark.min.css">
|
||||||
|
<script src="/assets/js/highlight.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Web2Print API</h1>
|
||||||
|
<p class="subtitle">Générez des PDF à partir de HTML avec Paged.js</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<section class="intro">
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
<p>
|
||||||
|
L'API Web2Print permet de générer des documents PDF de haute qualité à partir de contenu HTML.
|
||||||
|
Elle utilise <strong>Paged.js</strong> pour appliquer les standards CSS Paged Media,
|
||||||
|
offrant un contrôle précis sur la mise en page, les sauts de page, les en-têtes, les pieds de page et bien plus.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cette API est conçue pour être simple d'utilisation tout en offrant une grande flexibilité
|
||||||
|
pour créer des documents professionnels tels que rapports, factures, catalogues ou livres.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="auth-info">
|
||||||
|
<strong>🔐 Authentification requise</strong>
|
||||||
|
<p>
|
||||||
|
Toutes les requêtes doivent inclure un header <code>X-API-Key</code> avec une clé API valide.
|
||||||
|
Contactez l'administrateur pour obtenir votre clé d'accès.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<h2>Endpoints</h2>
|
||||||
|
|
||||||
|
<article class="endpoint">
|
||||||
|
<div class="endpoint-header">
|
||||||
|
<span class="method">POST</span>
|
||||||
|
<span class="path">/generate</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="description">
|
||||||
|
Génère un document PDF à partir de contenu HTML. Le HTML peut inclure des styles inline
|
||||||
|
ou dans une balise <code><style></code>. CSS additionnel peut être fourni via le paramètre <code>css</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Paramètres</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Paramètre</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Requis</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>html</code></td>
|
||||||
|
<td>string</td>
|
||||||
|
<td><span class="badge required">Requis</span></td>
|
||||||
|
<td>Contenu HTML du document à convertir en PDF</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>css</code></td>
|
||||||
|
<td>string</td>
|
||||||
|
<td><span class="badge optional">Optionnel</span></td>
|
||||||
|
<td>Styles CSS additionnels à appliquer au document</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>options</code></td>
|
||||||
|
<td>object</td>
|
||||||
|
<td><span class="badge optional">Optionnel</span></td>
|
||||||
|
<td>Options de configuration pour la génération PDF</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Headers requis</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Header</th>
|
||||||
|
<th>Valeur</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>Content-Type</code></td>
|
||||||
|
<td><code>application/json</code></td>
|
||||||
|
<td>Type de contenu de la requête</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>X-API-Key</code></td>
|
||||||
|
<td>Votre clé API</td>
|
||||||
|
<td>Clé d'authentification</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Réponse</h3>
|
||||||
|
<p class="description">
|
||||||
|
En cas de succès, l'API retourne le fichier PDF binaire avec le header <code>Content-Type: application/pdf</code>.
|
||||||
|
En cas d'erreur, elle retourne un JSON avec un objet <code>error</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Exemples de code</h3>
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<button class="tab-button active" data-tab="curl">cURL</button>
|
||||||
|
<button class="tab-button" data-tab="javascript">JavaScript</button>
|
||||||
|
<button class="tab-button" data-tab="python">Python</button>
|
||||||
|
<button class="tab-button" data-tab="php">PHP</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content active" id="curl">
|
||||||
|
<pre><code class="language-bash">curl -X POST https://web2print.studio-variable.com/generate \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
-d '{
|
||||||
|
"html": "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><style>@page { size: A4; margin: 2cm; } body { font-family: Arial; } h1 { color: #333; }</style></head><body><h1>Mon Document</h1><p>Contenu du document.</p></body></html>"
|
||||||
|
}' \
|
||||||
|
--output document.pdf</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content" id="javascript">
|
||||||
|
<pre><code class="language-javascript">const response = await fetch('https://web2print.studio-variable.com/generate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-API-Key': 'YOUR_API_KEY'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
html: `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
@page { size: A4; margin: 2cm; }
|
||||||
|
body { font-family: Arial; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Mon Document</h1>
|
||||||
|
<p>Contenu du document.</p>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const blob = await response.blob();
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'document.pdf';
|
||||||
|
a.click();
|
||||||
|
} else {
|
||||||
|
const error = await response.json();
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content" id="python">
|
||||||
|
<pre><code class="language-python">import requests
|
||||||
|
|
||||||
|
url = 'https://web2print.studio-variable.com/generate'
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-API-Key': 'YOUR_API_KEY'
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
'html': '''<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
@page { size: A4; margin: 2cm; }
|
||||||
|
body { font-family: Arial; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Mon Document</h1>
|
||||||
|
<p>Contenu du document.</p>
|
||||||
|
</body>
|
||||||
|
</html>'''
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open('document.pdf', 'wb') as f:
|
||||||
|
f.write(response.content)
|
||||||
|
print('PDF généré avec succès')
|
||||||
|
else:
|
||||||
|
print('Erreur:', response.json())</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content" id="php">
|
||||||
|
<pre><code class="language-php">$ch = curl_init('https://web2print.studio-variable.com/generate');
|
||||||
|
|
||||||
|
$html = '<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
@page { size: A4; margin: 2cm; }
|
||||||
|
body { font-family: Arial; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Mon Document</h1>
|
||||||
|
<p>Contenu du document.</p>
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
|
||||||
|
$data = json_encode(['html' => $html]);
|
||||||
|
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'X-API-Key: YOUR_API_KEY'
|
||||||
|
],
|
||||||
|
CURLOPT_POSTFIELDS => $data
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode === 200) {
|
||||||
|
file_put_contents('document.pdf', $response);
|
||||||
|
echo 'PDF généré avec succès';
|
||||||
|
} else {
|
||||||
|
$error = json_decode($response, true);
|
||||||
|
echo 'Erreur: ' . $error['error'];
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Codes de réponse HTTP</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>200</code></td>
|
||||||
|
<td>Succès - Le PDF est retourné dans le corps de la réponse</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>400</code></td>
|
||||||
|
<td>Requête invalide - JSON mal formé ou paramètre manquant</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>401</code></td>
|
||||||
|
<td>Non autorisé - Clé API manquante ou invalide</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>405</code></td>
|
||||||
|
<td>Méthode non autorisée - Seul POST est accepté</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>413</code></td>
|
||||||
|
<td>Requête trop volumineuse - Le contenu HTML dépasse la limite</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>500</code></td>
|
||||||
|
<td>Erreur serveur - Erreur lors de la génération du PDF</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="container">
|
||||||
|
<p>Web2Print API © 2024 - Propulsé par Paged.js</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Gestion des onglets
|
||||||
|
document.querySelectorAll('.tab-button').forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const tabName = button.dataset.tab;
|
||||||
|
const tabGroup = button.closest('.endpoint');
|
||||||
|
|
||||||
|
// Désactiver tous les onglets et contenus
|
||||||
|
tabGroup.querySelectorAll('.tab-button').forEach(btn => {
|
||||||
|
btn.classList.remove('active');
|
||||||
|
});
|
||||||
|
tabGroup.querySelectorAll('.tab-content').forEach(content => {
|
||||||
|
content.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activer l'onglet et le contenu sélectionnés
|
||||||
|
button.classList.add('active');
|
||||||
|
tabGroup.querySelector(`#${tabName}`).classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Highlight.js
|
||||||
|
hljs.highlightAll();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
62
deploy/public/index.php
Normal file
62
deploy/public/index.php
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Autoloader simple
|
||||||
|
spl_autoload_register(function ($class) {
|
||||||
|
$prefix = 'Web2Print\\';
|
||||||
|
$baseDir = __DIR__ . '/../src/';
|
||||||
|
|
||||||
|
$len = strlen($prefix);
|
||||||
|
if (strncmp($prefix, $class, $len) !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativeClass = substr($class, $len);
|
||||||
|
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||||
|
|
||||||
|
if (file_exists($file)) {
|
||||||
|
require $file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Charger la configuration
|
||||||
|
$config = require __DIR__ . '/../config/config.php';
|
||||||
|
|
||||||
|
// Router simple
|
||||||
|
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||||
|
|
||||||
|
// Afficher la documentation sur la racine
|
||||||
|
if ($uri === '/' && $_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
readfile(__DIR__ . '/docs.html');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Définir le temps d'exécution max
|
||||||
|
set_time_limit($config['max_execution_time']);
|
||||||
|
|
||||||
|
// Headers CORS (si nécessaire)
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||||
|
|
||||||
|
// Gérer preflight OPTIONS
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentification
|
||||||
|
$auth = new \Web2Print\Middleware\AuthMiddleware($config);
|
||||||
|
if (!$auth->authenticate()) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint de génération PDF
|
||||||
|
if ($uri === '/generate') {
|
||||||
|
$generator = new \Web2Print\Services\PdfGenerator($config);
|
||||||
|
$controller = new \Web2Print\Controllers\GenerateController($generator, $config);
|
||||||
|
$controller->handle();
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['error' => 'Not found']);
|
||||||
|
}
|
||||||
71
deploy/src/Controllers/GenerateController.php
Normal file
71
deploy/src/Controllers/GenerateController.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Web2Print\Controllers;
|
||||||
|
|
||||||
|
use Web2Print\Services\PdfGenerator;
|
||||||
|
|
||||||
|
class GenerateController
|
||||||
|
{
|
||||||
|
private PdfGenerator $generator;
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
public function __construct(PdfGenerator $generator, array $config)
|
||||||
|
{
|
||||||
|
$this->generator = $generator;
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// Vérifier la méthode HTTP
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
$this->sendError(405, 'Method not allowed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lire le body JSON
|
||||||
|
$input = file_get_contents('php://input');
|
||||||
|
|
||||||
|
if (strlen($input) > $this->config['max_html_size']) {
|
||||||
|
$this->sendError(413, 'Request too large');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($input, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
$this->sendError(400, 'Invalid JSON');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valider les données
|
||||||
|
if (empty($data['html'])) {
|
||||||
|
$this->sendError(400, 'HTML content required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Générer le PDF
|
||||||
|
$pdf = $this->generator->generate(
|
||||||
|
$data['html'],
|
||||||
|
$data['css'] ?? null,
|
||||||
|
$data['options'] ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retourner le PDF
|
||||||
|
header('Content-Type: application/pdf');
|
||||||
|
header('Content-Length: ' . strlen($pdf));
|
||||||
|
echo $pdf;
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->sendError(500, 'PDF generation error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendError(int $code, string $message): void
|
||||||
|
{
|
||||||
|
http_response_code($code);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['error' => $message]);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
deploy/src/Middleware/AuthMiddleware.php
Normal file
38
deploy/src/Middleware/AuthMiddleware.php
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Web2Print\Middleware;
|
||||||
|
|
||||||
|
class AuthMiddleware
|
||||||
|
{
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
public function __construct(array $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authenticate(): bool
|
||||||
|
{
|
||||||
|
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||||
|
|
||||||
|
if (empty($apiKey)) {
|
||||||
|
$this->sendError(401, 'API key missing');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($apiKey, $this->config['api_keys'], true)) {
|
||||||
|
$this->sendError(403, 'Invalid API key');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendError(int $code, string $message): void
|
||||||
|
{
|
||||||
|
http_response_code($code);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['error' => $message]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
102
deploy/src/Services/PdfGenerator.php
Normal file
102
deploy/src/Services/PdfGenerator.php
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Web2Print\Services;
|
||||||
|
|
||||||
|
class PdfGenerator
|
||||||
|
{
|
||||||
|
private array $config;
|
||||||
|
|
||||||
|
public function __construct(array $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(string $html, ?string $css = null, array $options = []): string
|
||||||
|
{
|
||||||
|
// Créer un fichier HTML temporaire
|
||||||
|
$htmlFile = $this->createTempFile($html, $css);
|
||||||
|
$pdfFile = tempnam($this->config['tmp_dir'], 'pdf_') . '.pdf';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Construire la commande Paged.js CLI
|
||||||
|
$command = $this->buildCommand($htmlFile, $pdfFile, $options);
|
||||||
|
|
||||||
|
// Exécuter Paged.js CLI
|
||||||
|
$output = [];
|
||||||
|
$returnCode = 0;
|
||||||
|
exec($command . ' 2>&1', $output, $returnCode);
|
||||||
|
|
||||||
|
if ($returnCode !== 0) {
|
||||||
|
$this->log('Paged.js CLI error: ' . implode("\n", $output));
|
||||||
|
throw new \Exception('PDF generation failed: ' . implode("\n", $output));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lire le PDF généré
|
||||||
|
if (!file_exists($pdfFile)) {
|
||||||
|
throw new \Exception('PDF file not created');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdfContent = file_get_contents($pdfFile);
|
||||||
|
|
||||||
|
return $pdfContent;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// Nettoyer les fichiers temporaires
|
||||||
|
@unlink($htmlFile);
|
||||||
|
@unlink($pdfFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createTempFile(string $html, ?string $css): string
|
||||||
|
{
|
||||||
|
$fullHtml = $html;
|
||||||
|
|
||||||
|
// Injecter le CSS dans le HTML si fourni
|
||||||
|
if ($css) {
|
||||||
|
$styleTag = "<style>{$css}</style>";
|
||||||
|
if (stripos($html, '</head>') !== false) {
|
||||||
|
$fullHtml = str_ireplace('</head>', $styleTag . '</head>', $html);
|
||||||
|
} else {
|
||||||
|
// Si pas de <head>, créer une structure HTML complète
|
||||||
|
$fullHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'>{$styleTag}</head><body>{$html}</body></html>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// S'assurer que le HTML a une structure complète
|
||||||
|
if (stripos($fullHtml, '<!DOCTYPE') === false) {
|
||||||
|
$fullHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>{$fullHtml}</body></html>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$tempFile = tempnam($this->config['tmp_dir'], 'html_') . '.html';
|
||||||
|
file_put_contents($tempFile, $fullHtml);
|
||||||
|
|
||||||
|
return $tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCommand(string $htmlFile, string $pdfFile, array $options): string
|
||||||
|
{
|
||||||
|
$cmd = escapeshellcmd($this->config['pagedjs_bin']);
|
||||||
|
$cmd .= ' ' . escapeshellarg($htmlFile);
|
||||||
|
$cmd .= ' -o ' . escapeshellarg($pdfFile);
|
||||||
|
|
||||||
|
// Options supplémentaires pour Paged.js CLI
|
||||||
|
// Note: Paged.js utilise les règles CSS @page pour le format
|
||||||
|
// Les options sont limitées dans la CLI
|
||||||
|
|
||||||
|
// Timeout (millisecondes)
|
||||||
|
if (!empty($options['timeout'])) {
|
||||||
|
$cmd .= ' --timeout ' . (int)$options['timeout'];
|
||||||
|
} else {
|
||||||
|
$cmd .= ' --timeout ' . ($this->config['pagedjs_timeout'] * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function log(string $message): void
|
||||||
|
{
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$logMessage = "[{$timestamp}] {$message}\n";
|
||||||
|
file_put_contents($this->config['log_file'], $logMessage, FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
test-css-custom.md
Normal file
9
test-css-custom.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Test du CSS custom
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://web2print.studio-variable.com/generate -H "Content-Type: application/json" -H "X-API-Key: 25377ab6e2153b159d1d5fa22501228810b6aec9d63346bd0614045dc167061c" -d '{"html":"<!DOCTYPE html><html><head><meta charset=\"utf-8\"><style>@page { size: A4; margin: 2cm; } body { font-family: Arial; }</style></head><body><h1>Test CSS Custom</h1><p class=\"custom\">Ce texte devrait être rouge et en gras.</p></body></html>","css":"h1 { color: blue; } .custom { color: red; font-weight: bold; font-size: 20px; }"}' --output test-css-custom.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
Le PDF généré devrait avoir :
|
||||||
|
- Le h1 en **bleu** (CSS custom)
|
||||||
|
- Le paragraphe avec classe "custom" en **rouge et gras** (CSS custom)
|
||||||
99
web2print-specs.md
Normal file
99
web2print-specs.md
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Spécifications Service Web2Print
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
Service de conversion HTML/CSS vers PDF déployé sur VPS à l'URL `web2print.studio-variable.com`
|
||||||
|
|
||||||
|
## Décisions techniques
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
- **Backend** : PHP (maîtrise, cohérence avec environnement)
|
||||||
|
- **Générateur PDF** : Paged.js CLI (via exec CLI)
|
||||||
|
- **Serveur web** : Apache (déjà en place sur VPS)
|
||||||
|
- **Méthode d'appel** : API REST
|
||||||
|
- **Déploiement** : Installation directe sur VPS (pas de Docker)
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
PHP gère :
|
||||||
|
- Réception requêtes HTTP
|
||||||
|
- Authentification par clé API
|
||||||
|
- Validation des inputs
|
||||||
|
- Appel Paged.js CLI via shell_exec/exec
|
||||||
|
- Retour du PDF en binaire
|
||||||
|
|
||||||
|
### Avantages Paged.js
|
||||||
|
- Rendu identique entre front-end et back-end (même moteur)
|
||||||
|
- Support natif CSS Paged Media (marges, en-têtes/pieds, numérotation)
|
||||||
|
- Basé sur Chromium (meilleur support CSS moderne)
|
||||||
|
- Cohérence avec intégration front-end Paged.js prévue
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Plugin Kirby CMS
|
||||||
|
- Envoie requêtes HTTP avec clé API
|
||||||
|
- Reçoit PDF en binaire
|
||||||
|
- Stocke le PDF dans un champ Kirby
|
||||||
|
|
||||||
|
### Sécurité
|
||||||
|
- Authentification : clé API dans header HTTP (X-API-Key)
|
||||||
|
- Clés stockées dans config PHP (ou .env)
|
||||||
|
- Pas d'interface admin nécessaire
|
||||||
|
- Clé configurée manuellement dans le plugin Kirby
|
||||||
|
|
||||||
|
## Caractéristiques
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
- Volume : faible, occasionnel (quelques PDFs/jour)
|
||||||
|
- Taille documents : variable, jusqu'à des livres complets
|
||||||
|
- Ressources externes : autorisées (images, CSS, fonts via URL)
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
**Endpoint** : `POST /generate`
|
||||||
|
|
||||||
|
**Headers** :
|
||||||
|
```
|
||||||
|
X-API-Key: <clé-secrète>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Body** :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"html": "<html>...</html>",
|
||||||
|
"css": "...", // optionnel
|
||||||
|
"options": {
|
||||||
|
// À définir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response** : PDF en binaire (application/pdf)
|
||||||
|
|
||||||
|
## Points à clarifier
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
- [x] Déploiement : Installation directe sur VPS (Paged.js CLI via npm)
|
||||||
|
- [x] Structure du service : structure organisée (Controllers, Services, Middleware)
|
||||||
|
- [x] Config Apache : VirtualHost pointant vers le dossier du service
|
||||||
|
- [x] Stockage temporaire : /var/www/web2print/tmp/
|
||||||
|
|
||||||
|
### Fonctionnalités
|
||||||
|
- [ ] Options PDF exposées (format, marges, orientation, etc.)
|
||||||
|
- [ ] Timeout génération (60s suffisant pour livres ?)
|
||||||
|
- [ ] Limite taille HTML en entrée
|
||||||
|
- [ ] Ressources externes : tout autoriser ou liste blanche de domaines ?
|
||||||
|
- [ ] Timeout fetch ressources externes
|
||||||
|
|
||||||
|
### Gestion d'erreurs
|
||||||
|
- [ ] Format réponses d'erreur (JSON + HTTP status ?)
|
||||||
|
- [ ] Stockage logs (où ? rotation ?)
|
||||||
|
- [ ] Monitoring/alertes
|
||||||
|
|
||||||
|
### Sécurité avancée
|
||||||
|
- [ ] Rate limiting par clé API
|
||||||
|
- [ ] Validation/sanitization HTML
|
||||||
|
- [ ] Isolation processus Paged.js CLI
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Ce fichier sert de documentation temporaire pendant la phase de spécification
|
||||||
|
- Le service sera dans un projet séparé de ce repo
|
||||||
|
- Le plugin Kirby sera ajouté à ce projet plus tard
|
||||||
Loading…
Add table
Add a link
Reference in a new issue