Skip to content
Merged

Dev #20

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 21 additions & 18 deletions Dockerfile.api
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ FROM node:20-alpine AS builder

# Install security updates and build dependencies
RUN apk update && apk upgrade && \
apk add --no-cache dumb-init && \
rm -rf /var/cache/apk/*
apk add --no-cache dumb-init && \
rm -rf /var/cache/apk/*

WORKDIR /app

Expand Down Expand Up @@ -46,25 +46,25 @@ FROM node:20-alpine AS production

# Install security updates and runtime dependencies
RUN apk update && apk upgrade && \
apk add --no-cache \
dumb-init \
chromium \
chromium-chromedriver \
nss \
freetype \
freetype-dev \
harfbuzz \
ca-certificates \
ttf-freefont \
ttf-liberation \
font-noto \
font-noto-cjk \
font-noto-extra \
&& rm -rf /var/cache/apk/*
apk add --no-cache \
dumb-init \
chromium \
chromium-chromedriver \
nss \
freetype \
freetype-dev \
harfbuzz \
ca-certificates \
ttf-freefont \
ttf-liberation \
font-noto \
font-noto-cjk \
font-noto-extra \
&& rm -rf /var/cache/apk/*

# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && \
adduser -S apiuser -u 1001
adduser -S apiuser -u 1001

WORKDIR /app

Expand All @@ -80,6 +80,9 @@ ENV CHROME_PATH=/usr/bin/chromium-browser
# Copy built JavaScript files
COPY --from=builder --chown=apiuser:nodejs /app/apps/api/dist ./dist

# Copy public resources (CSS, JS) needed for PDF generation
COPY --from=builder --chown=apiuser:nodejs /app/apps/api/public ./public

# Copy node_modules from root (npm installs them there in monorepo)
COPY --from=builder --chown=apiuser:nodejs /app/node_modules ./node_modules

Expand Down
73 changes: 67 additions & 6 deletions apps/api/api/services/pdf.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,57 +133,118 @@ export class PdfService {

// Précharger toutes les ressources statiques en cache
private static async preloadResources(): Promise<void> {
const workingDir = process.cwd();
logger.info(`Working directory: ${workingDir}`);

const resources = [
{
key: 'primeicons',
path: path.join(process.cwd(), 'public', 'css', 'primeicons.css'),
path: path.join(workingDir, 'public', 'css', 'primeicons.css'),
},
{
key: 'tailwind',
path: path.join(process.cwd(), 'public', 'scripts', 'tailwind.js'),
path: path.join(workingDir, 'public', 'scripts', 'tailwind.js'),
},
{
key: 'chartjs',
path: path.join(process.cwd(), 'public', 'scripts', 'chart.js'),
path: path.join(workingDir, 'public', 'scripts', 'chart.js'),
},
];

// Diagnostic des dossiers disponibles
try {
const rootFiles = await fs.readdir(workingDir);
logger.info(`Root directory contents: ${rootFiles.join(', ')}`);

const publicPath = path.join(workingDir, 'public');
if (await fs.pathExists(publicPath)) {
const publicFiles = await fs.readdir(publicPath);
logger.info(`Public directory contents: ${publicFiles.join(', ')}`);

// Vérifier les sous-dossiers
const cssPath = path.join(publicPath, 'css');
const scriptsPath = path.join(publicPath, 'scripts');

if (await fs.pathExists(cssPath)) {
const cssFiles = await fs.readdir(cssPath);
logger.info(`CSS directory contents: ${cssFiles.join(', ')}`);
} else {
logger.warn(`CSS directory not found: ${cssPath}`);
}

if (await fs.pathExists(scriptsPath)) {
const scriptFiles = await fs.readdir(scriptsPath);
logger.info(`Scripts directory contents: ${scriptFiles.join(', ')}`);
} else {
logger.warn(`Scripts directory not found: ${scriptsPath}`);
}
} else {
logger.error(`Public directory not found: ${publicPath}`);
}
} catch (error) {
logger.error('Error during directory diagnostic:', error);
}

for (const resource of resources) {
try {
logger.info(`Checking resource: ${resource.key} at ${resource.path}`);

if (await fs.pathExists(resource.path)) {
const stats = await fs.stat(resource.path);
logger.info(`Resource ${resource.key} found, size: ${Math.round(stats.size / 1024)}KB`);

const content = await fs.readFile(resource.path, 'utf8');
this.resourcesCache.set(resource.key, content);
logger.info(`Cached resource: ${resource.key}`);
logger.info(`✅ Successfully cached resource: ${resource.key} (${Math.round(content.length / 1024)}KB)`);
} else {
logger.warn(`Resource not found: ${resource.path}`);
logger.error(`❌ Resource not found: ${resource.path}`);
}
} catch (error) {
logger.error(`Failed to cache resource ${resource.key}:`, error);
logger.error(`Failed to cache resource ${resource.key} from ${resource.path}:`, error);
}
}

logger.info(`Resources cache summary: ${this.resourcesCache.size} resources loaded`);
for (const [key] of this.resourcesCache.entries()) {
logger.info(` - ${key}: loaded`);
}
}

// Créer une page optimisée avec les ressources pré-chargées
private static async createOptimizedPage(): Promise<Page> {
const browser = this.getBrowser();
const page = await browser.newPage();

logger.info('Creating optimized page with cached resources...');

// Injecter les ressources depuis le cache
const primeiconsContent = this.resourcesCache.get('primeicons');
if (primeiconsContent) {
await page.addStyleTag({ content: primeiconsContent });
logger.info('✅ PrimeIcons CSS injected into page');
} else {
logger.warn('❌ PrimeIcons CSS not available in cache');
}

const tailwindContent = this.resourcesCache.get('tailwind');
if (tailwindContent) {
await page.addScriptTag({ content: tailwindContent });
logger.info('✅ Tailwind CSS script injected into page');
} else {
logger.warn('❌ Tailwind CSS script not available in cache');
}

const chartjsContent = this.resourcesCache.get('chartjs');
if (chartjsContent) {
await page.addScriptTag({ content: chartjsContent });
logger.info('✅ Chart.js script injected into page');
} else {
logger.warn('❌ Chart.js script not available in cache');
}

const injectedResources = [primeiconsContent, tailwindContent, chartjsContent].filter(Boolean).length;
logger.info(`Page optimization complete: ${injectedResources}/3 resources injected`);

return page;
}

Expand Down
58 changes: 45 additions & 13 deletions apps/main-dashboard/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,43 @@ events {
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Configuration MIME types complète
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
image/png png;
image/svg+xml svg svgz;
image/webp webp;

# JavaScript - CRITIQUE pour ngx-extended-pdf-viewer
application/javascript js mjs;
text/javascript js mjs;

# WebAssembly
application/wasm wasm;

# Fonts
font/woff woff;
font/woff2 woff2;
application/font-woff woff;
application/font-woff2 woff2;
font/ttf ttf;
font/otf otf;
application/vnd.ms-fontobject eot;

# Ajouter les MIME types manquants pour PDF.js
map $uri $content_type_override {
~\.mjs$ "application/javascript";
~\.wasm$ "application/wasm";
default "";
# Documents
application/pdf pdf;
application/json json;

# Autres
application/octet-stream bin exe dll deb dmg iso img msi msp msm;
}

default_type application/octet-stream;

sendfile on;
keepalive_timeout 65;
gzip on;
Expand All @@ -29,14 +56,14 @@ http {
try_files $uri $uri/ /index.html;
}

# Configuration spécifique pour les fichiers .mjs (modules JavaScript)
# FORCE MIME type pour .mjs - CRITIQUE
location ~* \.mjs$ {
add_header Content-Type "application/javascript" always;
expires 1y;
add_header Cache-Control "public, immutable";
}

# Configuration spécifique pour les fichiers .wasm (WebAssembly)
# FORCE MIME type pour .wasm
location ~* \.wasm$ {
add_header Content-Type "application/wasm" always;
expires 1y;
Expand All @@ -54,10 +81,15 @@ http {
expires 1y;
add_header Cache-Control "public, immutable";

# Permettre l'accès CORS si nécessaire
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
add_header Access-Control-Allow-Headers "Range" always;
# FORCE le bon MIME type pour tous les .mjs dans assets/
location ~ \.mjs$ {
add_header Content-Type "application/javascript" always;
}

# FORCE le bon MIME type pour tous les .wasm dans assets/
location ~ \.wasm$ {
add_header Content-Type "application/wasm" always;
}
}
}
}
63 changes: 63 additions & 0 deletions apps/main-dashboard/nginx.conf.old
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;

# MIME types additionnels pour PDF.js
types {
application/javascript mjs;
application/wasm wasm;
}

default_type application/octet-stream;

sendfile on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/wasm;

server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;

# Route par défaut - SPA routing
location / {
try_files $uri $uri/ /index.html;
}

# Configuration spécifique pour les fichiers .mjs (modules JavaScript)
location ~* \.mjs$ {
add_header Content-Type "application/javascript" always;
expires 1y;
add_header Cache-Control "public, immutable";
}

# Configuration spécifique pour les fichiers .wasm (WebAssembly)
location ~* \.wasm$ {
add_header Content-Type "application/wasm" always;
expires 1y;
add_header Cache-Control "public, immutable";
}

# Assets statiques généraux
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}

# Configuration spéciale pour le dossier assets (ngx-extended-pdf-viewer)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";

# Permettre l'accès CORS si nécessaire
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
add_header Access-Control-Allow-Headers "Range" always;
}
}
}
Empty file added test-mime-quick.sh
Empty file.