# Phase 3: Server Integration ## Objective Attach drive operations to the Quart application's routing layer and WebSocket handlers. This phase implements the API endpoints (`/files/` and `/files//meta`) and handles WebSocket binary uploads and list requests. ## Concepts ### Server Actions & Protocol We leverage the existing WebSocket action protocol for interactions and REST for byte streams: | Action | Method | Payload | Description | |--------|--------|---------|-------------| | `list-drives` | WS | `{}` | Lists available drives for the logged-in user. | | `list-files` | WS | `{driveName: "..."}` | Returns array of DB metadata objects for the specified drive. | | `upload-file` | WS Binary | `{driveName, fileName, mime?}` | Sends JSON header followed by binary stream. Returns `fileId`. | | `/files/` | GET | N/A | Serves raw file download. Uses resolved path. Auth required. | | `/files//meta` | GET | N/A | Lightweight metadata endpoint for existence checks. | **WebSocket Upload Sequence Example:** ```javascript // 1. Send metadata ws.send(JSON.stringify({action: "upload-file", nonce: "n1", body: {driveName: "Docs", fileName: "report.pdf"}})); // 2. Immediately follow with binary payload ws.send(blob); // 3. Server responds {action: "file-uploaded", fileId: 42, nonce: "n1"} ``` ## Tasks ### 3.1 Register Routes Extend `backend/src/app.py` with the following route handlers: ```python from quart import Quart, request, send_file, make_response from . import drive_service app = Quart(__name__) @app.route("/files/", methods=["GET"]) async def serve_file(id): try: current_user_id = request.headers.get("Authorization").split(" ")[1] # or via auth middleware file_path, mime_type = await drive_service.resolve_drive_file_for_serving(id, current_user_id) return await send_file(file_path, mimetype=mime_type) except FileNotFoundError as e: return make_response("Not found", 404) except PermissionError: return make_response("Forbidden", 403) @app.route("/files//meta", methods=["GET"]) async def get_file_meta(id): try: current_user_id = ... # same auth logic # Verify ownership briefly before returning meta file = await db.DriveFile.filter(id=id).select_related("drive__user").get_or_none() if not file or file.drive.user_id != current_user_id: raise Exception("Denied") return drive_service.get_drive_file_metadata(id) except Exception as e: return make_response({"error": str(e)}, 404) ``` *Note: Replace explicit auth extraction with your framework's actual token validation mechanism.* ### 3.2 Extend WebSocket Handler In `backend/src/app.py`, update the existing `server.websocket('/ws')` handler to cover new actions: - `list-drives`: Query `db.Drive.filter(user_id=user_id)`. Return a list of names and IDs. - `list-files`: Query `db.DriveFile.filter(drive__name=drive_name)` filtered by the authenticated user. - `upload-file`: Parse the first message for JSON headers (`driveName`, `fileName`, `mime`). Open the stream, call `drive_service.upload_file(...)`. Reply with `{action: "file-uploaded", fileId: ..., nonce: ...}`.