Compare commits
9 Commits
f0b61735cc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e8bb4e064 | ||
|
|
1ca2575612 | ||
|
|
7582813f20 | ||
|
|
9f53a34cfb | ||
|
|
36b62be2e1 | ||
|
|
c898ca4a65 | ||
|
|
992ccf056b | ||
|
|
282b7f7877 | ||
|
|
9062a8d7e0 |
63
.dockerignore
Normal file
63
.dockerignore
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
frontend/dist/
|
||||||
|
frontend/build/
|
||||||
|
backend/dist/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Database files (will be created in container)
|
||||||
|
backend/database/*.db
|
||||||
|
backend/database/*.db-journal
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
tests/
|
||||||
|
*.test.js
|
||||||
|
*.spec.js
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
*.md
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# Deployment scripts
|
||||||
|
deploy.sh
|
||||||
|
systemd/
|
||||||
|
nginx/
|
||||||
|
|
||||||
|
# Docker files (don't copy into itself)
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
docker-compose.yml
|
||||||
|
|
||||||
|
# Backups
|
||||||
|
backups/
|
||||||
|
|
||||||
203
DOCKER.md
Normal file
203
DOCKER.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# Docker Deployment Guide
|
||||||
|
|
||||||
|
Bu dokümantasyon, Oltalama uygulamasını Docker ile tek container'da çalıştırma rehberidir.
|
||||||
|
|
||||||
|
## Gereksinimler
|
||||||
|
|
||||||
|
- Docker 20.10+
|
||||||
|
- Docker Compose 2.0+ (opsiyonel)
|
||||||
|
|
||||||
|
## Hızlı Başlangıç
|
||||||
|
|
||||||
|
### Docker Compose ile (Önerilen)
|
||||||
|
|
||||||
|
1. **Environment dosyası oluştur** (opsiyonel):
|
||||||
|
```bash
|
||||||
|
cp backend/.env.example backend/.env
|
||||||
|
# backend/.env dosyasını düzenle
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Docker Compose ile başlat**:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Logları izle**:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Durdur**:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker ile (Manuel)
|
||||||
|
|
||||||
|
1. **Image'ı build et**:
|
||||||
|
```bash
|
||||||
|
docker build -t oltalama:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Container'ı çalıştır**:
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name oltalama \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-v $(pwd)/data/database:/app/database \
|
||||||
|
-v $(pwd)/data/logs:/app/logs \
|
||||||
|
-e NODE_ENV=production \
|
||||||
|
-e SESSION_SECRET=$(openssl rand -hex 32) \
|
||||||
|
-e FRONTEND_URL=http://localhost:3000 \
|
||||||
|
oltalama:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Aşağıdaki environment variable'ları ayarlayabilirsiniz:
|
||||||
|
|
||||||
|
- `NODE_ENV`: Ortam (production/development)
|
||||||
|
- `PORT`: Server portu (varsayılan: 3000)
|
||||||
|
- `DB_PATH`: Database dosya yolu (varsayılan: /app/database/oltalama.db)
|
||||||
|
- `SESSION_SECRET`: Session secret key (üretmek için: `openssl rand -hex 32`)
|
||||||
|
- `FRONTEND_URL`: Frontend URL'i (CORS için)
|
||||||
|
|
||||||
|
Daha fazla environment variable için `backend/.env.example` dosyasına bakın.
|
||||||
|
|
||||||
|
## Veri Kalıcılığı
|
||||||
|
|
||||||
|
Database ve log dosyaları volume'lerde saklanır:
|
||||||
|
|
||||||
|
- `./data/database`: SQLite database dosyaları
|
||||||
|
- `./data/logs`: Uygulama logları
|
||||||
|
|
||||||
|
İlk çalıştırmada bu dizinler otomatik oluşturulur.
|
||||||
|
|
||||||
|
## Admin Kullanıcı Oluşturma
|
||||||
|
|
||||||
|
İlk kurulumda admin kullanıcı oluşturmak için:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Container içine gir
|
||||||
|
docker exec -it oltalama sh
|
||||||
|
|
||||||
|
# Admin kullanıcı oluştur
|
||||||
|
node scripts/create-admin.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Veya container dışından:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it oltalama node scripts/create-admin.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health Check
|
||||||
|
|
||||||
|
Container health check endpoint'i: `http://localhost:3000/health`
|
||||||
|
|
||||||
|
Health check durumunu kontrol etmek için:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker ps
|
||||||
|
# HEALTHY durumunu göreceksiniz
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loglar
|
||||||
|
|
||||||
|
Logları görüntülemek için:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker Compose
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker logs -f oltalama
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Backup
|
||||||
|
|
||||||
|
Database'i yedeklemek için:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Container içindeki database'i kopyala
|
||||||
|
docker cp oltalama:/app/database/oltalama.db ./backup-$(date +%Y%m%d).db
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Container başlamıyor
|
||||||
|
|
||||||
|
1. Logları kontrol edin:
|
||||||
|
```bash
|
||||||
|
docker logs oltalama
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Port'un kullanılabilir olduğundan emin olun:
|
||||||
|
```bash
|
||||||
|
netstat -tuln | grep 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database hatası
|
||||||
|
|
||||||
|
1. Database volume'ünün yazılabilir olduğundan emin olun:
|
||||||
|
```bash
|
||||||
|
chmod -R 777 ./data/database
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Container'ı yeniden başlatın:
|
||||||
|
```bash
|
||||||
|
docker-compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend görünmüyor
|
||||||
|
|
||||||
|
1. Frontend build'inin doğru yapıldığından emin olun:
|
||||||
|
```bash
|
||||||
|
docker exec oltalama ls -la /app/src/public/dist
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Container'ı yeniden build edin:
|
||||||
|
```bash
|
||||||
|
docker-compose build --no-cache
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
Production için:
|
||||||
|
|
||||||
|
1. **Güvenli SESSION_SECRET kullanın**:
|
||||||
|
```bash
|
||||||
|
export SESSION_SECRET=$(openssl rand -hex 64)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **HTTPS için reverse proxy kullanın** (Nginx, Traefik, vb.)
|
||||||
|
|
||||||
|
3. **Resource limitleri ayarlayın**:
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml'e ekleyin
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '1'
|
||||||
|
memory: 512M
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Düzenli backup alın**
|
||||||
|
|
||||||
|
## Güncelleme
|
||||||
|
|
||||||
|
Uygulamayı güncellemek için:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Yeni image'ı build et
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Container'ı yeniden başlat
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sorun Bildirimi
|
||||||
|
|
||||||
|
Sorun yaşarsanız, lütfen GitHub Issues'da bildirin.
|
||||||
|
|
||||||
55
Dockerfile
Normal file
55
Dockerfile
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Multi-stage build for Oltalama application
|
||||||
|
# Stage 1: Build frontend
|
||||||
|
FROM node:20-alpine AS frontend-builder
|
||||||
|
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
|
||||||
|
# Copy frontend package files
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
|
||||||
|
# Install frontend dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy frontend source
|
||||||
|
COPY frontend/ ./
|
||||||
|
|
||||||
|
# Build frontend (output will be in dist/)
|
||||||
|
RUN npm run build && test -f dist/index.html
|
||||||
|
|
||||||
|
# Stage 2: Backend + Runtime
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies for sqlite3
|
||||||
|
RUN apk add --no-cache python3 make g++ sqlite
|
||||||
|
|
||||||
|
# Copy backend package files
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
|
||||||
|
# Install backend dependencies (production only)
|
||||||
|
RUN npm install --only=production
|
||||||
|
|
||||||
|
# Copy backend source
|
||||||
|
COPY backend/ ./
|
||||||
|
|
||||||
|
# Copy frontend build from previous stage to backend public directory
|
||||||
|
COPY --from=frontend-builder /app/frontend/dist ./src/public/dist
|
||||||
|
|
||||||
|
# Create database directory
|
||||||
|
RUN mkdir -p database
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||||
|
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||||
|
|
||||||
|
# Start script that runs migrations and seeds before starting the server
|
||||||
|
COPY docker-entrypoint.sh /usr/local/bin/
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
|
CMD ["node", "src/app.js"]
|
||||||
|
|
||||||
16
README.md
16
README.md
@@ -179,6 +179,21 @@ curl http://localhost:3000/api/stats/dashboard
|
|||||||
|
|
||||||
Sistem kullanıma hazır. Gmail ve Telegram ayarlarını yaparak phishing testlerinizi başlatabilirsiniz.
|
Sistem kullanıma hazır. Gmail ve Telegram ayarlarını yaparak phishing testlerinizi başlatabilirsiniz.
|
||||||
|
|
||||||
|
## 🐳 Docker ile Kurulum (Önerilen)
|
||||||
|
|
||||||
|
Tek container'da çalıştırmak için:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker Compose ile (en kolay)
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Veya Docker ile
|
||||||
|
docker build -t oltalama:latest .
|
||||||
|
docker run -d -p 3000:3000 --name oltalama oltalama:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Detaylı Docker dokümantasyonu:** `DOCKER.md`
|
||||||
|
|
||||||
## 🚀 Sunucu Kurulumu (Production)
|
## 🚀 Sunucu Kurulumu (Production)
|
||||||
|
|
||||||
### Otomatik Kurulum
|
### Otomatik Kurulum
|
||||||
@@ -239,6 +254,7 @@ node scripts/change-password.js
|
|||||||
## 📚 Dokümantasyon
|
## 📚 Dokümantasyon
|
||||||
|
|
||||||
- **Ana Doküman:** `README.md` (bu dosya)
|
- **Ana Doküman:** `README.md` (bu dosya)
|
||||||
|
- **Docker Kurulumu:** `DOCKER.md` 🐳 (Docker ile tek container)
|
||||||
- **Sunucu Kurulumu:** `DEPLOYMENT.md` 🚀 (Production kurulum)
|
- **Sunucu Kurulumu:** `DEPLOYMENT.md` 🚀 (Production kurulum)
|
||||||
- **Ollama AI Entegrasyonu:** `OLLAMA_SETUP.md` 🤖 (AI mail şablon oluşturma)
|
- **Ollama AI Entegrasyonu:** `OLLAMA_SETUP.md` 🤖 (AI mail şablon oluşturma)
|
||||||
- **Domain Yapılandırma:** `docs/DOMAIN_SETUP.md` 🌐 (Tek/İki domain)
|
- **Domain Yapılandırma:** `docs/DOMAIN_SETUP.md` 🌐 (Tek/İki domain)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
require('dotenv').config({ path: require('path').join(__dirname, '../.env') });
|
require('dotenv').config({ path: require('path').join(__dirname, '../.env') });
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
const helmet = require('helmet');
|
// const helmet = require('helmet'); // Geçici olarak devre dışı
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const logger = require('./config/logger');
|
const logger = require('./config/logger');
|
||||||
const sessionConfig = require('./config/session');
|
const sessionConfig = require('./config/session');
|
||||||
@@ -12,50 +12,109 @@ const { apiLimiter } = require('./middlewares/rateLimiter');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
// Security middleware
|
// Security middleware - Helmet'i tamamen kaldır (CORS ve mixed content sorunları için)
|
||||||
app.use(helmet());
|
// app.use(helmet()); // Geçici olarak devre dışı
|
||||||
|
|
||||||
// Dynamic CORS configuration (will be updated from settings)
|
|
||||||
let corsOptions = {
|
|
||||||
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
|
|
||||||
credentials: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Basic CSP for SPA and assets to avoid white page due to blocked module scripts
|
||||||
|
// Allow self assets, inline/eval for Vite-built bundles, blobs/data, and API/websocket connections
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
cors(corsOptions)(req, res, next);
|
// Only set CSP for HTML responses later, but keeping a permissive default helps some browsers on preload
|
||||||
|
res.setHeader(
|
||||||
|
'Content-Security-Policy',
|
||||||
|
[
|
||||||
|
"default-src 'self' data: blob:;",
|
||||||
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:;",
|
||||||
|
"style-src 'self' 'unsafe-inline';",
|
||||||
|
"img-src 'self' data: blob:;",
|
||||||
|
"font-src 'self' data:;",
|
||||||
|
"connect-src 'self' http: https: ws: wss:;",
|
||||||
|
"frame-ancestors 'self';",
|
||||||
|
'base-uri \'self\';',
|
||||||
|
].join(' ')
|
||||||
|
);
|
||||||
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Function to update CORS from database
|
// CORS - Her yerden erişime izin ver (tüm route'larda)
|
||||||
const updateCorsFromSettings = async () => {
|
app.use((req, res, next) => {
|
||||||
try {
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
const { Settings } = require('./models');
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
||||||
const corsEnabledSetting = await Settings.findOne({ where: { key: 'cors_enabled' } });
|
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
||||||
const frontendUrlSetting = await Settings.findOne({ where: { key: 'frontend_url' } });
|
res.header('Access-Control-Allow-Credentials', 'true');
|
||||||
|
|
||||||
if (corsEnabledSetting && corsEnabledSetting.value === 'true' && frontendUrlSetting) {
|
// OPTIONS request'i için hemen cevap ver
|
||||||
corsOptions.origin = frontendUrlSetting.value;
|
if (req.method === 'OPTIONS') {
|
||||||
logger.info(`CORS enabled for: ${frontendUrlSetting.value}`);
|
return res.sendStatus(200);
|
||||||
} else {
|
|
||||||
// Default: allow both frontend and backend on same origin
|
|
||||||
corsOptions.origin = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn('Could not load CORS settings from database, using defaults');
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Update CORS on startup (with delay to ensure DB is ready)
|
next();
|
||||||
setTimeout(updateCorsFromSettings, 2000);
|
});
|
||||||
|
|
||||||
// Export for use in settings controller
|
// CORS middleware'i de ekle (çift güvence)
|
||||||
app.updateCorsSettings = updateCorsFromSettings;
|
app.use(cors({
|
||||||
|
origin: '*', // Tüm origin'lere izin ver
|
||||||
|
credentials: true,
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Origin', 'Accept'],
|
||||||
|
}));
|
||||||
|
|
||||||
// Body parsing middleware
|
// Body parsing middleware
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
// Serve static files (landing page)
|
// Serve static files (landing page and frontend build)
|
||||||
app.use(express.static('src/public'));
|
const path = require('path');
|
||||||
|
app.use(express.static(path.join(__dirname, 'public'), {
|
||||||
|
setHeaders: (res, filePath) => {
|
||||||
|
// CORS headers for ALL static files (CSS, JS, images, etc.)
|
||||||
|
res.set('Access-Control-Allow-Origin', '*');
|
||||||
|
res.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||||
|
res.set('Access-Control-Allow-Headers', 'Content-Type, Accept, Origin');
|
||||||
|
res.set('Access-Control-Allow-Credentials', 'true');
|
||||||
|
// Ensure CSP also present on static (mainly harmless for non-HTML)
|
||||||
|
res.set('Content-Security-Policy',
|
||||||
|
"default-src 'self' data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; frame-ancestors 'self'; base-uri 'self';"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Content-Type header'larını doğru ayarla
|
||||||
|
if (filePath.endsWith('.js')) {
|
||||||
|
// Serve ES modules correctly
|
||||||
|
res.set('Content-Type', 'application/javascript; charset=utf-8');
|
||||||
|
} else if (filePath.endsWith('.css')) {
|
||||||
|
res.set('Content-Type', 'text/css; charset=utf-8');
|
||||||
|
} else if (filePath.endsWith('.mjs')) {
|
||||||
|
res.set('Content-Type', 'application/javascript; charset=utf-8');
|
||||||
|
} else if (filePath.endsWith('.svg')) {
|
||||||
|
res.set('Content-Type', 'image/svg+xml');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Serve compiled frontend assets at /assets (Vite output)
|
||||||
|
app.use('/assets', express.static(path.join(__dirname, 'public', 'dist', 'assets'), {
|
||||||
|
setHeaders: (res, filePath) => {
|
||||||
|
res.set('Access-Control-Allow-Origin', '*');
|
||||||
|
res.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||||
|
res.set('Access-Control-Allow-Headers', 'Content-Type, Accept, Origin');
|
||||||
|
res.set('Access-Control-Allow-Credentials', 'true');
|
||||||
|
res.set('Content-Security-Policy',
|
||||||
|
"default-src 'self' data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; frame-ancestors 'self'; base-uri 'self';"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filePath.endsWith('.js') || filePath.endsWith('.mjs')) {
|
||||||
|
res.set('Content-Type', 'application/javascript; charset=utf-8');
|
||||||
|
} else if (filePath.endsWith('.css')) {
|
||||||
|
res.set('Content-Type', 'text/css; charset=utf-8');
|
||||||
|
} else if (filePath.endsWith('.svg')) {
|
||||||
|
res.set('Content-Type', 'image/svg+xml');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Serve landing page at /landing route
|
||||||
|
app.get('/landing', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, 'public', 'landing.html'));
|
||||||
|
});
|
||||||
|
|
||||||
// Session middleware
|
// Session middleware
|
||||||
app.use(session(sessionConfig));
|
app.use(session(sessionConfig));
|
||||||
@@ -93,6 +152,35 @@ app.use('/api/stats', require('./routes/stats.routes'));
|
|||||||
// Public tracking route (no rate limit on this specific route)
|
// Public tracking route (no rate limit on this specific route)
|
||||||
app.use('/t', require('./routes/tracking.routes'));
|
app.use('/t', require('./routes/tracking.routes'));
|
||||||
|
|
||||||
|
// Serve frontend SPA (must be after API routes)
|
||||||
|
app.get('*', (req, res, next) => {
|
||||||
|
// Skip if it's an API route, tracking route, or landing page
|
||||||
|
if (req.path.startsWith('/api') || req.path.startsWith('/t') || req.path.startsWith('/landing') || req.path.startsWith('/health')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS headers for HTML
|
||||||
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
|
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||||
|
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
||||||
|
// Explicit CSP for HTML documents
|
||||||
|
res.setHeader(
|
||||||
|
'Content-Security-Policy',
|
||||||
|
"default-src 'self' data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; frame-ancestors 'self'; base-uri 'self';"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Serve frontend index.html for all other routes
|
||||||
|
const frontendPath = path.join(__dirname, 'public', 'dist', 'index.html');
|
||||||
|
res.sendFile(frontendPath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
// If frontend build doesn't exist, inform clearly
|
||||||
|
res.status(503).send(
|
||||||
|
'Frontend build not found. Please build the frontend (npm run build) and ensure dist is copied to backend/src/public/dist.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
res.status(404).json({
|
res.status(404).json({
|
||||||
@@ -110,14 +198,15 @@ const startServer = async () => {
|
|||||||
// Test database connection
|
// Test database connection
|
||||||
await testConnection();
|
await testConnection();
|
||||||
|
|
||||||
// Start listening
|
// Start listening on all interfaces (0.0.0.0) to allow remote access
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, '0.0.0.0', () => {
|
||||||
logger.info(`🚀 Server is running on port ${PORT}`);
|
logger.info(`🚀 Server is running on port ${PORT}`);
|
||||||
logger.info(`📊 Environment: ${process.env.NODE_ENV || 'development'}`);
|
logger.info(`📊 Environment: ${process.env.NODE_ENV || 'development'}`);
|
||||||
logger.info(`🔗 Health check: http://localhost:${PORT}/health`);
|
logger.info(`🔗 Health check: http://0.0.0.0:${PORT}/health`);
|
||||||
console.log(`\n✨ Oltalama Backend Server Started!`);
|
console.log(`\n✨ Oltalama Backend Server Started!`);
|
||||||
console.log(`🌐 API: http://localhost:${PORT}/api`);
|
console.log(`🌐 API: http://0.0.0.0:${PORT}/api`);
|
||||||
console.log(`🎯 Tracking: http://localhost:${PORT}/t/:token\n`);
|
console.log(`🎯 Tracking: http://0.0.0.0:${PORT}/t/:token`);
|
||||||
|
console.log(`🌍 Server listening on all interfaces (0.0.0.0:${PORT})\n`);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to start server:', error);
|
logger.error('Failed to start server:', error);
|
||||||
|
|||||||
@@ -166,14 +166,9 @@ exports.updateSystemSettings = async (req, res, next) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update CORS configuration if available
|
|
||||||
if (req.app && req.app.updateCorsSettings) {
|
|
||||||
await req.app.updateCorsSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Sistem ayarları güncellendi. CORS ayarları uygulandı.',
|
message: 'Sistem ayarları güncellendi.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
|
|||||||
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
services:
|
||||||
|
oltalama:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: oltalama
|
||||||
|
ports:
|
||||||
|
- "0.0.0.0:3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
- DB_PATH=/app/database/oltalama.db
|
||||||
|
- SESSION_SECRET=${SESSION_SECRET:-change-me-in-production}
|
||||||
|
- FRONTEND_URL=${FRONTEND_URL:-http://localhost:3000}
|
||||||
|
volumes:
|
||||||
|
# Persist database
|
||||||
|
- ./data/database:/app/database
|
||||||
|
# Persist logs
|
||||||
|
- ./data/logs:/app/logs
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
56
docker-entrypoint.sh
Normal file
56
docker-entrypoint.sh
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Starting Oltalama application..."
|
||||||
|
|
||||||
|
# Ensure database directory exists
|
||||||
|
mkdir -p database
|
||||||
|
|
||||||
|
# Run database migrations
|
||||||
|
echo "📊 Running database migrations..."
|
||||||
|
if node migrations/run-migrations.js; then
|
||||||
|
echo "✅ Migrations completed successfully"
|
||||||
|
else
|
||||||
|
echo "⚠️ Migration failed, but continuing..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if we should seed the database
|
||||||
|
# Only seed if database is empty (no admin users)
|
||||||
|
if [ ! -f "database/oltalama.db" ] || [ ! -s "database/oltalama.db" ]; then
|
||||||
|
echo "🌱 Database is empty, seeding initial data..."
|
||||||
|
if node seeders/run-seeders.js; then
|
||||||
|
echo "✅ Seeding completed successfully"
|
||||||
|
else
|
||||||
|
echo "⚠️ Seeding failed, but continuing..."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Check if admin user exists
|
||||||
|
if command -v sqlite3 >/dev/null 2>&1; then
|
||||||
|
ADMIN_COUNT=$(sqlite3 database/oltalama.db "SELECT COUNT(*) FROM admin_user;" 2>/dev/null || echo "0")
|
||||||
|
if [ "$ADMIN_COUNT" = "0" ]; then
|
||||||
|
echo "🌱 Database exists but no admin user found. Running seeders..."
|
||||||
|
if node seeders/run-seeders.js; then
|
||||||
|
echo "✅ Seeding completed successfully"
|
||||||
|
else
|
||||||
|
echo "⚠️ Seeding failed, but continuing..."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "✅ Database already initialized with admin user(s), skipping seeders."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ sqlite3 command not found, skipping admin user check."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure frontend build exists before starting server
|
||||||
|
FRONTEND_INDEX="src/public/dist/index.html"
|
||||||
|
if [ ! -f "$FRONTEND_INDEX" ]; then
|
||||||
|
echo "❌ Frontend build not found at $FRONTEND_INDEX"
|
||||||
|
echo "ℹ️ Please run 'npm run build' in the frontend (or rebuild the Docker image) so the dist assets are available."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
echo "✨ Starting server..."
|
||||||
|
exec "$@"
|
||||||
|
|
||||||
@@ -1,6 +1,18 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
|
const resolveApiUrl = () => {
|
||||||
|
if (import.meta.env.VITE_API_URL) {
|
||||||
|
return import.meta.env.VITE_API_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined' && window.location?.origin) {
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'http://localhost:3000';
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_URL = resolveApiUrl();
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: API_URL,
|
baseURL: API_URL,
|
||||||
|
|||||||
@@ -4,4 +4,17 @@ import react from '@vitejs/plugin-react'
|
|||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
base: '/', // Base path - root'tan servis edilecek
|
||||||
|
build: {
|
||||||
|
assetsDir: 'assets',
|
||||||
|
// Relative path'ler kullan (absolute değil)
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
// Asset dosyaları için relative path
|
||||||
|
assetFileNames: 'assets/[name]-[hash][extname]',
|
||||||
|
chunkFileNames: 'assets/[name]-[hash].js',
|
||||||
|
entryFileNames: 'assets/[name]-[hash].js',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user