prepped files for container deployment
This commit is contained in:
parent
f90435399d
commit
a991d18685
@ -65,7 +65,7 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchArticles = () => {
|
const fetchArticles = () => {
|
||||||
fetch('http://localhost:9000/api/articles', {
|
fetch('/api/articles', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
},
|
},
|
||||||
@ -97,7 +97,7 @@ function App() {
|
|||||||
fetchArticles();
|
fetchArticles();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`http://localhost:9000/api/search/advanced?${params.toString()}`, {
|
fetch(`/api/search/advanced?${params.toString()}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ function App() {
|
|||||||
const isNew = !updatedArticle.ka_number || updatedArticle.ka_number === 'New Article';
|
const isNew = !updatedArticle.ka_number || updatedArticle.ka_number === 'New Article';
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
fetch('http://localhost:9000/api/articles', {
|
fetch('/api/articles', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -146,7 +146,7 @@ function App() {
|
|||||||
})
|
})
|
||||||
.catch(error => console.error('Error creating article:', error));
|
.catch(error => console.error('Error creating article:', error));
|
||||||
} else {
|
} else {
|
||||||
fetch(`http://localhost:9000/api/articles/${updatedArticle.ka_number}`, {
|
fetch(`/api/articles/${updatedArticle.ka_number}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -176,7 +176,7 @@ function App() {
|
|||||||
|
|
||||||
const handleCreateNew = async () => {
|
const handleCreateNew = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/articles', {
|
const response = await fetch('/api/articles', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@ -205,7 +205,7 @@ function App() {
|
|||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (confirm('Are you sure you want to delete this article?')) {
|
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',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
})
|
})
|
||||||
|
|||||||
@ -14,7 +14,7 @@ function AdminPanel({ token, onBack }) {
|
|||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/admin/users', {
|
const response = await fetch('/api/admin/users', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ function AdminPanel({ token, onBack }) {
|
|||||||
|
|
||||||
const handleSaveRole = async (userId) => {
|
const handleSaveRole = async (userId) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:9000/api/admin/users/${userId}/role`, {
|
const response = await fetch(`/api/admin/users/${userId}/role`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@ -76,7 +76,7 @@ function AdminPanel({ token, onBack }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:9000/api/admin/users/${userId}`, {
|
const response = await fetch(`/api/admin/users/${userId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
|
|||||||
@ -125,7 +125,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
|||||||
|
|
||||||
const fetchCategories = async () => {
|
const fetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/categories', {
|
const response = await fetch('/api/categories', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@ -142,7 +142,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
|||||||
|
|
||||||
const fetchTags = async () => {
|
const fetchTags = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/tags', {
|
const response = await fetch('/api/tags', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
|||||||
onSave(articleData);
|
onSave(articleData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch(`http://localhost:9000/api/articles/${article.ka_number}/categories`, {
|
await fetch(`/api/articles/${article.ka_number}/categories`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@ -180,7 +180,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
|||||||
body: JSON.stringify({ categories: selectedCategories })
|
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',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@ -222,7 +222,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
|||||||
if (!quill) return;
|
if (!quill) return;
|
||||||
|
|
||||||
const range = quill.getSelection(true);
|
const range = quill.getSelection(true);
|
||||||
const mediaUrl = `http://localhost:9000${mediaItem.url}`;
|
const mediaUrl = `/${mediaItem.url}`;
|
||||||
|
|
||||||
if (mediaItem.type === 'image') {
|
if (mediaItem.type === 'image') {
|
||||||
quill.insertEmbed(range.index, 'image', mediaUrl);
|
quill.insertEmbed(range.index, 'image', mediaUrl);
|
||||||
@ -241,7 +241,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
|||||||
if (!newCategoryInput.trim()) return;
|
if (!newCategoryInput.trim()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/categories', {
|
const response = await fetch('/api/categories', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ function DraftsList({ token, onBack, onSelectDraft }) {
|
|||||||
|
|
||||||
const fetchDrafts = async () => {
|
const fetchDrafts = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/articles/drafts', {
|
const response = await fetch('\/api/articles/drafts', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ function DraftsList({ token, onBack, onSelectDraft }) {
|
|||||||
if(!confirm('Are you sure you want to delete this draft?')) return;
|
if(!confirm('Are you sure you want to delete this draft?')) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:9000/api/articles/${kaNumber}`, {
|
const response = await fetch(`/api/articles/${kaNumber}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
|
|||||||
@ -30,7 +30,7 @@ function FilterBar({ token, onFilterChange }) {
|
|||||||
|
|
||||||
const fetchCategories = async () => {
|
const fetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/categories', {
|
const response = await fetch('/api/categories', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ function FilterBar({ token, onFilterChange }) {
|
|||||||
|
|
||||||
const fetchTags = async () => {
|
const fetchTags = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/tags', {
|
const response = await fetch('/api/tags', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ function Login({ onLoginSuccess, onSwitchToRegister }) {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/auth/login', {
|
const response = await fetch('/api/auth/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -76,7 +76,7 @@ function Login({ onLoginSuccess, onSwitchToRegister }) {
|
|||||||
const loginResponse = await msalInstance.loginPopup(loginRequest);
|
const loginResponse = await msalInstance.loginPopup(loginRequest);
|
||||||
|
|
||||||
// Send the access token to your backend
|
// 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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
|||||||
@ -44,7 +44,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
|
|||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:9000/api/articles/${kaNumber}/media`, {
|
const response = await fetch(`/api/articles/${kaNumber}/media`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'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;
|
if (!confirm('Are you sure you want to delete this file?')) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:9000/api/articles/${kaNumber}/media/${filename}`, {
|
const response = await fetch(`/api/articles/${kaNumber}/media/${filename}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`
|
||||||
@ -139,7 +139,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
|
|||||||
<div key={item.filename} className='media-item'>
|
<div key={item.filename} className='media-item'>
|
||||||
{item.type === 'image' ? (
|
{item.type === 'image' ? (
|
||||||
<img
|
<img
|
||||||
src={`http://localhost:9000${item.url}`}
|
src={`${item.url}`}
|
||||||
alt={item.filename}
|
alt={item.filename}
|
||||||
onClick={() => onInsertMedia(item)}
|
onClick={() => onInsertMedia(item)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -27,7 +27,7 @@ function Registration({ onRegistrationSuccess, onSwitchToLogin }) {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:9000/api/auth/register', {
|
const response = await fetch('/api/auth/register', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
|||||||
39
docker-compose.yml
Normal file
39
docker-compose.yml
Normal 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
23
dockerfile
Normal 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"]
|
||||||
@ -1,7 +1,7 @@
|
|||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const {getUserById} = require('./db');
|
const {getUserById} = require('./db');
|
||||||
|
|
||||||
const JWT_SECRET = 'THISISTOTALLYSECRETDONTLOOK';
|
const JWT_SECRET = 'T0JcuWfi3A2GdRd71FnBmH2c16guYVT/d3Jo1I9hdcs=';
|
||||||
const JWT_EXPIRATION = '24h';
|
const JWT_EXPIRATION = '24h';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const PORT = 9000;
|
|||||||
app.use(cors())
|
app.use(cors())
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
const msalClient = new msal.ConfidentialClientApplication(entraConfig);
|
|
||||||
const upload = multer({
|
const upload = multer({
|
||||||
dest: './temp_uploads/',
|
dest: './temp_uploads/',
|
||||||
limits: {
|
limits: {
|
||||||
@ -56,6 +56,12 @@ if (!fs.existsSync('./media')) {
|
|||||||
|
|
||||||
initDb().then(() => {
|
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) => {
|
app.get('/', (req, res) => {
|
||||||
res.json({ message: 'Server is running' });
|
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, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server running on http://localhost:${PORT}`);
|
console.log(`Server listening on ${PORT}`);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('Failed to initialize database:', err);
|
console.error('Failed to initialize database:', err);
|
||||||
});
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user