This commit is contained in:
Gal 2025-07-29 18:13:19 +02:00
parent 819f52241c
commit bcea6da633
Signed by: gal
GPG Key ID: F035BC65003BC00B
8 changed files with 334 additions and 3 deletions

145
.gitignore vendored Normal file
View File

@ -0,0 +1,145 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
package-lock.json
yarn.lock
pnpm-lock.yaml
# Build outputs
dist/
build/
.vite/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
logs/
*.log
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Coverage
.coverage
.nyc_output
coverage/
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Docker
.dockerignore
# Production
.env.production
.env.local
.env.development.local
.env.test.local
.env.production.local

11
backend/.env.example Normal file
View File

@ -0,0 +1,11 @@
# FastAPI Configuration
HOST=0.0.0.0
PORT=8000
DEBUG=false
# CORS Settings
CORS_ORIGINS=https://slow-reader.velouria.dev
# Application Settings
APP_NAME=Slow Reader API
VERSION=1.0.0

30
backend/Dockerfile Normal file
View File

@ -0,0 +1,30 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# Copy project files
COPY pyproject.toml ./
COPY backend/ ./backend/
# Install uv
RUN pip install uv
# Install dependencies
RUN uv pip install --system -r pyproject.toml
# Download NLTK data
RUN python -c "import nltk; nltk.download('punkt'); nltk.download('punkt_tab')"
# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]

48
docker-compose.yml Normal file
View File

@ -0,0 +1,48 @@
version: '3.8'
services:
backend:
build:
context: .
dockerfile: backend/Dockerfile
restart: always
networks:
- default
- traefik_network
labels:
- "traefik.enable=true"
- "traefik.http.routers.slow-reader-api.rule=Host(`slow-reader.velouria.dev`) && (PathPrefix(`/api/`) || PathPrefix(`/ws/`))"
- "traefik.http.routers.slow-reader-api.entrypoints=websecure"
- "traefik.http.routers.slow-reader-api.tls.certresolver=myresolver"
- "traefik.http.services.slow-reader-api.loadbalancer.server.port=8000"
- "traefik.docker.network=traefik_network"
- "homepage.group=Tools"
- "homepage.name=Slow Reader API"
- "homepage.description=Reading Focus API"
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
restart: always
depends_on:
- backend
networks:
- default
- traefik_network
labels:
- "traefik.enable=true"
- "traefik.http.routers.slow-reader.rule=Host(`slow-reader.velouria.dev`)"
- "traefik.http.routers.slow-reader.entrypoints=websecure"
- "traefik.http.routers.slow-reader.tls.certresolver=myresolver"
- "traefik.http.services.slow-reader.loadbalancer.server.port=80"
- "traefik.docker.network=traefik_network"
- "homepage.group=Tools"
- "homepage.name=Slow Reader"
- "homepage.description=Focused Reading Experience"
networks:
default:
name: slow-reader_default
traefik_network:
external: true

41
frontend/Dockerfile Normal file
View File

@ -0,0 +1,41 @@
# Build stage
FROM node:18-alpine as build
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM nginx:alpine
# Copy built assets from build stage
COPY --from=build /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Create non-root user
RUN addgroup -g 1001 -S appuser && \
adduser -S appuser -u 1001 -G appuser && \
chown -R appuser:appuser /usr/share/nginx/html && \
chown -R appuser:appuser /var/cache/nginx && \
chown -R appuser:appuser /var/log/nginx && \
chown -R appuser:appuser /etc/nginx/conf.d && \
touch /var/run/nginx.pid && \
chown -R appuser:appuser /var/run/nginx.pid
USER appuser
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

37
frontend/nginx.conf Normal file
View File

@ -0,0 +1,37 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Enable gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline' 'unsafe-eval'" always;
# Handle client-side routing
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Cache HTML with shorter expiry
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public";
}
}

View File

@ -299,7 +299,11 @@ That's what Slow Reader is about. Just you and the text, moving at a pace that l
}, },
connectWebSocket() { connectWebSocket() {
const wsUrl = `ws://localhost:8000/ws/reading-session` const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = window.location.host
const wsUrl = import.meta.env.DEV
? `ws://localhost:8000/ws/reading-session`
: `${protocol}//${host}/ws/reading-session`
this.websocket = new WebSocket(wsUrl) this.websocket = new WebSocket(wsUrl)
this.websocket.onopen = () => { this.websocket.onopen = () => {
@ -556,7 +560,8 @@ That's what Slow Reader is about. Just you and the text, moving at a pace that l
this.extractedArticle = null this.extractedArticle = null
try { try {
const response = await fetch('/api/extract-article', { const apiBase = import.meta.env.DEV ? '/api' : '/api'
const response = await fetch(`${apiBase}/extract-article`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -633,7 +638,8 @@ That's what Slow Reader is about. Just you and the text, moving at a pace that l
} }
try { try {
const response = await fetch('/api/analyze-text', { const apiBase = import.meta.env.DEV ? '/api' : '/api'
const response = await fetch(`${apiBase}/analyze-text`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -17,6 +17,19 @@ export default defineConfig({
} }
} }
}, },
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue'],
pdfjs: ['pdfjs-dist']
}
}
}
},
optimizeDeps: { optimizeDeps: {
include: ['pdfjs-dist'] include: ['pdfjs-dist']
} }