- Add POST /upload endpoint to server.py (saves to photos/ dir) - Add DELETE /photos/ endpoint for photo removal - Replace base64 photoData with server-stored photoPath - Export/import uses path string instead of large base64 data
85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
import http.server
|
|
import socketserver
|
|
import os
|
|
import urllib.parse
|
|
import uuid
|
|
|
|
PORT = 8000
|
|
PHOTO_DIR = 'photos'
|
|
os.makedirs(PHOTO_DIR, exist_ok=True)
|
|
|
|
|
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
|
|
def do_POST(self):
|
|
if self.path == '/upload':
|
|
content_len = int(self.headers.get('Content-Length', 0))
|
|
body = self.rfile.read(content_len)
|
|
# parse multipart/form-data
|
|
boundary = self.headers.get('Content-Type', '').split('boundary=')[-1].encode()
|
|
if not boundary:
|
|
self.send_error(400, 'Missing boundary')
|
|
return
|
|
parts = body.split(b'--' + boundary)
|
|
filename = None
|
|
file_data = None
|
|
for part in parts:
|
|
if b'Content-Disposition' in part:
|
|
header_end = part.find(b'\r\n\r\n')
|
|
if header_end == -1:
|
|
continue
|
|
header_section = part[:header_end].decode('utf-8', errors='replace')
|
|
data = part[header_end + 4:]
|
|
data = data.rstrip(b'\r\n--')
|
|
# Check if this part has a filename
|
|
for line in header_section.split('\r\n'):
|
|
if 'filename="' in line:
|
|
orig_name = line.split('filename="')[1].split('"')[0]
|
|
ext = os.path.splitext(orig_name)[1] or '.jpg'
|
|
fname = uuid.uuid4().hex + ext
|
|
file_data = data
|
|
filename = fname
|
|
break
|
|
|
|
if filename and file_data:
|
|
path = os.path.join(PHOTO_DIR, filename)
|
|
with open(path, 'wb') as f:
|
|
f.write(file_data)
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.end_headers()
|
|
self.wfile.write(('{"path":"photos/' + filename + '"}').encode())
|
|
else:
|
|
self.send_error(400, 'No file found')
|
|
else:
|
|
self.send_error(404)
|
|
|
|
def do_DELETE(self):
|
|
parsed = urllib.parse.urlparse(self.path)
|
|
if parsed.path.startswith('/photos/'):
|
|
rel = os.path.relpath(parsed.path, '/photos/')
|
|
# basic path safety
|
|
if '..' in rel or rel.startswith('/'):
|
|
self.send_error(400)
|
|
return
|
|
filepath = os.path.join(PHOTO_DIR, rel)
|
|
if os.path.exists(filepath):
|
|
os.remove(filepath)
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write(b'{"ok":true}')
|
|
else:
|
|
self.send_error(404)
|
|
else:
|
|
self.send_error(404)
|
|
|
|
def log_message(self, format, *args):
|
|
if '/upload' in str(args[0]) or '/photos/' in str(args[0]):
|
|
return # skip noisy upload logs
|
|
super().log_message(format, *args)
|
|
|
|
|
|
with socketserver.TCPServer(("", PORT), Handler) as httpd:
|
|
print(f"Serving at http://0.0.0.0:{PORT}")
|
|
httpd.serve_forever()
|