76 lines
3.2 KiB
Markdown
76 lines
3.2 KiB
Markdown
# 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/<int:id>` and `/files/<int:id>/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/<int:id>` | GET | N/A | Serves raw file download. Uses resolved path. Auth required. |
|
|
| `/files/<int:id>/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/<int:id>", 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/<int:id>/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: ...}`.
|