From a991d186856ba09d0b6f49cec2ce6357c49a9320 Mon Sep 17 00:00:00 2001 From: MattLeo Date: Tue, 9 Dec 2025 08:17:44 -0600 Subject: [PATCH] prepped files for container deployment --- client/kb-frontend/src/App.jsx | 12 +++--- .../kb-frontend/src/components/AdminPanel.jsx | 6 +-- .../src/components/ArticleEdit.jsx | 12 +++--- .../kb-frontend/src/components/DraftsList.jsx | 4 +- .../kb-frontend/src/components/FilterBar.jsx | 4 +- client/kb-frontend/src/components/Login.jsx | 4 +- .../src/components/MediaGallery.jsx | 6 +-- .../src/components/Registration.jsx | 2 +- docker-compose.yml | 39 +++++++++++++++++++ dockerfile | 23 +++++++++++ server/auth.js | 2 +- server/server.js | 16 +++++++- 12 files changed, 102 insertions(+), 28 deletions(-) create mode 100644 docker-compose.yml create mode 100644 dockerfile diff --git a/client/kb-frontend/src/App.jsx b/client/kb-frontend/src/App.jsx index a11d4e6..26b7fd4 100644 --- a/client/kb-frontend/src/App.jsx +++ b/client/kb-frontend/src/App.jsx @@ -65,7 +65,7 @@ function App() { }; const fetchArticles = () => { - fetch('http://localhost:9000/api/articles', { + fetch('/api/articles', { headers: { 'Authorization': `Bearer ${token}` }, @@ -97,7 +97,7 @@ function App() { fetchArticles(); } - fetch(`http://localhost:9000/api/search/advanced?${params.toString()}`, { + fetch(`/api/search/advanced?${params.toString()}`, { headers: { 'Authorization': `Bearer ${token}` } @@ -130,7 +130,7 @@ function App() { const isNew = !updatedArticle.ka_number || updatedArticle.ka_number === 'New Article'; if (isNew) { - fetch('http://localhost:9000/api/articles', { + fetch('/api/articles', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -146,7 +146,7 @@ function App() { }) .catch(error => console.error('Error creating article:', error)); } else { - fetch(`http://localhost:9000/api/articles/${updatedArticle.ka_number}`, { + fetch(`/api/articles/${updatedArticle.ka_number}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', @@ -176,7 +176,7 @@ function App() { const handleCreateNew = async () => { try { - const response = await fetch('http://localhost:9000/api/articles', { + const response = await fetch('/api/articles', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, @@ -205,7 +205,7 @@ function App() { const handleDelete = () => { if (confirm('Are you sure you want to delete this article?')) { - fetch(`http://localhost:9000/api/articles/${selectedArticle.ka_number}`, { + fetch(`/api/articles/${selectedArticle.ka_number}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }) diff --git a/client/kb-frontend/src/components/AdminPanel.jsx b/client/kb-frontend/src/components/AdminPanel.jsx index f10916b..3ba30cb 100644 --- a/client/kb-frontend/src/components/AdminPanel.jsx +++ b/client/kb-frontend/src/components/AdminPanel.jsx @@ -14,7 +14,7 @@ function AdminPanel({ token, onBack }) { const fetchUsers = async () => { try { - const response = await fetch('http://localhost:9000/api/admin/users', { + const response = await fetch('/api/admin/users', { headers: { 'Authorization': `Bearer ${token}` } @@ -41,7 +41,7 @@ function AdminPanel({ token, onBack }) { const handleSaveRole = async (userId) => { try { - const response = await fetch(`http://localhost:9000/api/admin/users/${userId}/role`, { + const response = await fetch(`/api/admin/users/${userId}/role`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, @@ -76,7 +76,7 @@ function AdminPanel({ token, onBack }) { } try { - const response = await fetch(`http://localhost:9000/api/admin/users/${userId}`, { + const response = await fetch(`/api/admin/users/${userId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` diff --git a/client/kb-frontend/src/components/ArticleEdit.jsx b/client/kb-frontend/src/components/ArticleEdit.jsx index e98243a..d3f40c6 100644 --- a/client/kb-frontend/src/components/ArticleEdit.jsx +++ b/client/kb-frontend/src/components/ArticleEdit.jsx @@ -125,7 +125,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) { const fetchCategories = async () => { try { - const response = await fetch('http://localhost:9000/api/categories', { + const response = await fetch('/api/categories', { headers: { 'Authorization': `Bearer ${token}` } @@ -142,7 +142,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) { const fetchTags = async () => { try { - const response = await fetch('http://localhost:9000/api/tags', { + const response = await fetch('/api/tags', { headers: { 'Authorization': `Bearer ${token}` } @@ -171,7 +171,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) { onSave(articleData); try { - await fetch(`http://localhost:9000/api/articles/${article.ka_number}/categories`, { + await fetch(`/api/articles/${article.ka_number}/categories`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, @@ -180,7 +180,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) { body: JSON.stringify({ categories: selectedCategories }) }); - await fetch(`http://localhost:9000/api/articles/${article.ka_number}/tags`, { + await fetch(`/api/articles/${article.ka_number}/tags`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, @@ -222,7 +222,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) { if (!quill) return; const range = quill.getSelection(true); - const mediaUrl = `http://localhost:9000${mediaItem.url}`; + const mediaUrl = `/${mediaItem.url}`; if (mediaItem.type === 'image') { quill.insertEmbed(range.index, 'image', mediaUrl); @@ -241,7 +241,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) { if (!newCategoryInput.trim()) return; try { - const response = await fetch('http://localhost:9000/api/categories', { + const response = await fetch('/api/categories', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, diff --git a/client/kb-frontend/src/components/DraftsList.jsx b/client/kb-frontend/src/components/DraftsList.jsx index 9ac8768..824f588 100644 --- a/client/kb-frontend/src/components/DraftsList.jsx +++ b/client/kb-frontend/src/components/DraftsList.jsx @@ -12,7 +12,7 @@ function DraftsList({ token, onBack, onSelectDraft }) { const fetchDrafts = async () => { try { - const response = await fetch('http://localhost:9000/api/articles/drafts', { + const response = await fetch('\/api/articles/drafts', { headers: { 'Authorization': `Bearer ${token}` } @@ -38,7 +38,7 @@ function DraftsList({ token, onBack, onSelectDraft }) { if(!confirm('Are you sure you want to delete this draft?')) return; try { - const response = await fetch(`http://localhost:9000/api/articles/${kaNumber}`, { + const response = await fetch(`/api/articles/${kaNumber}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` diff --git a/client/kb-frontend/src/components/FilterBar.jsx b/client/kb-frontend/src/components/FilterBar.jsx index 5905eae..19f6c18 100644 --- a/client/kb-frontend/src/components/FilterBar.jsx +++ b/client/kb-frontend/src/components/FilterBar.jsx @@ -30,7 +30,7 @@ function FilterBar({ token, onFilterChange }) { const fetchCategories = async () => { try { - const response = await fetch('http://localhost:9000/api/categories', { + const response = await fetch('/api/categories', { headers: { 'Authorization': `Bearer ${token}` } @@ -47,7 +47,7 @@ function FilterBar({ token, onFilterChange }) { const fetchTags = async () => { try { - const response = await fetch('http://localhost:9000/api/tags', { + const response = await fetch('/api/tags', { headers: { 'Authorization': `Bearer ${token}` } diff --git a/client/kb-frontend/src/components/Login.jsx b/client/kb-frontend/src/components/Login.jsx index 37e4fd2..39d0200 100644 --- a/client/kb-frontend/src/components/Login.jsx +++ b/client/kb-frontend/src/components/Login.jsx @@ -35,7 +35,7 @@ function Login({ onLoginSuccess, onSwitchToRegister }) { setLoading(true); try { - const response = await fetch('http://localhost:9000/api/auth/login', { + const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -76,7 +76,7 @@ function Login({ onLoginSuccess, onSwitchToRegister }) { const loginResponse = await msalInstance.loginPopup(loginRequest); // Send the access token to your backend - const response = await fetch('http://localhost:9000/api/auth/microsoft', { + const response = await fetch('/api/auth/microsoft', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/client/kb-frontend/src/components/MediaGallery.jsx b/client/kb-frontend/src/components/MediaGallery.jsx index 4d70c3f..a7ae7f2 100644 --- a/client/kb-frontend/src/components/MediaGallery.jsx +++ b/client/kb-frontend/src/components/MediaGallery.jsx @@ -44,7 +44,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) { formData.append('file', file); try { - const response = await fetch(`http://localhost:9000/api/articles/${kaNumber}/media`, { + const response = await fetch(`/api/articles/${kaNumber}/media`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` @@ -72,7 +72,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) { if (!confirm('Are you sure you want to delete this file?')) return; try { - const response = await fetch(`http://localhost:9000/api/articles/${kaNumber}/media/${filename}`, { + const response = await fetch(`/api/articles/${kaNumber}/media/${filename}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` @@ -139,7 +139,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
{item.type === 'image' ? ( {item.filename} onInsertMedia(item)} /> diff --git a/client/kb-frontend/src/components/Registration.jsx b/client/kb-frontend/src/components/Registration.jsx index 0a7f284..b233372 100644 --- a/client/kb-frontend/src/components/Registration.jsx +++ b/client/kb-frontend/src/components/Registration.jsx @@ -27,7 +27,7 @@ function Registration({ onRegistrationSuccess, onSwitchToLogin }) { setLoading(true); try { - const response = await fetch('http://localhost:9000/api/auth/register', { + const response = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..786ee23 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +version: '3.8' + +services: + knowledge-base: + build: + context: . + dockerfile: Dockerfile + container_name: knowledge-base + restart: unless-stopped + environment: + - NODE_ENV=production + - PORT=9000 + - JWT_SECRET=${JWT_SECRET} + - MICROSOFT_CLIENT_ID=${MICROSOFT_CLIENT_ID} + - MICROSOFT_CLIENT_SECRET=${MICROSOFT_CLIENT_SECRET} + - MICROSOFT_TENANT_ID=${MICROSOFT_TENANT_ID} + volumes: + - kb-data:/app/kb.db + - kb-media:/app/media + networks: + - traefik + labels: + # Traefik routing + - "traefik.enable=true" + - "traefik.http.routers.kb.rule=Host(`kb.jv.com`)" + - "traefik.http.routers.kb.entrypoints=websecure" + - "traefik.http.routers.kb.tls=true" + - "traefik.http.services.kb.loadbalancer.server.port=9000" + - "traefik.docker.network=traefik_traefik-proxy" + +networks: + traefik: + external: true + +volumes: + kb-data: + driver: local + kb-media: + driver: local \ No newline at end of file diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..9ad1ad9 --- /dev/null +++ b/dockerfile @@ -0,0 +1,23 @@ +# Combined Frontend + Backend Dockerfile +# Multi-stage build + +FROM node:20-slim AS frontend-build +WORKDIR /app/frontend +COPY client/kb-frontend/package*.json ./ +RUN npm ci +COPY client/kb-frontend ./ +RUN npm run build +FROM node:20-slim +RUN apt-get update && apt-get install -y \ + python3 \ + make \ + g++ \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY server/package*.json ./ +RUN npm ci --only=production +COPY server/ ./ +COPY --from=frontend-build /app/frontend/dist ./frontend/dist +RUN mkdir -p /app/media +EXPOSE 9000 +CMD ["node", "server.js"] \ No newline at end of file diff --git a/server/auth.js b/server/auth.js index 0d9abd7..40ee003 100644 --- a/server/auth.js +++ b/server/auth.js @@ -1,7 +1,7 @@ const jwt = require('jsonwebtoken'); const {getUserById} = require('./db'); -const JWT_SECRET = 'THISISTOTALLYSECRETDONTLOOK'; +const JWT_SECRET = 'T0JcuWfi3A2GdRd71FnBmH2c16guYVT/d3Jo1I9hdcs='; const JWT_EXPIRATION = '24h'; diff --git a/server/server.js b/server/server.js index 82866f7..4e5e0d0 100644 --- a/server/server.js +++ b/server/server.js @@ -39,7 +39,7 @@ const PORT = 9000; app.use(cors()) app.use(express.json()); -const msalClient = new msal.ConfidentialClientApplication(entraConfig); + const upload = multer({ dest: './temp_uploads/', limits: { @@ -56,6 +56,12 @@ if (!fs.existsSync('./media')) { initDb().then(() => { + app.use('/api/auth', authRoutes); + app.use('/api/articles', articleRoutes); + app.use('/api/admin', adminRoutes); + app.use('/api/categories', categoryRoutes); + app.use('/api/tags', tagRoutes); + app.get('/', (req, res) => { res.json({ message: 'Server is running' }); }); @@ -666,9 +672,15 @@ initDb().then(() => { } }); + app.use(express.static(path.join(__dirname, 'frontend/dist'))); + app.use('/media', express.static(path.join(__dirname, 'media'))); + app.get('*', (req, res) => { + res.sendFile(path.join(__dirname, 'frontend/dist', 'index.html')); + app.listen(PORT, () => { - console.log(`Server running on http://localhost:${PORT}`); + console.log(`Server listening on ${PORT}`); }); +}); }).catch(err => { console.error('Failed to initialize database:', err); }); \ No newline at end of file