Changed terminology from "API" to "Routes" to better reflect server-rendered HTML approach: - Renamed submission_api.md → submission_routes.md - Renamed dashboard_api.md → dashboard_routes.md - Renamed admin_api.md → admin_routes.md - Updated headers to clarify "Response Type: Server-rendered HTML (no JavaScript required)" - Updated references in plan.md and quickstart.md This clarifies that the application uses traditional web routes with form submissions and HTML responses, not REST API endpoints with JSON. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
422 lines
10 KiB
Markdown
422 lines
10 KiB
Markdown
# Dashboard Routes Contract
|
|
|
|
**Scope**: Product owner dashboard web routes (User Story P3)
|
|
**Authentication**: Required (session-based via Flask-Login)
|
|
**Response Type**: Server-rendered HTML (no JavaScript required)
|
|
|
|
---
|
|
|
|
## GET /login
|
|
|
|
Display login form for product owners and administrators.
|
|
|
|
### Request
|
|
|
|
**Headers**: None required
|
|
|
|
**Query Parameters**:
|
|
- `next` (string, optional): Redirect URL after successful login
|
|
|
|
### Response
|
|
|
|
**Success (200 OK)**:
|
|
```html
|
|
Content-Type: text/html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>Login - Reklamator</title></head>
|
|
<body>
|
|
<h1>Login</h1>
|
|
<form method="POST" action="/login">
|
|
<input type="email" name="email" required placeholder="Email">
|
|
<input type="password" name="password" required placeholder="Password">
|
|
<button type="submit">Login</button>
|
|
</form>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**Already Authenticated (302 Redirect)**: Redirect to `/dashboard`
|
|
|
|
---
|
|
|
|
## POST /login
|
|
|
|
Authenticate product owner or administrator.
|
|
|
|
### Request
|
|
|
|
**Headers**:
|
|
- `Content-Type: application/x-www-form-urlencoded`
|
|
|
|
**Form Data**:
|
|
- `email` (string, required): User email
|
|
- `password` (string, required): User password
|
|
|
|
### Response
|
|
|
|
**Success (302 Redirect)**:
|
|
```http
|
|
HTTP/1.1 302 Found
|
|
Location: /dashboard
|
|
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
|
|
```
|
|
|
|
**Error (401 Unauthorized)**:
|
|
```html
|
|
Content-Type: text/html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<body>
|
|
<h1>Login Failed</h1>
|
|
<p>Invalid email or password.</p>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### Functional Requirements Covered
|
|
- FR-056: Authentication required for dashboard
|
|
- FR-063: Password verification against bcrypt hash
|
|
|
|
---
|
|
|
|
## GET /logout
|
|
|
|
Log out current user.
|
|
|
|
### Request
|
|
|
|
**Authentication**: Required (session cookie)
|
|
|
|
### Response
|
|
|
|
**Success (302 Redirect)**:
|
|
```http
|
|
HTTP/1.1 302 Found
|
|
Location: /login
|
|
Set-Cookie: session=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT
|
|
```
|
|
|
|
---
|
|
|
|
## GET /dashboard
|
|
|
|
Display product owner dashboard with feedback list.
|
|
|
|
### Request
|
|
|
|
**Authentication**: Required (session cookie)
|
|
|
|
**Query Parameters**:
|
|
- `page` (integer, optional, default=1): Page number for pagination
|
|
- `category` (string, optional): Filter by category (idea/feature_request/bug/complaint)
|
|
- `language` (string, optional): Filter by original language (ISO 639-1 code)
|
|
- `status` (string, optional): Filter by status (analyzed/reviewed/in_progress/resolved/rejected)
|
|
- `date_from` (string, optional): Filter by date range start (ISO 8601 date)
|
|
- `date_to` (string, optional): Filter by date range end (ISO 8601 date)
|
|
- `search` (string, optional): Keyword search across text/translation/summary
|
|
|
|
### Response
|
|
|
|
**Success (200 OK)**:
|
|
```html
|
|
Content-Type: text/html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>Feedback Dashboard</title></head>
|
|
<body>
|
|
<h1>Feedback Dashboard</h1>
|
|
|
|
<!-- Product selector if multiple products assigned -->
|
|
<select name="product">
|
|
<option value="001-acme-app">Acme Mobile App (127 items)</option>
|
|
<option value="002-beta-service">Beta Service (43 items)</option>
|
|
</select>
|
|
|
|
<!-- Filters -->
|
|
<form method="GET" action="/dashboard">
|
|
<select name="category">
|
|
<option value="">All Categories</option>
|
|
<option value="idea">Ideas</option>
|
|
<option value="feature_request">Feature Requests</option>
|
|
<option value="bug">Bugs</option>
|
|
<option value="complaint">Complaints</option>
|
|
</select>
|
|
<select name="status">
|
|
<option value="">All Statuses</option>
|
|
<option value="analyzed">Analyzed</option>
|
|
<option value="reviewed">Reviewed</option>
|
|
<option value="in_progress">In Progress</option>
|
|
<option value="resolved">Resolved</option>
|
|
<option value="rejected">Rejected</option>
|
|
</select>
|
|
<input type="text" name="search" placeholder="Search feedback...">
|
|
<button type="submit">Filter</button>
|
|
</form>
|
|
|
|
<!-- Feedback list -->
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Date</th>
|
|
<th>Category</th>
|
|
<th>Original Lang</th>
|
|
<th>Summary</th>
|
|
<th>Status</th>
|
|
<th>Attachments</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><a href="/feedback/a3f2c1d5">a3f2c1d5</a></td>
|
|
<td>2025-10-15 14:32</td>
|
|
<td>Bug</td>
|
|
<td>DE</td>
|
|
<td>User reports app crashes when uploading large files...</td>
|
|
<td>Reviewed</td>
|
|
<td>2 files</td>
|
|
</tr>
|
|
<!-- More rows... -->
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Pagination -->
|
|
<div class="pagination">
|
|
<a href="/dashboard?page=1">1</a>
|
|
<a href="/dashboard?page=2">2</a>
|
|
<a href="/dashboard?page=3">3</a>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**Error (401 Unauthorized)**: Not authenticated
|
|
```http
|
|
HTTP/1.1 302 Found
|
|
Location: /login?next=/dashboard
|
|
```
|
|
|
|
**Error (403 Forbidden)**: User has no assigned products
|
|
```html
|
|
Content-Type: text/html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<body>
|
|
<h1>No Access</h1>
|
|
<p>You are not assigned to any products.</p>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### Behavior
|
|
|
|
- Display only feedback for products assigned to current user (FR-033)
|
|
- Admin users see all products
|
|
- Default sort: newest first (FR-041)
|
|
- Pagination: 50 items per page (SC-008: <3s for 1000 items)
|
|
- Filters preserved in URL for sharing/bookmarking
|
|
|
|
### Functional Requirements Covered
|
|
- FR-032: Authenticated dashboard access
|
|
- FR-033: Product owner access control
|
|
- FR-034: Display all analysis results
|
|
- FR-036-FR-041: Filtering and searching
|
|
- FR-041: Reverse chronological order
|
|
|
|
---
|
|
|
|
## GET /feedback/{feedback_id}
|
|
|
|
Display detailed view of a single feedback item.
|
|
|
|
### Request
|
|
|
|
**Authentication**: Required (session cookie)
|
|
|
|
**Path Parameters**:
|
|
- `feedback_id` (UUID, required): Feedback identifier
|
|
|
|
### Response
|
|
|
|
**Success (200 OK)**:
|
|
```html
|
|
Content-Type: text/html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>Feedback Detail - a3f2c1d5</title></head>
|
|
<body>
|
|
<h1>Feedback Detail</h1>
|
|
|
|
<div class="metadata">
|
|
<p><strong>ID:</strong> a3f2c1d5-8b4e-4f1a-9c2d-7e6f5a4b3c2d</p>
|
|
<p><strong>Product:</strong> Acme Mobile App</p>
|
|
<p><strong>Submitted:</strong> 2025-10-15 14:32:10 UTC</p>
|
|
<p><strong>Original Language:</strong> German (DE)</p>
|
|
<p><strong>Category:</strong> Bug (confidence: 0.92)</p>
|
|
<p><strong>Status:</strong>
|
|
<form method="POST" action="/feedback/a3f2c1d5/status">
|
|
<select name="status">
|
|
<option value="analyzed">Analyzed</option>
|
|
<option value="reviewed" selected>Reviewed</option>
|
|
<option value="in_progress">In Progress</option>
|
|
<option value="resolved">Resolved</option>
|
|
<option value="rejected">Rejected</option>
|
|
</select>
|
|
<button type="submit">Update Status</button>
|
|
</form>
|
|
</p>
|
|
</div>
|
|
|
|
<h2>AI Analysis Summary</h2>
|
|
<p>User reports that the app crashes when uploading large files. This appears to be a bug affecting the file upload module, preventing users from submitting documents over 5MB.</p>
|
|
|
|
<h2>Original Text (German)</h2>
|
|
<pre>Die App stürzt ab, wenn ich versuche, große Dateien hochzuladen. Jedes Mal wenn ich eine PDF über 5MB hochlade, friert die App ein und schließt sich.</pre>
|
|
|
|
<h2>Translation (English)</h2>
|
|
<pre>The app crashes when I try to upload large files. Every time I upload a PDF over 5MB, the app freezes and closes.</pre>
|
|
|
|
<h2>Attachments</h2>
|
|
<ul>
|
|
<li><a href="/feedback/a3f2c1d5/attachment/screenshot.png" target="_blank">screenshot.png</a> (245 KB)</li>
|
|
<li><a href="/feedback/a3f2c1d5/attachment/error_log.txt" target="_blank">error_log.txt</a> (1 KB)</li>
|
|
</ul>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**Error (401 Unauthorized)**: Not authenticated
|
|
```http
|
|
HTTP/1.1 302 Found
|
|
Location: /login?next=/feedback/{feedback_id}
|
|
```
|
|
|
|
**Error (403 Forbidden)**: User not authorized for this product
|
|
```html
|
|
Content-Type: text/html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<body>
|
|
<h1>Access Denied</h1>
|
|
<p>You do not have permission to view this feedback.</p>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**Error (404 Not Found)**: Feedback does not exist
|
|
```html
|
|
Content-Type: text/html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<body>
|
|
<h1>Feedback Not Found</h1>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### Functional Requirements Covered
|
|
- FR-034: Display complete feedback details
|
|
- FR-035: Links to download attachments
|
|
- FR-042: Status indicators
|
|
- FR-044: File attachments with icons/thumbnails
|
|
|
|
---
|
|
|
|
## POST /feedback/{feedback_id}/status
|
|
|
|
Update the status of a feedback item.
|
|
|
|
### Request
|
|
|
|
**Authentication**: Required (session cookie)
|
|
|
|
**Path Parameters**:
|
|
- `feedback_id` (UUID, required): Feedback identifier
|
|
|
|
**Form Data**:
|
|
- `status` (string, required): New status (analyzed/reviewed/in_progress/resolved/rejected)
|
|
|
|
### Response
|
|
|
|
**Success (302 Redirect)**:
|
|
```http
|
|
HTTP/1.1 302 Found
|
|
Location: /feedback/{feedback_id}
|
|
```
|
|
|
|
**Error (403 Forbidden)**: User not authorized for this product
|
|
**Error (404 Not Found)**: Feedback does not exist
|
|
|
|
### Side Effects
|
|
|
|
- Updates `metadata.yaml`: `status` field
|
|
- Preserves timestamp of status change
|
|
|
|
### Functional Requirements Covered
|
|
- FR-042: Mark feedback with status indicators
|
|
- FR-043: Preserve status when filtering
|
|
|
|
---
|
|
|
|
## GET /feedback/{feedback_id}/attachment/{filename}
|
|
|
|
Download or view an attached file.
|
|
|
|
### Request
|
|
|
|
**Authentication**: Required (session cookie)
|
|
|
|
**Path Parameters**:
|
|
- `feedback_id` (UUID, required): Feedback identifier
|
|
- `filename` (string, required): Sanitized filename
|
|
|
|
### Response
|
|
|
|
**Success (200 OK)**: Image file
|
|
```http
|
|
HTTP/1.1 200 OK
|
|
Content-Type: image/png
|
|
Content-Disposition: inline; filename="screenshot.png"
|
|
Content-Length: 245678
|
|
|
|
[binary image data]
|
|
```
|
|
|
|
**Success (200 OK)**: Document file
|
|
```http
|
|
HTTP/1.1 200 OK
|
|
Content-Type: application/pdf
|
|
Content-Disposition: attachment; filename="report.pdf"
|
|
Content-Length: 1234567
|
|
|
|
[binary document data]
|
|
```
|
|
|
|
**Error (403 Forbidden)**: User not authorized for this product
|
|
**Error (404 Not Found)**: File does not exist
|
|
|
|
### Security
|
|
|
|
- Files served via Flask route (not direct filesystem access per FR-058)
|
|
- Access control enforced: User must have access to parent product
|
|
- Path traversal prevention: Filename sanitized
|
|
- MIME type from stored metadata (not client-provided)
|
|
|
|
### Functional Requirements Covered
|
|
- FR-035: Provide links to download/view attachments
|
|
- FR-058: Prevent unauthorized file access
|
|
|
|
---
|
|
|
|
## Rate Limiting
|
|
|
|
Dashboard endpoints are NOT rate limited (authenticated users only).
|