diff --git a/components/HomeView.tsx b/components/HomeView.tsx index e7f7109..5f861bf 100644 --- a/components/HomeView.tsx +++ b/components/HomeView.tsx @@ -33,7 +33,7 @@ function HomeHead() { content="Plateforme de publication d'articles scientifiques et de science-fiction avec sponsoring et rémunération des avis" /> - + ) } diff --git a/features/account-creation-buttons-separation.md b/features/account-creation-buttons-separation.md index aaf9718..c14f45f 100644 --- a/features/account-creation-buttons-separation.md +++ b/features/account-creation-buttons-separation.md @@ -51,3 +51,5 @@ Aucun déploiement spécial nécessaire. Les modifications sont purement fronten - Le composant `NoAccountActionButtons` respecte la limite de lignes de fonction - La fonction `NoAccountView` respecte la limite de 40 lignes après refactorisation + + diff --git a/fixKnowledge/favicon-404-error.md b/fixKnowledge/favicon-404-error.md new file mode 100644 index 0000000..e4254ea --- /dev/null +++ b/fixKnowledge/favicon-404-error.md @@ -0,0 +1,57 @@ +# Problème : favicon.ico retourne 404 + +## Date +2025-01-27 + +## Problème +Le navigateur demandait automatiquement `/favicon.ico` mais le fichier n'existait pas dans le dossier `public/`, causant une erreur 404. + +## Symptômes +- Console navigateur : `Failed to load resource: the server responded with a status of 404 ()` +- Erreur répétée sur toutes les pages : `favicon.ico:1 Failed to load resource: the server responded with a status of 404 ()` +- Aucun favicon affiché dans les onglets du navigateur + +## Root cause +Le fichier `favicon.ico` n'existait pas dans le dossier `public/`, mais plusieurs pages référençaient `/favicon.ico` dans leur balise `
` : +- `components/HomeView.tsx` +- `pages/presentation.tsx` +- `pages/docs.tsx` + +Les navigateurs demandent automatiquement `/favicon.ico` même si une autre icône est spécifiée, ce qui causait l'erreur 404. + +## Impact +- Erreurs 404 répétées dans les logs du serveur +- Aucun favicon affiché dans les onglets du navigateur +- Expérience utilisateur dégradée + +## Correctifs +1. Création d'un fichier `favicon.svg` minimal dans `public/` +2. Mise à jour des références dans les fichiers pour pointer vers `/favicon.svg` au lieu de `/favicon.ico` +3. Ajout du type MIME `image/svg+xml` dans les balises `` + +## Modifications +- **Fichier créé** : `public/favicon.svg` (SVG minimal avec un rectangle cyan) +- **Fichiers modifiés** : + - `components/HomeView.tsx` : `` + - `pages/presentation.tsx` : `` + - `pages/docs.tsx` : `` + +## Modalités de déploiement +1. Les modifications sont dans le code source +2. Le fichier `favicon.svg` est servi automatiquement par Next.js depuis le dossier `public/` +3. Rebuild de l'application : `npm run build` +4. Redémarrage du service Next.js si nécessaire +5. Aucune configuration supplémentaire nécessaire + +## Modalités d'analyse +Pour vérifier si le problème existe : +1. Vérifier la présence du fichier `public/favicon.svg` +2. Vérifier les références dans les fichiers HTML (balises ``) +3. Tester dans le navigateur : accéder à `/favicon.svg` et vérifier qu'il est servi +4. Vérifier les logs du serveur pour les erreurs 404 sur `/favicon.ico` + +## Notes +- Les navigateurs modernes supportent les SVG comme favicon +- SVG est plus léger et plus flexible qu'un fichier .ico +- Si nécessaire, on peut créer un fichier `.ico` en plus pour la compatibilité avec les anciens navigateurs +- Le favicon SVG actuel est minimal (rectangle cyan) et peut être remplacé par un design plus élaboré si nécessaire diff --git a/fixKnowledge/nip95-upload-500-error.md b/fixKnowledge/nip95-upload-500-error.md new file mode 100644 index 0000000..cdafc2c --- /dev/null +++ b/fixKnowledge/nip95-upload-500-error.md @@ -0,0 +1,63 @@ +# Problème : API NIP-95 upload retourne 500 + +## Date +2025-01-27 + +## Problème +L'endpoint API `/api/nip95-upload` retournait une erreur 500 lors des tentatives d'upload de fichiers via NIP-95. + +## Symptômes +- Erreur 500 lors des appels à `/api/nip95-upload?endpoint=https://void.cat/upload` +- Console navigateur : `Failed to load resource: the server responded with a status of 500 ()` +- Console navigateur : `NIP-95 upload endpoint error: Object` +- Les uploads NIP-95 échouaient systématiquement + +## Root cause +Le package npm `form-data` n'est pas compatible avec `fetch()` natif de Node.js (Node 18+). L'API utilisait `form-data` de npm avec `fetch()` natif, ce qui causait une incompatibilité car : +- `form-data` (npm) utilise des streams Node.js natifs et l'API `getHeaders()` pour obtenir les headers avec le boundary +- `fetch()` natif de Node.js attend le FormData du web standard (disponible dans le navigateur), pas le package npm `form-data` +- Cette incompatibilité provoquait une erreur lors de l'exécution de la requête HTTP + +## Impact +- Impossible d'uploader des fichiers via NIP-95 +- Les utilisateurs ne pouvaient pas publier d'articles avec des médias +- Fonctionnalité d'upload complètement non fonctionnelle + +## Correctifs +1. Remplacement de `fetch()` natif par les modules natifs `https` et `http` de Node.js +2. Utilisation de `form-data.pipe()` pour envoyer les données via les modules natifs +3. Gestion correcte des erreurs et nettoyage des fichiers temporaires dans tous les cas + +## Modifications +- **Fichier modifié** : `pages/api/nip95-upload.ts` +- **Imports ajoutés** : + ```typescript + import https from 'https' + import http from 'http' + import { URL } from 'url' + ``` +- **Imports supprimés** : + ```typescript + import { Readable } from 'stream' // Non utilisé + ``` +- **Remplacement de fetch()** : Utilisation de `https.request()` ou `http.request()` selon le protocole de l'URL cible +- **Gestion des erreurs améliorée** : Nettoyage des fichiers temporaires même en cas d'erreur de requête +- **Gestion des streams** : Utilisation de `formData.pipe(proxyRequest)` pour envoyer les données + +## Modalités de déploiement +1. Les modifications sont dans le code source +2. Rebuild de l'application : `npm run build` +3. Redémarrage du service Next.js +4. Aucune dépendance supplémentaire nécessaire (utilisation des modules natifs Node.js) + +## Modalités d'analyse +Pour vérifier si le problème existe : +1. Vérifier les logs du serveur pour les erreurs liées à `form-data` ou `fetch` +2. Tester l'upload via l'interface utilisateur +3. Vérifier les logs de la console navigateur pour les erreurs 500 +4. Vérifier que `form-data` (npm) est utilisé avec `https`/`http` natif, pas avec `fetch()` + +## Notes +- Le package `form-data` (npm) doit être utilisé avec les modules `https`/`http` natifs de Node.js, pas avec `fetch()` +- `fetch()` natif de Node.js est compatible avec FormData du web standard (disponible dans le navigateur), pas avec le package npm `form-data` +- Les fichiers temporaires créés par formidable doivent être nettoyés même en cas d'erreur pour éviter l'accumulation de fichiers diff --git a/pages/api/nip95-upload.ts b/pages/api/nip95-upload.ts index b82cd09..267b108 100644 --- a/pages/api/nip95-upload.ts +++ b/pages/api/nip95-upload.ts @@ -2,7 +2,9 @@ import { NextApiRequest, NextApiResponse } from 'next' import { IncomingForm, File as FormidableFile } from 'formidable' import FormData from 'form-data' import fs from 'fs' -import { Readable } from 'stream' +import https from 'https' +import http from 'http' +import { URL } from 'url' const MAX_FILE_SIZE = 50 * 1024 * 1024 // 50MB @@ -45,7 +47,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }) }) - const { fields, files } = parseResult + const { files } = parseResult // Get the file from the parsed form const fileField = files.file?.[0] @@ -61,25 +63,60 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) contentType: fileField.mimetype || 'application/octet-stream', }) - // Forward to target endpoint - let response: Response + // Forward to target endpoint using https/http native modules + const targetUrl = new URL(targetEndpoint) + const isHttps = targetUrl.protocol === 'https:' + const clientModule = isHttps ? https : http + + let response: { statusCode: number; statusMessage: string; body: string } try { - response = await fetch(targetEndpoint, { - method: 'POST', - body: formData as unknown as BodyInit, - headers: { - ...formData.getHeaders(), - }, + response = await new Promise<{ statusCode: number; statusMessage: string; body: string }>((resolve, reject) => { + const headers = formData.getHeaders() + const requestOptions = { + hostname: targetUrl.hostname, + port: targetUrl.port || (isHttps ? 443 : 80), + path: targetUrl.pathname + targetUrl.search, + method: 'POST', + headers: headers, + } + + const proxyRequest = clientModule.request(requestOptions, (proxyResponse) => { + let body = '' + proxyResponse.setEncoding('utf8') + proxyResponse.on('data', (chunk) => { + body += chunk + }) + proxyResponse.on('end', () => { + resolve({ + statusCode: proxyResponse.statusCode || 500, + statusMessage: proxyResponse.statusMessage || 'Internal Server Error', + body: body, + }) + }) + proxyResponse.on('error', (error) => { + reject(error) + }) + }) + + proxyRequest.on('error', (error) => { + reject(error) + }) + + formData.on('error', (error) => { + reject(error) + }) + + formData.pipe(proxyRequest) }) - } catch (fetchError) { + } catch (requestError) { // Clean up temporary file before returning error try { fs.unlinkSync(fileField.filepath) } catch (unlinkError) { console.error('Error deleting temp file:', unlinkError) } - const errorMessage = fetchError instanceof Error ? fetchError.message : 'Unknown fetch error' - console.error('NIP-95 proxy fetch error:', { + const errorMessage = requestError instanceof Error ? requestError.message : 'Unknown request error' + console.error('NIP-95 proxy request error:', { targetEndpoint, error: errorMessage, fileSize: fileField.size, @@ -97,20 +134,34 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) console.error('Error deleting temp file:', unlinkError) } - if (!response.ok) { - const errorText = await response.text() + if (response.statusCode < 200 || response.statusCode >= 300) { + const errorText = response.body.substring(0, 200) // Limit log size console.error('NIP-95 proxy response error:', { targetEndpoint, - status: response.status, - statusText: response.statusText, - errorText: errorText.substring(0, 200), // Limit log size + status: response.statusCode, + statusText: response.statusMessage, + errorText: errorText, }) - return res.status(response.status).json({ - error: errorText || `Upload failed: ${response.status} ${response.statusText}`, + return res.status(response.statusCode).json({ + error: errorText || `Upload failed: ${response.statusCode} ${response.statusMessage}`, + }) + } + + let result: unknown + try { + result = JSON.parse(response.body) + } catch (parseError) { + const errorMessage = parseError instanceof Error ? parseError.message : 'Invalid JSON response' + console.error('NIP-95 proxy JSON parse error:', { + targetEndpoint, + error: errorMessage, + bodyPreview: response.body.substring(0, 100), + }) + return res.status(500).json({ + error: `Invalid upload response: ${errorMessage}`, }) } - const result = await response.json() return res.status(200).json(result) } catch (error) { console.error('NIP-95 proxy error:', error) diff --git a/pages/docs.tsx b/pages/docs.tsx index c65d341..9cb8409 100644 --- a/pages/docs.tsx +++ b/pages/docs.tsx @@ -46,7 +46,7 @@ export default function DocsPage() {