fix: FilePreview fileType case + Tailwind v4 gradient transparent bug
- FilePreview.vue: add normalizedFileType computed to handle backend returning uppercase HTML/MD/PPTX (fixes preview/download buttons) - FilePreview.vue: bg-gradient-to-r from-orange-500 -> bg-orange-500 (Tailwind v4 gradient + CSS variable = transparent) - ReportCard.vue: bg-gradient-to-r -> bg-orange-600 for selected state - Add .opencode/, node_modules/, dist/ to .gitignore - Initial git setup for publish project
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Simple static file server with /api proxy to backend."""
|
||||
import os
|
||||
import http.server
|
||||
import socketserver
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
|
||||
PORT = 80
|
||||
BACKEND = os.environ.get('BACKEND_URL', 'http://publish-backend:8080')
|
||||
STATIC_DIR = '/app/dist'
|
||||
UPLOADS_DIR = '/app/uploads'
|
||||
STATIC_PATH = Path(STATIC_DIR)
|
||||
UPLOADS_PATH = Path(UPLOADS_DIR)
|
||||
|
||||
|
||||
class ProxyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
"""Serve static files + proxy /api/ requests to backend."""
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith('/api/') or self.path.startswith('/uploads/'):
|
||||
self.proxy_request()
|
||||
else:
|
||||
super().do_GET()
|
||||
|
||||
def do_POST(self):
|
||||
if self.path.startswith('/api/'):
|
||||
self.proxy_request()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_PUT(self):
|
||||
if self.path.startswith('/api/'):
|
||||
self.proxy_request()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_DELETE(self):
|
||||
if self.path.startswith('/api/'):
|
||||
self.proxy_request()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def proxy_request(self):
|
||||
"""Forward request to backend and return response."""
|
||||
url = BACKEND + self.path
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(content_length) if content_length else None
|
||||
|
||||
headers = {}
|
||||
for key in ('Content-Type', 'Authorization', 'Accept', 'X-Requested-With'):
|
||||
if key in self.headers:
|
||||
headers[key] = self.headers[key]
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(url, data=body, headers=headers, method=self.command)
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
self.send_response(response.status)
|
||||
self.send_headers(response.headers)
|
||||
self.wfile.write(response.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
self.send_response(e.code)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(e.read())
|
||||
except Exception as e:
|
||||
self.send_response(502)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(f'{{"error": "{e}"}}'.encode())
|
||||
|
||||
def send_headers(self, headers):
|
||||
"""Copy headers from backend response."""
|
||||
for key, value in headers.items():
|
||||
if key not in ('Transfer-Encoding', 'Connection'):
|
||||
self.send_header(key, value)
|
||||
self.end_headers()
|
||||
|
||||
def translate_path(self, path):
|
||||
"""Serve from /app/dist for frontend, /app/uploads for files."""
|
||||
if path.startswith('/uploads/'):
|
||||
return str(UPLOADS_PATH / path[9:])
|
||||
return str(STATIC_PATH / path.lstrip('/'))
|
||||
|
||||
|
||||
class ReuseAddrTCPServer(socketserver.TCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.chdir('/app')
|
||||
with ReuseAddrTCPServer(('', PORT), ProxyHTTPRequestHandler) as httpd:
|
||||
print(f'Serving on port {PORT}, backend at {BACKEND}')
|
||||
httpd.serve_forever()
|
||||
Reference in New Issue
Block a user