2025-10-16 13:47:40 +02:00
|
|
|
# Submission Routes Contract
|
2025-10-15 22:31:26 +02:00
|
|
|
|
2025-10-16 13:47:40 +02:00
|
|
|
**Scope**: Anonymous feedback submission web routes (User Story P1)
|
2025-10-15 22:31:26 +02:00
|
|
|
**Authentication**: None (anonymous access)
|
2025-10-16 13:47:40 +02:00
|
|
|
**Response Type**: Server-rendered HTML (no JavaScript required)
|
2025-10-15 22:31:26 +02:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## GET /submit/{product_slug}
|
|
|
|
|
|
|
|
|
|
Display the feedback submission form for a specific product.
|
|
|
|
|
|
|
|
|
|
### Request
|
|
|
|
|
|
|
|
|
|
**Path Parameters**:
|
|
|
|
|
- `product_slug` (string, required): Product's URL-safe identifier
|
|
|
|
|
|
|
|
|
|
**Headers**: None required
|
|
|
|
|
|
|
|
|
|
**Query Parameters**: None
|
|
|
|
|
|
|
|
|
|
### Response
|
|
|
|
|
|
|
|
|
|
**Success (200 OK)**:
|
|
|
|
|
```html
|
|
|
|
|
Content-Type: text/html
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<head><title>Submit Feedback - {Product Name}</title></head>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>Submit Feedback for {Product Name}</h1>
|
|
|
|
|
<form method="POST" action="/submit/{product_slug}" enctype="multipart/form-data">
|
|
|
|
|
<textarea name="feedback_text" maxlength="10000"></textarea>
|
|
|
|
|
<input type="file" name="attachments" multiple accept=".pdf,.docx,.txt,.jpg,.png,.gif,.webp">
|
|
|
|
|
<button type="submit">Submit Feedback</button>
|
|
|
|
|
</form>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Error (404 Not Found)**: Product does not exist or is archived
|
|
|
|
|
```html
|
|
|
|
|
Content-Type: text/html
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>Product Not Found</h1>
|
|
|
|
|
<p>The product you're looking for does not exist or is no longer accepting feedback.</p>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Functional Requirements Covered
|
|
|
|
|
- FR-001: Public, unauthenticated submission form
|
|
|
|
|
- FR-002: Text input up to 10,000 characters
|
|
|
|
|
- FR-004: Up to 3 file attachments
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## POST /submit/{product_slug}
|
|
|
|
|
|
|
|
|
|
Submit anonymous feedback for a specific product.
|
|
|
|
|
|
|
|
|
|
### Request
|
|
|
|
|
|
|
|
|
|
**Path Parameters**:
|
|
|
|
|
- `product_slug` (string, required): Product's URL-safe identifier
|
|
|
|
|
|
|
|
|
|
**Headers**:
|
|
|
|
|
- `Content-Type: multipart/form-data`
|
|
|
|
|
|
|
|
|
|
**Form Data**:
|
|
|
|
|
- `feedback_text` (string, optional): Feedback text (0-10,000 characters)
|
|
|
|
|
- `attachments` (file[], optional): Up to 3 files, max 10MB each
|
|
|
|
|
|
|
|
|
|
**Example**:
|
|
|
|
|
```http
|
|
|
|
|
POST /submit/acme-app HTTP/1.1
|
|
|
|
|
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
|
|
|
|
|
|
|
|
|
|
------WebKitFormBoundary
|
|
|
|
|
Content-Disposition: form-data; name="feedback_text"
|
|
|
|
|
|
|
|
|
|
The app crashes when uploading files over 5MB.
|
|
|
|
|
------WebKitFormBoundary
|
|
|
|
|
Content-Disposition: form-data; name="attachments"; filename="screenshot.png"
|
|
|
|
|
Content-Type: image/png
|
|
|
|
|
|
|
|
|
|
[binary data]
|
|
|
|
|
------WebKitFormBoundary--
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Response
|
|
|
|
|
|
|
|
|
|
**Success (200 OK)**:
|
|
|
|
|
```html
|
|
|
|
|
Content-Type: text/html
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>Thank You!</h1>
|
|
|
|
|
<p>Your feedback has been submitted successfully.</p>
|
|
|
|
|
<p>Your feedback ID: {feedback_id}</p>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Error (400 Bad Request)**: Validation failure
|
|
|
|
|
```html
|
|
|
|
|
Content-Type: text/html
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>Submission Error</h1>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>Feedback must contain text or at least one attachment</li>
|
|
|
|
|
<li>Maximum 3 attachments allowed</li>
|
|
|
|
|
<li>Text cannot exceed 10,000 characters</li>
|
|
|
|
|
<li>File size cannot exceed 10MB per file</li>
|
|
|
|
|
<li>Unsupported file type: {filename}</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Error (413 Payload Too Large)**: File size exceeds limit
|
|
|
|
|
```html
|
|
|
|
|
Content-Type: text/html
|
|
|
|
|
HTTP/1.1 413 Payload Too Large
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>File Too Large</h1>
|
|
|
|
|
<p>One or more files exceed the 10MB limit.</p>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Error (429 Too Many Requests)**: Rate limit exceeded
|
|
|
|
|
```html
|
|
|
|
|
Content-Type: text/html
|
|
|
|
|
X-RateLimit-Limit: 10
|
|
|
|
|
X-RateLimit-Remaining: 0
|
|
|
|
|
X-RateLimit-Reset: 1697456789
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>Too Many Submissions</h1>
|
|
|
|
|
<p>You have exceeded the submission limit of 10 per hour. Please try again later.</p>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Error (451 Unavailable For Legal Reasons)**: Malware detected
|
|
|
|
|
```html
|
|
|
|
|
Content-Type: text/html
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>Security Error</h1>
|
|
|
|
|
<p>One or more files failed security scanning. Please ensure your files are safe and try again.</p>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Validation Rules
|
|
|
|
|
|
|
|
|
|
1. **Text Validation** (FR-002, FR-003):
|
|
|
|
|
- Length: 0-10,000 characters
|
|
|
|
|
- Encoding: UTF-8, any language accepted
|
|
|
|
|
- Empty allowed if attachments present
|
|
|
|
|
|
|
|
|
|
2. **Attachment Validation** (FR-004, FR-005, FR-006):
|
|
|
|
|
- Count: 0-3 files
|
|
|
|
|
- Size: Max 10MB per file
|
|
|
|
|
- Types: PDF, DOCX, TXT, JPG, PNG, GIF, WebP
|
|
|
|
|
- MIME type validation (server-side)
|
|
|
|
|
|
|
|
|
|
3. **Submission Validation** (FR-011, FR-012):
|
|
|
|
|
- Must have text OR attachments (not both empty)
|
|
|
|
|
|
|
|
|
|
4. **Security** (FR-059, FR-060):
|
|
|
|
|
- ClamAV virus scan before storage
|
|
|
|
|
- Filename sanitization (remove path traversal)
|
|
|
|
|
- MIME type validation
|
|
|
|
|
|
|
|
|
|
5. **Rate Limiting** (FR-061):
|
|
|
|
|
- 10 submissions per hour per IP address
|
|
|
|
|
|
|
|
|
|
### Functional Requirements Covered
|
|
|
|
|
- FR-001 to FR-012: Complete submission flow
|
|
|
|
|
- FR-055: No IP/session tracking stored
|
|
|
|
|
- FR-059, FR-060: File security
|
|
|
|
|
- FR-061: Rate limiting
|
|
|
|
|
|
|
|
|
|
### Side Effects
|
|
|
|
|
|
|
|
|
|
1. **File System**:
|
|
|
|
|
- Creates `data/products/{product_id}/feedback/{feedback_id}/`
|
|
|
|
|
- Writes `metadata.yaml`, `content.txt`
|
|
|
|
|
- Writes `attachments/{filename}` if files uploaded
|
|
|
|
|
|
|
|
|
|
2. **Async Processing**:
|
|
|
|
|
- Triggers AI analysis background job
|
|
|
|
|
- Updates feedback status: `submitted` → `analyzing`
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## POST /submit/{product_slug}/status/{feedback_id}
|
|
|
|
|
|
|
|
|
|
**Note**: This endpoint is OUT OF SCOPE for MVP. Feedback submission is fire-and-forget. Users cannot track submission status anonymously.
|
|
|
|
|
|
|
|
|
|
Future consideration: Anonymous status check via feedback ID (requires balancing anonymity with user experience).
|