prepped files for container deployment

This commit is contained in:
MattLeo 2025-12-09 08:17:44 -06:00
parent f90435399d
commit a991d18685
12 changed files with 102 additions and 28 deletions

View File

@ -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}` }
})

View File

@ -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}`

View File

@ -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}`,

View File

@ -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}`

View File

@ -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}`
}

View File

@ -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'

View File

@ -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 }) {
<div key={item.filename} className='media-item'>
{item.type === 'image' ? (
<img
src={`http://localhost:9000${item.url}`}
src={`${item.url}`}
alt={item.filename}
onClick={() => onInsertMedia(item)}
/>

View File

@ -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'

39
docker-compose.yml Normal file
View File

@ -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

23
dockerfile Normal file
View File

@ -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"]

View File

@ -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';

View File

@ -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);
});