diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2b48345 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +yarn.lock +package-lock.json + +# Build output +dist +build + +# Environment +.env +.env.* + +# Database (sera créée dans le volume Docker) +data + +# Uploads (seront créés dans le volume Docker) +public/uploads + +# Logs +*.log +logs + +# Git +.git +.gitignore +.gitattributes + +# IDE +.vscode +.idea +*.swp +*.swo +*.swn + +# OS files +.DS_Store +Thumbs.db + +# Docker +Dockerfile* +docker-compose*.yml +.dockerignore + +# Misc +README.md +.editorconfig +.prettierrc diff --git a/Dockerfile b/Dockerfile index 1d682a9..e9f4372 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,29 @@ # Image de base Node.js 20 Alpine pour optimiser la taille +FROM node:20-alpine AS builder + +# Définir le répertoire de travail +WORKDIR /app + +# Copier les fichiers de dépendances +COPY package*.json ./ +COPY tsconfig*.json ./ +COPY vite.config.ts ./ +COPY components.json ./ +COPY postcss.config.js ./ +COPY tailwind.config.* ./ +COPY eslint.config.js ./ + +# Installer TOUTES les dépendances (dev + prod) +RUN npm ci + +# Copier le code source +COPY index.html ./ +COPY src ./src + +# Construire l'application React +RUN npm run build + +# ========== Image de production ========== FROM node:20-alpine # Définir le répertoire de travail @@ -10,11 +35,19 @@ RUN apk add --no-cache sqlite # Copier les fichiers de dépendances COPY package*.json ./ -# Installer les dépendances de production uniquement +# Installer uniquement les dépendances de production pour le serveur RUN npm ci --only=production -# Copier le code source -COPY . . +# Copier le code du serveur +COPY server.js ./ +COPY config ./config +COPY routes ./routes +COPY middleware ./middleware +COPY services ./services +COPY migrations ./migrations + +# Copier le build de l'application React depuis le builder +COPY --from=builder /app/dist ./dist # Créer les dossiers nécessaires et définir les permissions RUN mkdir -p /app/data /app/public/uploads && \ @@ -31,4 +64,4 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD node -e "require('http').get('http://localhost:2222/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))" # Démarrer l'application -CMD ["node", "server.js"] \ No newline at end of file +CMD ["node", "server.js"] diff --git a/package.json b/package.json index db40eec..6fffe81 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,11 @@ "description": "Application web de notes et todo list moderne avec Docker", "main": "server.js", "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", "start": "node server.js", - "dev": "nodemon server.js", + "server:dev": "nodemon server.js", "test": "echo \"Error: no test specified\" && exit 1", "docker:build": "docker-compose build", "docker:up": "docker-compose up -d", @@ -18,26 +21,92 @@ "todo", "docker", "express", - "sqlite" + "sqlite", + "react", + "typescript" ], "author": "", "license": "MIT", "dependencies": { + "@dyad-sh/react-vite-component-tagger": "^0.0.6", + "@radix-ui/react-accordion": "^1.2.2", + "@radix-ui/react-alert-dialog": "^1.1.4", + "@radix-ui/react-aspect-ratio": "^1.1.1", + "@radix-ui/react-avatar": "^1.1.2", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-collapsible": "^1.1.2", + "@radix-ui/react-context-menu": "^2.2.4", + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-hover-card": "^1.1.4", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-menubar": "^1.1.4", + "@radix-ui/react-navigation-menu": "^1.2.2", + "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-progress": "^1.1.1", + "@radix-ui/react-radio-group": "^1.2.2", + "@radix-ui/react-scroll-area": "^1.2.2", + "@radix-ui/react-select": "^2.1.4", + "@radix-ui/react-separator": "^1.1.1", + "@radix-ui/react-slider": "^1.2.2", + "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.2", + "@radix-ui/react-toast": "^1.2.4", + "@radix-ui/react-toggle": "^1.1.1", + "@radix-ui/react-toggle-group": "^1.1.1", + "@radix-ui/react-tooltip": "^1.1.6", + "@tanstack/react-query": "^5.62.18", "axios": "^1.13.2", "bcrypt": "^5.1.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.0.4", "cors": "^2.8.5", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.5.2", "express": "^4.18.2", "express-rate-limit": "^7.1.5", "express-validator": "^7.0.1", "helmet": "^7.1.0", + "input-otp": "^1.4.1", "jsonwebtoken": "^9.0.2", + "lucide-react": "^0.469.0", "multer": "^1.4.5-lts.1", + "next-themes": "^0.4.4", + "react": "^19.0.0", + "react-day-picker": "^8.10.1", + "react-dom": "^19.0.0", + "react-hook-form": "^7.54.2", + "react-resizable-panels": "^2.1.8", + "react-router-dom": "^7.1.3", + "recharts": "^2.15.0", "rss-parser": "^3.13.0", + "sonner": "^1.7.2", "sqlite3": "^5.1.7", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "vaul": "^1.1.1", "winston": "^3.11.0" }, "devDependencies": { - "nodemon": "^3.0.2" + "@eslint/js": "^9.18.0", + "@types/node": "^22.10.5", + "@types/react": "^19.0.6", + "@types/react-dom": "^19.0.2", + "@vitejs/plugin-react": "^4.3.4", + "@vitejs/plugin-react-swc": "^3.7.2", + "autoprefixer": "^10.4.20", + "eslint": "^9.18.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "nodemon": "^3.0.2", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "typescript": "~5.7.2", + "typescript-eslint": "^8.20.0", + "vite": "^6.0.7" }, "engines": { "node": ">=20.0.0" diff --git a/server.js b/server.js index ea40c01..905906b 100644 --- a/server.js +++ b/server.js @@ -65,8 +65,10 @@ const authLimiter = rateLimit({ app.use('/api/auth', authLimiter); // Servir les fichiers statiques -app.use(express.static(path.join(__dirname, 'public'))); +// Servir les uploads depuis public/uploads app.use('/uploads', express.static(uploadsDir)); +// Servir l'application React depuis dist/ +app.use(express.static(path.join(__dirname, 'dist'))); // Healthcheck endpoint pour Docker app.get('/health', (req, res) => { @@ -94,7 +96,7 @@ app.get('*', (req, res) => { if (req.path.startsWith('/api/')) { return res.status(404).json({ error: 'Endpoint non trouvé' }); } - res.sendFile(path.join(__dirname, 'public', 'index.html')); + res.sendFile(path.join(__dirname, 'dist', 'index.html')); }); // Gestionnaire d'erreurs global diff --git a/vite.config.ts b/vite.config.ts index 970f7f8..ce46bfd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,10 +8,14 @@ export default defineConfig(() => ({ host: "::", port: 2222, }, + build: { + outDir: "dist", + emptyOutDir: true, + }, plugins: [dyadComponentTagger(), react()], resolve: { alias: { "@": path.resolve(__dirname, "./src"), }, }, -})); \ No newline at end of file +}));