Add implementation planning artifacts for anonymous feedback platform
Complete Phase 0 (Research) and Phase 1 (Design) of implementation planning workflow: - plan.md: Technical context, constitution check, and project structure - research.md: Technology decisions for AI integration, file storage, authentication, security - data-model.md: Domain entities with validation rules and state transitions - contracts/: API specifications for submission, dashboard, and admin endpoints - quickstart.md: Developer setup guide with test-first workflow - CLAUDE.md: Updated agent context with tech stack All constitutional requirements validated. Ready for task generation phase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,604 @@
|
||||
# Admin API Contract
|
||||
|
||||
**Scope**: Product and user management endpoints (User Story P4)
|
||||
**Authentication**: Required (admin role only)
|
||||
|
||||
---
|
||||
|
||||
## GET /admin/products
|
||||
|
||||
Display list of all registered products.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
### Response
|
||||
|
||||
**Success (200 OK)**:
|
||||
```html
|
||||
Content-Type: text/html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Product Management</title></head>
|
||||
<body>
|
||||
<h1>Product Management</h1>
|
||||
|
||||
<a href="/admin/products/new">+ Create New Product</a>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Submission URL</th>
|
||||
<th>Target Language</th>
|
||||
<th>Feedback Count</th>
|
||||
<th>Assigned Owners</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>001-acme-app</td>
|
||||
<td>Acme Mobile App</td>
|
||||
<td>Active</td>
|
||||
<td><a href="/submit/acme-app">/submit/acme-app</a></td>
|
||||
<td>English (en)</td>
|
||||
<td>127</td>
|
||||
<td>2 owners</td>
|
||||
<td>
|
||||
<a href="/admin/products/001-acme-app/edit">Edit</a> |
|
||||
<a href="/admin/products/001-acme-app/archive">Archive</a>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- More rows... -->
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
**Error (403 Forbidden)**: User is not an administrator
|
||||
```html
|
||||
Content-Type: text/html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Access Denied</h1>
|
||||
<p>Administrator privileges required.</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Functional Requirements Covered
|
||||
- FR-045: List all products
|
||||
- FR-054: Display product statistics
|
||||
|
||||
---
|
||||
|
||||
## GET /admin/products/new
|
||||
|
||||
Display form to create a new product.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
### Response
|
||||
|
||||
**Success (200 OK)**:
|
||||
```html
|
||||
Content-Type: text/html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Create New Product</h1>
|
||||
<form method="POST" action="/admin/products">
|
||||
<label>Product ID (URL-safe):
|
||||
<input type="text" name="id" pattern="[a-z0-9-]+" required placeholder="001-my-product">
|
||||
</label>
|
||||
|
||||
<label>Name:
|
||||
<input type="text" name="name" maxlength="100" required placeholder="My Product">
|
||||
</label>
|
||||
|
||||
<label>Description:
|
||||
<textarea name="description" maxlength="500" placeholder="Optional description"></textarea>
|
||||
</label>
|
||||
|
||||
<label>Target Language for Translations:
|
||||
<select name="target_language" required>
|
||||
<option value="en">English</option>
|
||||
<option value="de">German</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="ja">Japanese</option>
|
||||
<!-- More languages... -->
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>Submission URL Slug:
|
||||
<input type="text" name="submission_url_slug" pattern="[a-z0-9-]+" required placeholder="my-product">
|
||||
</label>
|
||||
|
||||
<label>Assigned Product Owners:
|
||||
<select name="assigned_owner_ids" multiple>
|
||||
<option value="owner-001">Jane Smith (jane.smith@example.com)</option>
|
||||
<option value="owner-002">John Doe (john.doe@example.com)</option>
|
||||
<!-- More owners... -->
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<button type="submit">Create Product</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Functional Requirements Covered
|
||||
- FR-045: Form to register new products
|
||||
- FR-047: Set preferred target language
|
||||
- FR-048: Assign product owners
|
||||
|
||||
---
|
||||
|
||||
## POST /admin/products
|
||||
|
||||
Create a new product.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Form Data**:
|
||||
- `id` (string, required): Unique product identifier (URL-safe, lowercase, hyphens allowed)
|
||||
- `name` (string, required): Display name (1-100 characters)
|
||||
- `description` (string, optional): Description (max 500 characters)
|
||||
- `target_language` (string, required): ISO 639-1 language code
|
||||
- `submission_url_slug` (string, required): URL-safe slug (unique)
|
||||
- `assigned_owner_ids` (string[], required): At least one product owner ID
|
||||
|
||||
### Response
|
||||
|
||||
**Success (302 Redirect)**:
|
||||
```http
|
||||
HTTP/1.1 302 Found
|
||||
Location: /admin/products
|
||||
```
|
||||
|
||||
**Error (400 Bad Request)**: Validation failure
|
||||
```html
|
||||
Content-Type: text/html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Validation Error</h1>
|
||||
<ul>
|
||||
<li>Product ID must be unique</li>
|
||||
<li>Product ID must be URL-safe (lowercase, hyphens only)</li>
|
||||
<li>Submission URL slug must be unique</li>
|
||||
<li>At least one product owner must be assigned</li>
|
||||
<li>Target language must be valid ISO 639-1 code</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Side Effects
|
||||
|
||||
1. **File System**:
|
||||
- Creates `data/products/{product_id}/`
|
||||
- Writes `data/products/{product_id}/config.yaml`
|
||||
- Creates `data/products/{product_id}/feedback/` directory
|
||||
|
||||
2. **Config File** (`config.yaml`):
|
||||
```yaml
|
||||
id: "001-acme-app"
|
||||
name: "Acme Mobile App"
|
||||
description: "Customer feedback for Acme's flagship mobile application"
|
||||
target_language: "en"
|
||||
submission_url_slug: "acme-app"
|
||||
created_date: "2025-10-15"
|
||||
status: "active"
|
||||
assigned_owner_ids:
|
||||
- "owner-001"
|
||||
- "owner-002"
|
||||
statistics:
|
||||
total_feedback_count: 0
|
||||
last_submission: null
|
||||
```
|
||||
|
||||
### Functional Requirements Covered
|
||||
- FR-045: Register new products
|
||||
- FR-046: Unique product identifier
|
||||
- FR-047: Set target language
|
||||
- FR-048: Assign product owners
|
||||
- FR-049: Generate unique submission URL
|
||||
|
||||
---
|
||||
|
||||
## GET /admin/products/{product_id}/edit
|
||||
|
||||
Display form to edit an existing product.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Path Parameters**:
|
||||
- `product_id` (string, required): Product identifier
|
||||
|
||||
### Response
|
||||
|
||||
**Success (200 OK)**: Same form as create, pre-populated with existing values
|
||||
|
||||
**Error (404 Not Found)**: Product does not exist
|
||||
|
||||
### Functional Requirements Covered
|
||||
- FR-051: Update product details
|
||||
|
||||
---
|
||||
|
||||
## POST /admin/products/{product_id}
|
||||
|
||||
Update an existing product.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Path Parameters**:
|
||||
- `product_id` (string, required): Product identifier
|
||||
|
||||
**Form Data**: Same as POST /admin/products (except `id` is immutable)
|
||||
|
||||
### Response
|
||||
|
||||
**Success (302 Redirect)**:
|
||||
```http
|
||||
HTTP/1.1 302 Found
|
||||
Location: /admin/products
|
||||
```
|
||||
|
||||
**Error (400 Bad Request)**: Validation failure
|
||||
**Error (404 Not Found)**: Product does not exist
|
||||
|
||||
### Side Effects
|
||||
|
||||
- Updates `data/products/{product_id}/config.yaml`
|
||||
- Product `id` cannot be changed (immutable)
|
||||
- Changing `target_language` affects future feedback translations only (FR-048)
|
||||
|
||||
### Functional Requirements Covered
|
||||
- FR-048: Update product owner assignments
|
||||
- FR-051: Update product details
|
||||
|
||||
---
|
||||
|
||||
## POST /admin/products/{product_id}/archive
|
||||
|
||||
Archive a product (stop accepting new feedback).
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Path Parameters**:
|
||||
- `product_id` (string, required): Product identifier
|
||||
|
||||
### Response
|
||||
|
||||
**Success (302 Redirect)**:
|
||||
```http
|
||||
HTTP/1.1 302 Found
|
||||
Location: /admin/products
|
||||
```
|
||||
|
||||
**Error (404 Not Found)**: Product does not exist
|
||||
|
||||
### Side Effects
|
||||
|
||||
- Updates `data/products/{product_id}/config.yaml`: Sets `status: "archived"`
|
||||
- Submission form (GET /submit/{product_slug}) returns 404 for archived products (FR-053)
|
||||
- Historical feedback preserved (FR-052)
|
||||
|
||||
### Functional Requirements Covered
|
||||
- FR-052: Archive products without deleting feedback
|
||||
- FR-053: Prevent new submissions to archived products
|
||||
|
||||
---
|
||||
|
||||
## POST /admin/products/{product_id}/unarchive
|
||||
|
||||
Reactivate an archived product.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Path Parameters**:
|
||||
- `product_id` (string, required): Product identifier
|
||||
|
||||
### Response
|
||||
|
||||
**Success (302 Redirect)**:
|
||||
```http
|
||||
HTTP/1.1 302 Found
|
||||
Location: /admin/products
|
||||
```
|
||||
|
||||
### Side Effects
|
||||
|
||||
- Updates `data/products/{product_id}/config.yaml`: Sets `status: "active"`
|
||||
- Submission form becomes available again
|
||||
|
||||
### Functional Requirements Covered
|
||||
- Allow reversing archive operation (not explicitly in FR but useful)
|
||||
|
||||
---
|
||||
|
||||
## GET /admin/users
|
||||
|
||||
Display list of all users (product owners and admins).
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
### Response
|
||||
|
||||
**Success (200 OK)**:
|
||||
```html
|
||||
Content-Type: text/html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>User Management</h1>
|
||||
|
||||
<a href="/admin/users/new">+ Create New User</a>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Email</th>
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
<th>Assigned Products</th>
|
||||
<th>Last Login</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>owner-001</td>
|
||||
<td>jane.smith@example.com</td>
|
||||
<td>Jane Smith</td>
|
||||
<td>Product Owner</td>
|
||||
<td>2 products</td>
|
||||
<td>2025-10-15 09:23</td>
|
||||
<td>
|
||||
<a href="/admin/users/owner-001/edit">Edit</a> |
|
||||
<a href="/admin/users/owner-001/delete">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- More rows... -->
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Functional Requirements Covered
|
||||
- User management interface (implied by FR-048: assigning owners)
|
||||
|
||||
---
|
||||
|
||||
## GET /admin/users/new
|
||||
|
||||
Display form to create a new user.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
### Response
|
||||
|
||||
**Success (200 OK)**:
|
||||
```html
|
||||
Content-Type: text/html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Create New User</h1>
|
||||
<form method="POST" action="/admin/users">
|
||||
<label>Email:
|
||||
<input type="email" name="email" required>
|
||||
</label>
|
||||
|
||||
<label>Name:
|
||||
<input type="text" name="name" maxlength="100" required>
|
||||
</label>
|
||||
|
||||
<label>Password:
|
||||
<input type="password" name="password" minlength="8" required>
|
||||
</label>
|
||||
|
||||
<label>Role:
|
||||
<select name="role" required>
|
||||
<option value="product_owner">Product Owner</option>
|
||||
<option value="admin">Administrator</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<button type="submit">Create User</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /admin/users
|
||||
|
||||
Create a new user.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Form Data**:
|
||||
- `email` (string, required): Valid email address (unique)
|
||||
- `name` (string, required): Display name (1-100 characters)
|
||||
- `password` (string, required): Password (min 8 characters)
|
||||
- `role` (string, required): "product_owner" or "admin"
|
||||
|
||||
### Response
|
||||
|
||||
**Success (302 Redirect)**:
|
||||
```http
|
||||
HTTP/1.1 302 Found
|
||||
Location: /admin/users
|
||||
```
|
||||
|
||||
**Error (400 Bad Request)**: Validation failure
|
||||
```html
|
||||
Content-Type: text/html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Validation Error</h1>
|
||||
<ul>
|
||||
<li>Email must be unique</li>
|
||||
<li>Password must be at least 8 characters</li>
|
||||
<li>Invalid role specified</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Side Effects
|
||||
|
||||
- Appends new user to `data/users.yaml`
|
||||
- Password hashed with bcrypt (cost factor 12) before storage (FR-063)
|
||||
- Generates unique user ID (e.g., "owner-001", "admin-002")
|
||||
|
||||
### Functional Requirements Covered
|
||||
- FR-063: Secure password storage (bcrypt)
|
||||
- User creation for product owner assignment
|
||||
|
||||
---
|
||||
|
||||
## GET /admin/users/{user_id}/edit
|
||||
|
||||
Display form to edit an existing user.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Path Parameters**:
|
||||
- `user_id` (string, required): User identifier
|
||||
|
||||
### Response
|
||||
|
||||
**Success (200 OK)**: Same form as create, pre-populated (except password field empty)
|
||||
|
||||
**Error (404 Not Found)**: User does not exist
|
||||
|
||||
---
|
||||
|
||||
## POST /admin/users/{user_id}
|
||||
|
||||
Update an existing user.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Path Parameters**:
|
||||
- `user_id` (string, required): User identifier
|
||||
|
||||
**Form Data**:
|
||||
- `email` (string, required): Valid email address
|
||||
- `name` (string, required): Display name
|
||||
- `password` (string, optional): New password (if changing)
|
||||
- `role` (string, required): "product_owner" or "admin"
|
||||
|
||||
### Response
|
||||
|
||||
**Success (302 Redirect)**:
|
||||
```http
|
||||
HTTP/1.1 302 Found
|
||||
Location: /admin/users
|
||||
```
|
||||
|
||||
### Side Effects
|
||||
|
||||
- Updates user entry in `data/users.yaml`
|
||||
- If password provided, re-hash with bcrypt
|
||||
- Email and role can be updated
|
||||
|
||||
---
|
||||
|
||||
## POST /admin/users/{user_id}/delete
|
||||
|
||||
Delete a user.
|
||||
|
||||
### Request
|
||||
|
||||
**Authentication**: Required (admin role)
|
||||
|
||||
**Path Parameters**:
|
||||
- `user_id` (string, required): User identifier
|
||||
|
||||
### Response
|
||||
|
||||
**Success (302 Redirect)**:
|
||||
```http
|
||||
HTTP/1.1 302 Found
|
||||
Location: /admin/users
|
||||
```
|
||||
|
||||
**Error (400 Bad Request)**: Cannot delete self
|
||||
```html
|
||||
Content-Type: text/html
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Cannot Delete</h1>
|
||||
<p>You cannot delete your own account.</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Side Effects
|
||||
|
||||
- Removes user from `data/users.yaml`
|
||||
- User automatically unassigned from all products
|
||||
- Historical feedback metadata unchanged (no user tracking in feedback)
|
||||
|
||||
---
|
||||
|
||||
## Access Control
|
||||
|
||||
All admin endpoints enforce:
|
||||
1. User must be authenticated (session cookie)
|
||||
2. User role must be "admin"
|
||||
3. Otherwise: 403 Forbidden response
|
||||
|
||||
### Functional Requirements Covered
|
||||
- FR-045: Admin can register products
|
||||
- FR-048: Admin can assign product owners
|
||||
- FR-051: Admin can update products
|
||||
- FR-052: Admin can archive products
|
||||
@@ -0,0 +1,420 @@
|
||||
# Dashboard API Contract
|
||||
|
||||
**Scope**: Product owner dashboard endpoints (User Story P3)
|
||||
**Authentication**: Required (session-based via Flask-Login)
|
||||
|
||||
---
|
||||
|
||||
## 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).
|
||||
@@ -0,0 +1,219 @@
|
||||
# Submission API Contract
|
||||
|
||||
**Scope**: Anonymous feedback submission endpoints (User Story P1)
|
||||
**Authentication**: None (anonymous access)
|
||||
|
||||
---
|
||||
|
||||
## 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).
|
||||
@@ -0,0 +1,410 @@
|
||||
# Data Model: Anonymous Feedback Platform (Reklamator)
|
||||
|
||||
**Branch**: `001-build-an-application` | **Date**: 2025-10-15
|
||||
|
||||
This document defines the domain entities, their attributes, relationships, validation rules, and state transitions for the Reklamator application.
|
||||
|
||||
## Entity Overview
|
||||
|
||||
```
|
||||
Product (1) ----< (N) Feedback
|
||||
| |
|
||||
| |---< (N) Attachment
|
||||
| |
|
||||
| |---- (1) AnalysisResult
|
||||
|
|
||||
|----< (N) ProductOwner
|
||||
|
||||
Administrator (manages all entities)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity Definitions
|
||||
|
||||
### 1. Feedback Submission
|
||||
|
||||
**Description**: Represents a single feedback item submitted by an anonymous user.
|
||||
|
||||
**Storage Location**: `data/products/{product_id}/feedback/{feedback_id}/`
|
||||
|
||||
#### Attributes
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `id` | UUID v4 | Yes | Auto-generated | Unique identifier |
|
||||
| `product_id` | String | Yes | Must reference existing product | Associated product identifier |
|
||||
| `original_text` | String | Yes* | 1-10,000 characters | Original feedback text (*empty if file-only submission) |
|
||||
| `original_language` | String (ISO 639-1) | No | 2-char code | Detected language (e.g., "en", "de", "ja") |
|
||||
| `submission_timestamp` | ISO 8601 DateTime | Yes | Auto-generated | When feedback was submitted (UTC) |
|
||||
| `status` | Enum | Yes | See Status enum below | Current processing/review status |
|
||||
| `category` | Enum | No | See Category enum below | AI-assigned category (null if analysis pending/failed) |
|
||||
| `attachment_count` | Integer | Yes | 0-3 | Number of attached files |
|
||||
|
||||
#### Status Enum
|
||||
- `submitted` - Initial state after successful submission
|
||||
- `analyzing` - AI analysis in progress
|
||||
- `analysis_failed` - AI analysis encountered error
|
||||
- `analyzed` - AI analysis completed successfully
|
||||
- `reviewed` - Product owner has reviewed
|
||||
- `in_progress` - Product owner marked as being worked on
|
||||
- `resolved` - Product owner marked as resolved
|
||||
- `rejected` - Product owner marked as not actionable
|
||||
|
||||
#### Category Enum (AI-assigned)
|
||||
- `idea` - New concept or suggestion
|
||||
- `feature_request` - Request for specific functionality
|
||||
- `bug` - Problem or defect report
|
||||
- `complaint` - Negative feedback about existing functionality
|
||||
|
||||
#### Validation Rules
|
||||
- FR-002: `original_text` max length 10,000 characters
|
||||
- FR-011: At least one of (`original_text`, `attachment_count > 0`) must be true
|
||||
- FR-012: Cannot be empty (no text AND no attachments)
|
||||
- FR-027: `submission_timestamp` immutable after creation
|
||||
|
||||
#### State Transitions
|
||||
```
|
||||
submitted → analyzing → analyzed → {reviewed, in_progress, resolved, rejected}
|
||||
↓
|
||||
analysis_failed (terminal state until manual retry)
|
||||
```
|
||||
|
||||
#### File Representation (metadata.yaml)
|
||||
```yaml
|
||||
id: "a3f2c1d5-8b4e-4f1a-9c2d-7e6f5a4b3c2d"
|
||||
product_id: "001-acme-app"
|
||||
original_language: "en"
|
||||
submission_timestamp: "2025-10-15T14:32:10Z"
|
||||
status: "analyzed"
|
||||
category: "bug"
|
||||
attachment_count: 2
|
||||
attachments:
|
||||
- filename: "screenshot.png"
|
||||
size_bytes: 245678
|
||||
mime_type: "image/png"
|
||||
- filename: "error_log.txt"
|
||||
size_bytes: 1234
|
||||
mime_type: "text/plain"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Product/Service
|
||||
|
||||
**Description**: Represents a product or service for which feedback can be collected.
|
||||
|
||||
**Storage Location**: `data/products/{product_id}/config.yaml`
|
||||
|
||||
#### Attributes
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `id` | String | Yes | Unique, URL-safe slug | Product identifier (e.g., "001-acme-app") |
|
||||
| `name` | String | Yes | 1-100 characters | Display name |
|
||||
| `description` | String | No | Max 500 characters | Product description |
|
||||
| `target_language` | String (ISO 639-1) | Yes | 2-char code | Preferred language for AI translations |
|
||||
| `submission_url_slug` | String | Yes | URL-safe, unique | URL path for submission form (e.g., "/submit/acme-app") |
|
||||
| `created_date` | ISO 8601 Date | Yes | Auto-generated | When product was registered |
|
||||
| `status` | Enum | Yes | "active" or "archived" | Current status |
|
||||
| `assigned_owner_ids` | List[String] | No | Must reference existing users | Product owner user IDs |
|
||||
|
||||
#### Validation Rules
|
||||
- FR-046: `id` must be unique across all products
|
||||
- FR-047: `target_language` must be valid ISO 639-1 code
|
||||
- FR-049: `submission_url_slug` must be unique and URL-safe (alphanumeric + hyphens)
|
||||
- FR-053: Cannot accept new feedback if `status` is "archived"
|
||||
|
||||
#### State Transitions
|
||||
```
|
||||
active ⇄ archived (bidirectional, admin only)
|
||||
```
|
||||
|
||||
#### File Representation (config.yaml)
|
||||
```yaml
|
||||
id: "001-acme-app"
|
||||
name: "Acme Mobile App"
|
||||
description: "Customer feedback for Acme's flagship mobile application"
|
||||
target_language: "en"
|
||||
submission_url_slug: "acme-app"
|
||||
created_date: "2025-10-01"
|
||||
status: "active"
|
||||
assigned_owner_ids:
|
||||
- "owner-001"
|
||||
- "owner-002"
|
||||
statistics:
|
||||
total_feedback_count: 127
|
||||
last_submission: "2025-10-15T14:32:10Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Analysis Result
|
||||
|
||||
**Description**: Represents the AI-generated analysis of a feedback submission.
|
||||
|
||||
**Storage Location**: `data/products/{product_id}/feedback/{feedback_id}/analysis.md`
|
||||
**Additional Storage**: Original text stored in `content.txt` for reference
|
||||
|
||||
#### Attributes (Markdown Format)
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `translated_text` | Markdown | Yes* | Feedback translated to target language (*if different from original) |
|
||||
| `summary` | Markdown | Yes | Concise 2-3 sentence summary in target language |
|
||||
| `detected_category` | Enum | Yes | Category assigned by AI (idea/feature_request/bug/complaint) |
|
||||
| `confidence_score` | Float (0.0-1.0) | Yes | AI confidence in categorization |
|
||||
| `analysis_timestamp` | ISO 8601 DateTime | Yes | When analysis completed |
|
||||
| `model_used` | String | Yes | AI model identifier (e.g., "claude-3-haiku-20240307") |
|
||||
| `error_message` | String | No | Error details if analysis failed |
|
||||
|
||||
#### Validation Rules
|
||||
- FR-014: `summary` should be 2-3 sentences maximum
|
||||
- FR-015: `translated_text` required unless original language = target language
|
||||
- FR-016: Original text preserved in `content.txt` alongside analysis
|
||||
- FR-017: Detected language stored in feedback metadata.yaml `original_language`
|
||||
|
||||
#### File Representation (analysis.md)
|
||||
```markdown
|
||||
# Feedback Analysis
|
||||
|
||||
**Analyzed**: 2025-10-15T14:35:22Z
|
||||
**Model**: claude-3-haiku-20240307
|
||||
**Category**: bug (confidence: 0.92)
|
||||
**Original Language**: de → **Target Language**: en
|
||||
|
||||
## Summary
|
||||
|
||||
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.
|
||||
|
||||
## Translation
|
||||
|
||||
**Original (German):**
|
||||
> 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.
|
||||
|
||||
**Translated (English):**
|
||||
The app crashes when I try to upload large files. Every time I upload a PDF over 5MB, the app freezes and closes.
|
||||
|
||||
## Attachments
|
||||
|
||||
- screenshot.png (245 KB)
|
||||
- error_log.txt (1 KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Attachment
|
||||
|
||||
**Description**: Represents a file (document or image) uploaded with feedback.
|
||||
|
||||
**Storage Location**: `data/products/{product_id}/feedback/{feedback_id}/attachments/{filename}`
|
||||
|
||||
#### Attributes (stored in feedback metadata.yaml)
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `filename` | String | Yes | Sanitized, max 255 chars | Original filename (sanitized for safety) |
|
||||
| `size_bytes` | Integer | Yes | Max 10,485,760 (10MB) | File size in bytes |
|
||||
| `mime_type` | String | Yes | See allowed types | Validated MIME type |
|
||||
| `upload_timestamp` | ISO 8601 DateTime | Yes | Auto-generated | When file was uploaded |
|
||||
| `virus_scan_status` | Enum | Yes | "clean" or "infected" | ClamAV scan result |
|
||||
|
||||
#### Allowed MIME Types
|
||||
- Documents: `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document` (DOCX), `text/plain`
|
||||
- Images: `image/jpeg`, `image/png`, `image/gif`, `image/webp`
|
||||
|
||||
#### Validation Rules
|
||||
- FR-004: Maximum 3 attachments per feedback
|
||||
- FR-006: Maximum 10MB per file
|
||||
- FR-026: Preserve original filename (sanitized)
|
||||
- FR-059: Sanitize filename to prevent directory traversal
|
||||
- FR-060: Must pass ClamAV virus scan before storage
|
||||
|
||||
#### Security Sanitization
|
||||
- Remove directory traversal patterns: `../`, `..\\`, absolute paths
|
||||
- Replace unsafe characters: `<>:"|?*`
|
||||
- Limit filename length to 255 characters
|
||||
- If duplicate filename, append counter: `file.pdf` → `file_2.pdf`
|
||||
|
||||
---
|
||||
|
||||
### 5. Product Owner
|
||||
|
||||
**Description**: Represents an authenticated user responsible for reviewing feedback for one or more products.
|
||||
|
||||
**Storage Location**: `data/users.yaml`
|
||||
|
||||
#### Attributes
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `id` | String | Yes | Unique | User identifier (e.g., "owner-001") |
|
||||
| `email` | String | Yes | Valid email, unique | Login email address |
|
||||
| `password_hash` | String (bcrypt) | Yes | bcrypt format | Hashed password (never store plaintext) |
|
||||
| `name` | String | Yes | 1-100 characters | Display name |
|
||||
| `role` | Enum | Yes | "product_owner" | User role (always "product_owner" for this entity) |
|
||||
| `assigned_product_ids` | List[String] | Yes | Must reference existing products | Products this owner can access |
|
||||
| `created_date` | ISO 8601 Date | Yes | Auto-generated | Account creation date |
|
||||
| `last_login` | ISO 8601 DateTime | No | Auto-updated | Last successful login |
|
||||
|
||||
#### Validation Rules
|
||||
- FR-048: Can be assigned to multiple products
|
||||
- FR-056: Must authenticate to access dashboard
|
||||
- FR-063: Passwords hashed with bcrypt (cost factor 12)
|
||||
- FR-033: Can only view feedback for `assigned_product_ids`
|
||||
|
||||
#### File Representation (users.yaml entry)
|
||||
```yaml
|
||||
users:
|
||||
- id: "owner-001"
|
||||
email: "jane.smith@example.com"
|
||||
password_hash: "$2b$12$KIXxBt5H4vE2zT9vN8FqOe9Jx..."
|
||||
name: "Jane Smith"
|
||||
role: "product_owner"
|
||||
assigned_product_ids:
|
||||
- "001-acme-app"
|
||||
- "002-beta-service"
|
||||
created_date: "2025-09-15"
|
||||
last_login: "2025-10-15T09:23:11Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Administrator
|
||||
|
||||
**Description**: Represents a privileged user who can register products, assign owners, and manage system configuration.
|
||||
|
||||
**Storage Location**: `data/users.yaml` (same file as Product Owners)
|
||||
|
||||
#### Attributes
|
||||
|
||||
| Field | Type | Required | Validation | Description |
|
||||
|-------|------|----------|------------|-------------|
|
||||
| `id` | String | Yes | Unique | User identifier (e.g., "admin-001") |
|
||||
| `email` | String | Yes | Valid email, unique | Login email address |
|
||||
| `password_hash` | String (bcrypt) | Yes | bcrypt format | Hashed password |
|
||||
| `name` | String | Yes | 1-100 characters | Display name |
|
||||
| `role` | Enum | Yes | "admin" | User role (always "admin" for this entity) |
|
||||
| `assigned_product_ids` | List[String] | Yes | Empty list | Empty = access to all products |
|
||||
| `created_date` | ISO 8601 Date | Yes | Auto-generated | Account creation date |
|
||||
| `last_login` | ISO 8601 DateTime | No | Auto-updated | Last successful login |
|
||||
|
||||
#### Validation Rules
|
||||
- Admin role grants full access regardless of `assigned_product_ids`
|
||||
- FR-045: Can create/modify/archive products
|
||||
- FR-048: Can assign/unassign product owners
|
||||
- Same authentication requirements as Product Owner (FR-056, FR-063)
|
||||
|
||||
#### File Representation (users.yaml entry)
|
||||
```yaml
|
||||
users:
|
||||
- id: "admin-001"
|
||||
email: "admin@reklamator.local"
|
||||
password_hash: "$2b$12$vL3Fx9..."
|
||||
name: "System Administrator"
|
||||
role: "admin"
|
||||
assigned_product_ids: [] # Empty = all access
|
||||
created_date: "2025-09-01"
|
||||
last_login: "2025-10-15T10:45:33Z"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Domain Rules & Invariants
|
||||
|
||||
### Cross-Entity Rules
|
||||
|
||||
1. **Product-Feedback Relationship** (1:N)
|
||||
- Every Feedback must reference exactly one valid Product
|
||||
- Product can have zero or many Feedback items
|
||||
- Archived products cannot receive new feedback (FR-053)
|
||||
|
||||
2. **Feedback-Attachment Relationship** (1:N)
|
||||
- Feedback can have 0-3 Attachments (FR-004, FR-010)
|
||||
- Attachments cannot exist without parent Feedback (cascade delete)
|
||||
|
||||
3. **Feedback-AnalysisResult Relationship** (1:1)
|
||||
- Every analyzed Feedback has exactly one AnalysisResult
|
||||
- AnalysisResult created asynchronously after Feedback submission
|
||||
- Original content preserved even if analysis fails (FR-020)
|
||||
|
||||
4. **Product-Owner Relationship** (N:M)
|
||||
- Product can have 1 or more assigned Product Owners (FR-048)
|
||||
- Product Owner can be assigned to multiple Products
|
||||
- Admin users bypass assignment logic (implicit access to all)
|
||||
|
||||
5. **Anonymity Constraint** (Global)
|
||||
- No IP addresses stored in Feedback metadata (FR-055, SC-010)
|
||||
- No session tracking for anonymous submissions
|
||||
- Rate limiting uses IP for abuse prevention only (not persisted)
|
||||
|
||||
### Deletion Rules
|
||||
|
||||
- **Feedback Deletion**: Deletes metadata.yaml, content.txt, analysis.md, and all attachments/
|
||||
- **Product Archival**: Sets status to "archived", preserves all feedback (FR-052)
|
||||
- **Product Owner Removal**: Unassigns from products, does not delete feedback
|
||||
- **Cascade Protection**: Cannot delete Product with active feedback (archive instead)
|
||||
|
||||
---
|
||||
|
||||
## Storage Implementation Notes
|
||||
|
||||
### Directory Structure Example
|
||||
|
||||
```
|
||||
data/
|
||||
├── users.yaml # All users (admins + owners)
|
||||
└── products/
|
||||
├── 001-acme-app/
|
||||
│ ├── config.yaml # Product metadata
|
||||
│ └── feedback/
|
||||
│ ├── a3f2c1d5-8b4e-.../
|
||||
│ │ ├── metadata.yaml # Feedback + attachment metadata
|
||||
│ │ ├── content.txt # Original feedback text
|
||||
│ │ ├── analysis.md # AI analysis report
|
||||
│ │ └── attachments/
|
||||
│ │ ├── screenshot.png
|
||||
│ │ └── error_log.txt
|
||||
│ └── b7e1f3d2-4a9c-.../
|
||||
│ ├── metadata.yaml
|
||||
│ ├── content.txt
|
||||
│ └── analysis.md # No attachments/ for this one
|
||||
└── 002-beta-service/
|
||||
├── config.yaml
|
||||
└── feedback/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### File Format Standards
|
||||
|
||||
- **YAML**: UTF-8 encoding, 2-space indentation, explicit type declarations
|
||||
- **Markdown**: CommonMark specification, UTF-8 encoding
|
||||
- **Text Files**: UTF-8 encoding with BOM handling
|
||||
|
||||
### Indexing Strategy (Performance)
|
||||
|
||||
For dashboard performance (SC-008, SC-014):
|
||||
- Cache product feedback counts in `config.yaml` statistics
|
||||
- Implement pagination (50 items per page)
|
||||
- Load metadata.yaml only, defer analysis.md loading until detail view
|
||||
- File modification times used for sorting (newest first per FR-041)
|
||||
|
||||
---
|
||||
|
||||
## Validation Summary by Functional Requirement
|
||||
|
||||
| FR | Validation Location | Rule |
|
||||
|----|---------------------|------|
|
||||
| FR-002 | Feedback.original_text | Max 10,000 characters |
|
||||
| FR-004 | Feedback.attachment_count | 0-3 attachments |
|
||||
| FR-006 | Attachment.size_bytes | Max 10MB per file |
|
||||
| FR-011 | Feedback validation | At least text OR files required |
|
||||
| FR-012 | Feedback validation | Cannot be completely empty |
|
||||
| FR-046 | Product.id | Must be unique |
|
||||
| FR-048 | Product.assigned_owner_ids | 1+ owners required |
|
||||
| FR-053 | Product status check | Reject if archived |
|
||||
| FR-063 | User.password_hash | bcrypt with cost 12 |
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**: Define API contracts in `/contracts/` directory
|
||||
@@ -0,0 +1,188 @@
|
||||
# Implementation Plan: Anonymous Feedback Platform (Reklamator)
|
||||
|
||||
**Branch**: `001-build-an-application` | **Date**: 2025-10-15 | **Spec**: [spec.md](./spec.md)
|
||||
**Input**: Feature specification from `/specs/001-build-an-application/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
Build a minimal web application using Flask that enables anonymous feedback submission with AI-powered analysis and translation. The system uses a file-based storage approach with folders for each submission, YAML metadata files, and markdown-formatted AI analysis reports. Product owners access analyzed feedback through an authenticated web dashboard. Design prioritizes simplicity and functionality over aesthetics - plain HTML without CSS frameworks or JavaScript libraries.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Python 3.11+
|
||||
**Primary Dependencies**: Flask (web framework), no CSS frameworks, no JavaScript libraries
|
||||
**Storage**: File-based - folders per feedback item with YAML metadata and markdown reports
|
||||
**Testing**: pytest (contract and integration tests prioritized per constitution)
|
||||
**Target Platform**: Linux server (web application)
|
||||
**Project Type**: web (backend + frontend, but minimal frontend without frameworks)
|
||||
**AI Integration**: NEEDS CLARIFICATION - Claude API or pluggable AI provider interface
|
||||
**Performance Goals**: Handle 100 concurrent submissions, <3s dashboard load for 1000 items
|
||||
**Constraints**: <30s AI analysis time for 95% of submissions, complete anonymity (no IP/session tracking)
|
||||
**Scale/Scope**: MVP supports 100 products, 10,000 feedback items per product, 50+ languages
|
||||
**File Upload**: NEEDS CLARIFICATION - malware scanning approach, storage location strategy
|
||||
**Authentication**: NEEDS CLARIFICATION - session management approach for product owners
|
||||
**Rate Limiting**: NEEDS CLARIFICATION - implementation strategy for submission abuse prevention
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
### ✅ I. Specification-First Development
|
||||
**Status**: PASS
|
||||
Complete specification exists at `specs/001-build-an-application/spec.md` with prioritized user stories (P1-P4), 64 functional requirements with unique IDs (FR-001 to FR-064), measurable success criteria (SC-001 to SC-014), and comprehensive edge cases. All user stories are independently testable.
|
||||
|
||||
### ✅ II. Test-First Discipline
|
||||
**Status**: PASS (Will be enforced during implementation)
|
||||
Plan includes pytest as testing framework. Implementation phase will follow mandatory workflow: write tests → verify failures → implement code → refactor. Contract and integration tests prioritized per constitution.
|
||||
|
||||
### ✅ III. Independent User Stories
|
||||
**Status**: PASS
|
||||
Four user stories explicitly prioritized (P1: Anonymous Submission, P2: AI Analysis, P3: Dashboard, P4: Product Management). Each story is independently deliverable and testable. P1 can function standalone, P2 depends only on P1, P3 on P1+P2, P4 adds multi-product support.
|
||||
|
||||
### ✅ IV. Simplicity & Justification
|
||||
**Status**: PASS
|
||||
Design explicitly minimizes complexity: plain HTML without CSS frameworks, no JavaScript libraries, file-based storage (no database), Flask for web framework (minimal dependencies). User input emphasizes "designed as simple as possible" and "functionality over design."
|
||||
|
||||
**Potential Complexity Point**: File-based storage vs. database
|
||||
- **Decision**: File-based storage with folder-per-feedback structure
|
||||
- **Rationale**: Simpler deployment, no database setup/maintenance, natural fit for storing files+metadata together, sufficient for MVP scale (100 products × 10k items)
|
||||
- **Alternative Rejected**: PostgreSQL/SQLite - adds operational complexity, requires schema migrations, doesn't simplify file attachment handling
|
||||
|
||||
### ✅ V. Documentation as Code
|
||||
**Status**: PASS
|
||||
Specification-driven workflow with all documentation in version control under `/specs/001-build-an-application/`. This plan will generate: research.md, data-model.md, contracts/, quickstart.md per constitution requirements.
|
||||
|
||||
### Gate Result: ✅ PASS - Proceed to Phase 0 Research
|
||||
|
||||
No constitutional violations detected. Complexity Tracking table remains empty.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```
|
||||
specs/[###-feature]/
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```
|
||||
reklamator/
|
||||
├── app/
|
||||
│ ├── __init__.py # Flask app factory
|
||||
│ ├── routes/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── submission.py # Anonymous feedback submission endpoints
|
||||
│ │ ├── dashboard.py # Product owner dashboard endpoints
|
||||
│ │ └── admin.py # Product/owner management endpoints
|
||||
│ ├── services/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── feedback_storage.py # File-based storage operations
|
||||
│ │ ├── ai_analyzer.py # AI analysis/translation interface
|
||||
│ │ └── auth.py # Session management
|
||||
│ ├── models/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── feedback.py # Feedback domain model
|
||||
│ │ ├── product.py # Product domain model
|
||||
│ │ └── user.py # Product owner/admin model
|
||||
│ ├── templates/ # Plain HTML templates (Jinja2)
|
||||
│ │ ├── submission_form.html
|
||||
│ │ ├── dashboard.html
|
||||
│ │ ├── feedback_detail.html
|
||||
│ │ └── admin_products.html
|
||||
│ └── utils/
|
||||
│ ├── __init__.py
|
||||
│ ├── file_validator.py # File upload validation
|
||||
│ └── rate_limiter.py # Submission rate limiting
|
||||
│
|
||||
├── data/ # File-based storage root
|
||||
│ └── products/
|
||||
│ └── {product-id}/
|
||||
│ └── feedback/
|
||||
│ └── {feedback-id}/
|
||||
│ ├── metadata.yaml
|
||||
│ ├── analysis.md
|
||||
│ └── attachments/
|
||||
│
|
||||
├── tests/
|
||||
│ ├── contract/ # API contract tests
|
||||
│ │ ├── test_submission_api.py
|
||||
│ │ ├── test_dashboard_api.py
|
||||
│ │ └── test_admin_api.py
|
||||
│ ├── integration/ # User journey tests
|
||||
│ │ ├── test_feedback_submission_flow.py
|
||||
│ │ ├── test_ai_analysis_flow.py
|
||||
│ │ └── test_dashboard_access_flow.py
|
||||
│ └── unit/ # Optional unit tests for complex logic
|
||||
│ ├── test_feedback_storage.py
|
||||
│ └── test_file_validator.py
|
||||
│
|
||||
├── config/
|
||||
│ ├── development.py
|
||||
│ ├── production.py
|
||||
│ └── testing.py
|
||||
│
|
||||
├── requirements.txt
|
||||
├── pytest.ini
|
||||
└── run.py # Application entry point
|
||||
```
|
||||
|
||||
**Structure Decision**: Selected web application structure (Option 2 variant) with backend-focused layout since frontend is minimal (plain HTML templates). Flask follows a single-project structure but organized by layers (routes/services/models). The `data/` directory implements the file-based storage requirement with nested folders per product and feedback item. Templates directory contains plain HTML served by Flask without separate frontend build process.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
*Fill ONLY if Constitution Check has violations that must be justified*
|
||||
|
||||
No violations detected. Table remains empty.
|
||||
|
||||
---
|
||||
|
||||
## Post-Design Constitution Re-Check
|
||||
|
||||
*Re-evaluated after Phase 1 design completion*
|
||||
|
||||
### ✅ I. Specification-First Development
|
||||
**Status**: PASS (unchanged)
|
||||
Design artifacts (research.md, data-model.md, contracts/, quickstart.md) generated from specification. No implementation code written yet.
|
||||
|
||||
### ✅ II. Test-First Discipline
|
||||
**Status**: PASS (unchanged)
|
||||
API contracts define testable behaviors. Contract tests can be written before implementation. Quickstart guide includes test-first workflow examples.
|
||||
|
||||
### ✅ III. Independent User Stories
|
||||
**Status**: PASS (unchanged)
|
||||
Data model and API contracts support independent implementation of P1→P2→P3→P4 stories. Each has clear endpoints and data structures.
|
||||
|
||||
### ✅ IV. Simplicity & Justification
|
||||
**Status**: PASS (confirmed post-design)
|
||||
- File-based storage design confirmed (YAML + Markdown)
|
||||
- No database complexity introduced
|
||||
- Minimal dependencies: Flask + 7 small extensions
|
||||
- Plain HTML templates (no CSS frameworks, no JavaScript)
|
||||
- Direct file I/O (no ORM or abstraction layers)
|
||||
- Single-project structure (no microservices)
|
||||
|
||||
**Design Review**: All research decisions favor simplicity. No new complexity introduced during Phase 1.
|
||||
|
||||
### ✅ V. Documentation as Code
|
||||
**Status**: PASS (enhanced)
|
||||
Generated artifacts:
|
||||
- ✅ research.md (7 decision records)
|
||||
- ✅ data-model.md (6 entities fully specified)
|
||||
- ✅ contracts/ (3 API contract documents: submission, dashboard, admin)
|
||||
- ✅ quickstart.md (developer onboarding guide)
|
||||
- ✅ CLAUDE.md (agent context updated)
|
||||
|
||||
All documentation version-controlled, linked to spec.md.
|
||||
|
||||
### Final Gate Result: ✅ PASS - Ready for Phase 2 (Task Generation)
|
||||
|
||||
No constitutional violations introduced during design phase. Proceed to `/speckit.tasks` command.
|
||||
@@ -0,0 +1,469 @@
|
||||
# Quickstart Guide: Reklamator Development
|
||||
|
||||
**Branch**: `001-build-an-application` | **Date**: 2025-10-15
|
||||
|
||||
This guide helps developers set up the Reklamator development environment and understand the project structure.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.11 or higher
|
||||
- ClamAV daemon (`clamd`) for malware scanning
|
||||
- Git
|
||||
- Virtual environment tool (venv)
|
||||
|
||||
---
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd reklamator
|
||||
git checkout 001-build-an-application
|
||||
```
|
||||
|
||||
### 2. Create Virtual Environment
|
||||
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
```
|
||||
|
||||
### 3. Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**Expected Core Dependencies**:
|
||||
- Flask 3.0+
|
||||
- Flask-Login (session management)
|
||||
- Flask-Limiter (rate limiting)
|
||||
- Flask-WTF (CSRF protection)
|
||||
- anthropic (Claude API client)
|
||||
- clamd (ClamAV integration)
|
||||
- bcrypt (password hashing)
|
||||
- PyYAML (configuration files)
|
||||
- pytest, pytest-flask (testing)
|
||||
|
||||
### 4. Install and Configure ClamAV
|
||||
|
||||
**Ubuntu/Debian**:
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install clamav clamav-daemon
|
||||
sudo systemctl start clamav-daemon
|
||||
sudo systemctl enable clamav-daemon
|
||||
```
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
brew install clamav
|
||||
brew services start clamav
|
||||
```
|
||||
|
||||
**Verify ClamAV is running**:
|
||||
```bash
|
||||
clamdscan --version
|
||||
```
|
||||
|
||||
### 5. Set Up Environment Variables
|
||||
|
||||
Create `.env` file in project root:
|
||||
|
||||
```bash
|
||||
# Flask Configuration
|
||||
FLASK_APP=run.py
|
||||
FLASK_ENV=development
|
||||
SECRET_KEY=your-secret-key-here-change-in-production
|
||||
|
||||
# Claude API
|
||||
ANTHROPIC_API_KEY=your-claude-api-key-here
|
||||
|
||||
# ClamAV
|
||||
CLAMD_SOCKET=/var/run/clamav/clamd.ctl # Adjust path for your system
|
||||
|
||||
# File Storage
|
||||
DATA_DIR=./data
|
||||
|
||||
# Rate Limiting
|
||||
RATE_LIMIT_ENABLED=true
|
||||
RATE_LIMIT_PER_HOUR=10
|
||||
```
|
||||
|
||||
**Get Claude API Key**:
|
||||
1. Sign up at https://console.anthropic.com/
|
||||
2. Create an API key
|
||||
3. Add to `.env` file
|
||||
|
||||
### 6. Initialize Data Directory
|
||||
|
||||
```bash
|
||||
mkdir -p data/products
|
||||
```
|
||||
|
||||
### 7. Create Initial Admin User
|
||||
|
||||
Create `data/users.yaml`:
|
||||
|
||||
```yaml
|
||||
users:
|
||||
- id: "admin-001"
|
||||
email: "admin@localhost"
|
||||
password_hash: "$2b$12$KIXxBt5H4vE2zT9vN8FqOe9JxwLxPqz0q5kYv2Z3j4RQvN8FqOe9J" # Password: "admin123"
|
||||
name: "Admin User"
|
||||
role: "admin"
|
||||
assigned_product_ids: []
|
||||
created_date: "2025-10-15"
|
||||
last_login: null
|
||||
```
|
||||
|
||||
**Security Note**: Change the password immediately after first login!
|
||||
|
||||
To generate a new password hash:
|
||||
```python
|
||||
import bcrypt
|
||||
password = "your-password-here"
|
||||
hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))
|
||||
print(hash.decode('utf-8'))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running the Application
|
||||
|
||||
### Development Server
|
||||
|
||||
```bash
|
||||
python run.py
|
||||
```
|
||||
|
||||
Application will be available at: http://localhost:5000
|
||||
|
||||
### Production Server (Gunicorn)
|
||||
|
||||
```bash
|
||||
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure Overview
|
||||
|
||||
```
|
||||
reklamator/
|
||||
├── app/ # Application code
|
||||
│ ├── __init__.py # Flask app factory
|
||||
│ ├── routes/ # HTTP endpoints
|
||||
│ │ ├── submission.py # Anonymous feedback submission
|
||||
│ │ ├── dashboard.py # Product owner dashboard
|
||||
│ │ └── admin.py # Admin interface
|
||||
│ ├── services/ # Business logic
|
||||
│ │ ├── feedback_storage.py # File-based storage operations
|
||||
│ │ ├── ai_analyzer.py # AI analysis/translation
|
||||
│ │ └── auth.py # Authentication
|
||||
│ ├── models/ # Domain models
|
||||
│ │ ├── feedback.py # Feedback entity
|
||||
│ │ ├── product.py # Product entity
|
||||
│ │ └── user.py # User entity
|
||||
│ ├── templates/ # HTML templates (Jinja2)
|
||||
│ └── utils/ # Utilities
|
||||
│ ├── file_validator.py # File upload validation
|
||||
│ └── rate_limiter.py # Rate limiting
|
||||
│
|
||||
├── data/ # File-based storage
|
||||
│ ├── users.yaml # User accounts
|
||||
│ └── products/ # Product-specific data
|
||||
│ └── {product-id}/
|
||||
│ ├── config.yaml # Product metadata
|
||||
│ └── feedback/
|
||||
│ └── {feedback-id}/
|
||||
│ ├── metadata.yaml
|
||||
│ ├── content.txt
|
||||
│ ├── analysis.md
|
||||
│ └── attachments/
|
||||
│
|
||||
├── tests/ # Test suite
|
||||
│ ├── contract/ # API contract tests
|
||||
│ ├── integration/ # User journey tests
|
||||
│ └── unit/ # Unit tests
|
||||
│
|
||||
├── config/ # Configuration files
|
||||
│ ├── development.py
|
||||
│ ├── production.py
|
||||
│ └── testing.py
|
||||
│
|
||||
├── specs/ # Feature specifications (this directory)
|
||||
├── requirements.txt
|
||||
├── pytest.ini
|
||||
├── .env # Environment variables (not in git)
|
||||
└── run.py # Application entry point
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Creating a Test Product
|
||||
|
||||
1. Log in as admin: http://localhost:5000/login
|
||||
- Email: `admin@localhost`
|
||||
- Password: `admin123`
|
||||
|
||||
2. Navigate to: http://localhost:5000/admin/products
|
||||
|
||||
3. Click "Create New Product" and fill in:
|
||||
- ID: `001-test-product`
|
||||
- Name: `Test Product`
|
||||
- Target Language: `en`
|
||||
- Submission URL Slug: `test-product`
|
||||
- Assign yourself as product owner
|
||||
|
||||
4. Access submission form: http://localhost:5000/submit/test-product
|
||||
|
||||
### Submitting Test Feedback
|
||||
|
||||
1. Visit: http://localhost:5000/submit/test-product
|
||||
2. Enter feedback text
|
||||
3. Optionally attach files (max 3, max 10MB each)
|
||||
4. Submit
|
||||
|
||||
Feedback will be processed asynchronously. Check the dashboard to view analysis results.
|
||||
|
||||
### Viewing Feedback in Dashboard
|
||||
|
||||
1. Log in: http://localhost:5000/login
|
||||
2. Dashboard: http://localhost:5000/dashboard
|
||||
3. Click on feedback item to view details
|
||||
|
||||
### Running Tests
|
||||
|
||||
**All tests**:
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
**Contract tests only**:
|
||||
```bash
|
||||
pytest tests/contract/
|
||||
```
|
||||
|
||||
**Integration tests only**:
|
||||
```bash
|
||||
pytest tests/integration/
|
||||
```
|
||||
|
||||
**With coverage**:
|
||||
```bash
|
||||
pytest --cov=app --cov-report=html
|
||||
```
|
||||
|
||||
**Test-first workflow** (per constitution):
|
||||
1. Write test for new feature (should fail)
|
||||
2. Run test to verify failure
|
||||
3. Implement feature
|
||||
4. Run test to verify success
|
||||
5. Refactor if needed
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints Reference
|
||||
|
||||
### Anonymous Submission
|
||||
- `GET /submit/{product_slug}` - Submission form
|
||||
- `POST /submit/{product_slug}` - Submit feedback
|
||||
|
||||
### Authentication
|
||||
- `GET /login` - Login form
|
||||
- `POST /login` - Authenticate
|
||||
- `GET /logout` - Log out
|
||||
|
||||
### Dashboard (Product Owners)
|
||||
- `GET /dashboard` - Feedback list (with filters)
|
||||
- `GET /feedback/{feedback_id}` - Feedback detail
|
||||
- `POST /feedback/{feedback_id}/status` - Update status
|
||||
- `GET /feedback/{feedback_id}/attachment/{filename}` - Download attachment
|
||||
|
||||
### Admin
|
||||
- `GET /admin/products` - List products
|
||||
- `GET /admin/products/new` - Create product form
|
||||
- `POST /admin/products` - Create product
|
||||
- `GET /admin/products/{id}/edit` - Edit product form
|
||||
- `POST /admin/products/{id}` - Update product
|
||||
- `POST /admin/products/{id}/archive` - Archive product
|
||||
- `GET /admin/users` - List users
|
||||
- `POST /admin/users` - Create user
|
||||
- `POST /admin/users/{id}` - Update user
|
||||
|
||||
Full API contracts: See `/specs/001-build-an-application/contracts/`
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Development Configuration (`config/development.py`)
|
||||
|
||||
```python
|
||||
DEBUG = True
|
||||
TESTING = False
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key')
|
||||
DATA_DIR = os.environ.get('DATA_DIR', './data')
|
||||
ANTHROPIC_API_KEY = os.environ.get('ANTHROPIC_API_KEY')
|
||||
CLAMD_SOCKET = os.environ.get('CLAMD_SOCKET', '/var/run/clamav/clamd.ctl')
|
||||
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10MB max upload
|
||||
RATE_LIMIT_ENABLED = True
|
||||
RATE_LIMIT_PER_HOUR = 10
|
||||
```
|
||||
|
||||
### Production Configuration (`config/production.py`)
|
||||
|
||||
```python
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') # Required, no default
|
||||
DATA_DIR = os.environ.get('DATA_DIR', '/var/lib/reklamator/data')
|
||||
ANTHROPIC_API_KEY = os.environ.get('ANTHROPIC_API_KEY') # Required
|
||||
CLAMD_SOCKET = os.environ.get('CLAMD_SOCKET', '/var/run/clamav/clamd.ctl')
|
||||
MAX_CONTENT_LENGTH = 10 * 1024 * 1024
|
||||
RATE_LIMIT_ENABLED = True
|
||||
RATE_LIMIT_PER_HOUR = 10
|
||||
SESSION_COOKIE_SECURE = True # HTTPS only
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### ClamAV Connection Error
|
||||
|
||||
**Error**: `pyclamd.ConnectionError: Could not connect to clamd`
|
||||
|
||||
**Solution**:
|
||||
1. Verify ClamAV is running: `sudo systemctl status clamav-daemon`
|
||||
2. Check socket path: `ls /var/run/clamav/clamd.ctl`
|
||||
3. Update `CLAMD_SOCKET` in `.env` if needed
|
||||
4. Restart ClamAV: `sudo systemctl restart clamav-daemon`
|
||||
|
||||
### Claude API Error
|
||||
|
||||
**Error**: `anthropic.APIError: Invalid API key`
|
||||
|
||||
**Solution**:
|
||||
1. Verify API key in `.env` file
|
||||
2. Check key is active at https://console.anthropic.com/
|
||||
3. Ensure no extra whitespace in key
|
||||
|
||||
### File Upload Fails
|
||||
|
||||
**Error**: `413 Payload Too Large`
|
||||
|
||||
**Solution**:
|
||||
- Check file size (max 10MB per file)
|
||||
- Check total payload size (3 files + form data)
|
||||
- Verify `MAX_CONTENT_LENGTH` in config
|
||||
|
||||
**Error**: `Unsupported file type`
|
||||
|
||||
**Solution**:
|
||||
- Verify file extension: `.pdf`, `.docx`, `.txt`, `.jpg`, `.png`, `.gif`, `.webp`
|
||||
- Check MIME type matches extension
|
||||
|
||||
### Rate Limit Exceeded
|
||||
|
||||
**Error**: `429 Too Many Requests`
|
||||
|
||||
**Solution**:
|
||||
- Wait 1 hour before retrying
|
||||
- For development, disable rate limiting: `RATE_LIMIT_ENABLED=false` in `.env`
|
||||
- Or increase limit: `RATE_LIMIT_PER_HOUR=100`
|
||||
|
||||
---
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Test-First Discipline (Constitutional Requirement)
|
||||
|
||||
1. **Before implementing any feature**:
|
||||
- Write contract/integration test
|
||||
- Run test to verify it fails
|
||||
- Implement feature
|
||||
- Run test to verify success
|
||||
|
||||
2. **Test organization**:
|
||||
- Contract tests: Test API endpoints (HTTP requests/responses)
|
||||
- Integration tests: Test user journeys (multi-step workflows)
|
||||
- Unit tests: Test complex business logic in isolation
|
||||
|
||||
3. **Example test-first workflow**:
|
||||
|
||||
```python
|
||||
# Step 1: Write test (tests/contract/test_submission_api.py)
|
||||
def test_submit_feedback_with_text_only(client):
|
||||
response = client.post('/submit/test-product', data={
|
||||
'feedback_text': 'This is test feedback'
|
||||
})
|
||||
assert response.status_code == 200
|
||||
assert b'Thank You!' in response.data
|
||||
|
||||
# Step 2: Run test (should FAIL - endpoint not implemented)
|
||||
# pytest tests/contract/test_submission_api.py::test_submit_feedback_with_text_only
|
||||
|
||||
# Step 3: Implement feature (app/routes/submission.py)
|
||||
@bp.route('/submit/<product_slug>', methods=['POST'])
|
||||
def submit_feedback(product_slug):
|
||||
# Implementation here
|
||||
pass
|
||||
|
||||
# Step 4: Run test again (should PASS)
|
||||
# pytest tests/contract/test_submission_api.py::test_submit_feedback_with_text_only
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- Follow PEP 8
|
||||
- Use type hints where helpful
|
||||
- Keep functions small and focused
|
||||
- Prefer clear names over comments
|
||||
- Run linting: `flake8 app/`
|
||||
- Run formatting: `black app/`
|
||||
|
||||
### Git Workflow
|
||||
|
||||
- Feature branch: `001-build-an-application` (already created)
|
||||
- Commit messages: Descriptive, imperative mood
|
||||
- Test before committing
|
||||
- Regular integration to main branch
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Set up environment** following steps above
|
||||
2. **Run tests** to verify setup: `pytest`
|
||||
3. **Start development server**: `python run.py`
|
||||
4. **Create test product** via admin interface
|
||||
5. **Submit test feedback** via submission form
|
||||
6. **Review implementation plan**: `/specs/001-build-an-application/plan.md`
|
||||
7. **Begin task implementation**: Wait for `/specs/001-build-an-application/tasks.md` (generated by `/speckit.tasks`)
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **Feature Specification**: `/specs/001-build-an-application/spec.md`
|
||||
- **Implementation Plan**: `/specs/001-build-an-application/plan.md`
|
||||
- **Research**: `/specs/001-build-an-application/research.md`
|
||||
- **Data Model**: `/specs/001-build-an-application/data-model.md`
|
||||
- **API Contracts**: `/specs/001-build-an-application/contracts/`
|
||||
- **Flask Documentation**: https://flask.palletsprojects.com/
|
||||
- **Claude API Documentation**: https://docs.anthropic.com/
|
||||
- **Pytest Documentation**: https://docs.pytest.org/
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Refer to the specification documents or implementation plan for detailed requirements and design decisions.
|
||||
@@ -0,0 +1,366 @@
|
||||
# Research: Anonymous Feedback Platform (Reklamator)
|
||||
|
||||
**Branch**: `001-build-an-application` | **Date**: 2025-10-15
|
||||
|
||||
This document resolves all NEEDS CLARIFICATION items identified in the Technical Context section of plan.md.
|
||||
|
||||
## 1. AI Integration Approach
|
||||
|
||||
### Decision: Pluggable AI provider interface with Claude as default
|
||||
|
||||
**Rationale**:
|
||||
- User input specifies "we use claude but it can be any other service as well"
|
||||
- Designing for extensibility aligns with good architectural practice
|
||||
- Enables future migration to different AI providers without major refactoring
|
||||
|
||||
**Implementation Approach**:
|
||||
- Abstract base class `AIAnalyzer` defining interface: `analyze_feedback(text: str, target_lang: str) -> AnalysisResult`
|
||||
- Concrete implementation `ClaudeAnalyzer` using Anthropic API
|
||||
- Configuration-driven provider selection
|
||||
- API key management via environment variables
|
||||
|
||||
**Alternatives Considered**:
|
||||
- **Hard-coded Claude API integration**: Simpler initially but violates user requirement for provider flexibility
|
||||
- **LangChain framework**: Adds significant dependency weight for simple translation/categorization task
|
||||
- **Multiple provider implementations from start**: Premature complexity - implement Claude first, abstract as needed
|
||||
|
||||
**Claude API Specifics**:
|
||||
- Use `anthropic` Python SDK
|
||||
- Model: `claude-3-haiku-20240307` for cost-effective analysis (fast, sufficient for categorization/translation)
|
||||
- Prompt design: Single API call with structured output for category, summary, translation
|
||||
- Error handling: Retry logic for transient failures, graceful degradation for persistent errors
|
||||
|
||||
**Research References**:
|
||||
- Anthropic API Documentation: https://docs.anthropic.com/
|
||||
- Python SDK: https://github.com/anthropics/anthropic-sdk-python
|
||||
|
||||
---
|
||||
|
||||
## 2. File Upload - Malware Scanning Approach
|
||||
|
||||
### Decision: ClamAV integration via clamd for virus scanning
|
||||
|
||||
**Rationale**:
|
||||
- FR-060 requires malware scanning before storage
|
||||
- ClamAV is open-source, widely used, actively maintained
|
||||
- `clamd` provides Python bindings for integration
|
||||
- Suitable for on-premise deployment matching file-based storage philosophy
|
||||
|
||||
**Implementation Approach**:
|
||||
- Install ClamAV daemon (`clamd`) as system service
|
||||
- Use `clamd` Python library for scanning uploaded files
|
||||
- Scan files synchronously during upload before writing to disk
|
||||
- Reject files that fail virus scan with clear error message
|
||||
- Log scanning failures for security monitoring
|
||||
|
||||
**Configuration**:
|
||||
- Maximum file size: 10MB per file (FR-006)
|
||||
- Allowed extensions: `.pdf`, `.docx`, `.txt`, `.jpg`, `.png`, `.gif`, `.webp`
|
||||
- MIME type validation in addition to extension checking
|
||||
- Temporary upload storage cleaned after scan (pass or fail)
|
||||
|
||||
**Alternatives Considered**:
|
||||
- **Cloud-based scanning (VirusTotal API)**: Violates anonymity requirement (uploads data externally), adds latency
|
||||
- **No scanning**: Violates FR-060 security requirement
|
||||
- **Manual review**: Not scalable, delays feedback processing
|
||||
- **Python-based scanning (yara-python)**: More complex to configure, less comprehensive than ClamAV
|
||||
|
||||
**Dependencies**:
|
||||
- `clamd` Python library
|
||||
- ClamAV daemon installed on server
|
||||
|
||||
---
|
||||
|
||||
## 3. File Upload - Storage Location Strategy
|
||||
|
||||
### Decision: Local filesystem storage in `data/products/{product-id}/feedback/{feedback-id}/attachments/`
|
||||
|
||||
**Rationale**:
|
||||
- Aligns with file-based storage architecture (no database)
|
||||
- User input specifies "folders foreach user input" and "files in a folder"
|
||||
- Simple to implement, backup, and inspect
|
||||
- No additional service dependencies
|
||||
- Sufficient for MVP scale (100 products × 10k items × 3 files × 10MB = ~30TB worst case)
|
||||
|
||||
**Directory Structure**:
|
||||
```
|
||||
data/
|
||||
└── products/
|
||||
└── {product-id}/ # e.g., "001-acme-app"
|
||||
├── config.yaml # Product metadata (name, target language, owners)
|
||||
└── feedback/
|
||||
└── {feedback-id}/ # UUID v4, e.g., "a3f2c1d5-..."
|
||||
├── metadata.yaml # Feedback metadata (timestamp, status, category, etc.)
|
||||
├── content.txt # Original feedback text
|
||||
├── analysis.md # AI-generated analysis report
|
||||
└── attachments/
|
||||
├── original_filename_1.pdf
|
||||
├── original_filename_2.png
|
||||
└── original_filename_3.jpg
|
||||
```
|
||||
|
||||
**File Naming**:
|
||||
- Preserve original filenames to maintain user context
|
||||
- Sanitize filenames to prevent directory traversal (strip `../`, absolute paths, etc.)
|
||||
- Handle duplicate filenames by appending counter if needed
|
||||
|
||||
**Alternatives Considered**:
|
||||
- **Cloud storage (S3/GCS)**: Adds external dependency, cost, complexity; overkill for MVP
|
||||
- **Flat directory per product**: Poor scalability, difficult to organize metadata
|
||||
- **Database with BLOB storage**: Contradicts file-based storage decision, adds DB complexity
|
||||
- **Content-addressed storage (hash-based filenames)**: Loses original filename context, complicates presentation
|
||||
|
||||
**Backup Strategy** (out of scope for MVP but noted):
|
||||
- Simple filesystem backup via rsync/tar sufficient
|
||||
- Can upgrade to cloud sync if needed later
|
||||
|
||||
---
|
||||
|
||||
## 4. Authentication - Session Management Approach
|
||||
|
||||
### Decision: Flask-Login with server-side sessions for product owners/admins
|
||||
|
||||
**Rationale**:
|
||||
- Flask-Login is standard, well-tested session management for Flask
|
||||
- Server-side sessions prevent token tampering
|
||||
- Simple username/password authentication sufficient for MVP
|
||||
- FR-056, FR-063 require authentication and secure password storage
|
||||
|
||||
**Implementation Approach**:
|
||||
- Use `Flask-Login` extension for session management
|
||||
- Store user credentials in simple YAML file (products/users.yaml) for MVP consistency with file-based approach
|
||||
- Hash passwords with `bcrypt` (FR-063)
|
||||
- Session cookies: `HttpOnly`, `Secure` (HTTPS only), `SameSite=Lax`
|
||||
- Session timeout: 24 hours of inactivity
|
||||
|
||||
**User Model**:
|
||||
```yaml
|
||||
users:
|
||||
- id: "admin-001"
|
||||
email: "admin@example.com"
|
||||
password_hash: "$2b$12$..."
|
||||
role: "admin"
|
||||
assigned_products: [] # Empty = all products access
|
||||
|
||||
- id: "owner-001"
|
||||
email: "owner@example.com"
|
||||
password_hash: "$2b$12$..."
|
||||
role: "product_owner"
|
||||
assigned_products: ["001-acme-app", "002-beta-service"]
|
||||
```
|
||||
|
||||
**Access Control**:
|
||||
- Admins: Full access to all products, can manage products/owners
|
||||
- Product Owners: Read-only access to assigned products only (FR-033)
|
||||
- Anonymous users: Submission form access only (no authentication)
|
||||
|
||||
**Alternatives Considered**:
|
||||
- **JWT tokens**: More complex, unnecessary for server-rendered HTML application
|
||||
- **OAuth/SAML**: Over-engineered for MVP, adds external identity provider dependency
|
||||
- **Database-backed sessions**: Contradicts file-based architecture
|
||||
- **No authentication**: Violates FR-056 requirement
|
||||
|
||||
**Dependencies**:
|
||||
- `Flask-Login` extension
|
||||
- `bcrypt` for password hashing
|
||||
|
||||
---
|
||||
|
||||
## 5. Rate Limiting - Implementation Strategy
|
||||
|
||||
### Decision: Flask-Limiter with IP-based rate limiting for submission endpoint
|
||||
|
||||
**Rationale**:
|
||||
- FR-061 requires rate limiting (suggested: 10 submissions/hour/IP)
|
||||
- Flask-Limiter is standard, well-maintained Flask extension
|
||||
- IP-based limiting suitable for anonymous submissions
|
||||
- In-memory storage sufficient for MVP (single server deployment)
|
||||
|
||||
**Implementation Approach**:
|
||||
- Use `Flask-Limiter` extension
|
||||
- Apply rate limit decorator to submission route: `@limiter.limit("10 per hour")`
|
||||
- Storage backend: In-memory (default) for MVP
|
||||
- Return HTTP 429 Too Many Requests with clear error message
|
||||
- Exempt authenticated admin users from rate limits (for testing)
|
||||
|
||||
**Rate Limit Configuration**:
|
||||
- Anonymous submission: 10 requests per hour per IP address
|
||||
- Dashboard/admin routes: No rate limiting (authenticated users only)
|
||||
- Rate limit headers included in response: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
|
||||
|
||||
**Considerations**:
|
||||
- IP-based limiting can be circumvented via VPN/proxy but sufficient deterrent for casual abuse
|
||||
- Behind proxy/load balancer: Configure Flask-Limiter to read `X-Forwarded-For` header
|
||||
- Note: FR-055 requires no fingerprinting for identification - rate limiting is for abuse prevention only, not user tracking
|
||||
|
||||
**Alternatives Considered**:
|
||||
- **CAPTCHA (hCaptcha/reCAPTCHA)**: Adds friction to user experience, contradicts "lower barriers" goal
|
||||
- **Redis-backed rate limiting**: Unnecessary complexity for single-server MVP
|
||||
- **No rate limiting**: Violates FR-061 requirement, leaves system vulnerable to abuse
|
||||
- **Token bucket per session**: Requires session tracking for anonymous users, violates anonymity
|
||||
|
||||
**Dependencies**:
|
||||
- `Flask-Limiter` extension
|
||||
|
||||
---
|
||||
|
||||
## 6. Flask Best Practices for Simple HTML Applications
|
||||
|
||||
### Decision: Server-side rendering with Jinja2 templates, no JavaScript
|
||||
|
||||
**Rationale**:
|
||||
- User explicitly specifies "plain html using flask" and "does not use any css frameworks or javascript libraries"
|
||||
- Server-side rendering eliminates frontend build complexity
|
||||
- Jinja2 included with Flask, no additional dependencies
|
||||
- Forms use standard HTTP POST/GET, progressive enhancement approach
|
||||
|
||||
**Template Approach**:
|
||||
- Minimal inline CSS for basic layout (no framework)
|
||||
- Semantic HTML5 for accessibility
|
||||
- Server-side form validation with error display
|
||||
- Standard browser form controls (no custom widgets)
|
||||
|
||||
**Form Handling**:
|
||||
- POST requests for submissions
|
||||
- Server-side validation with error messages
|
||||
- Flash messages for user feedback
|
||||
- Redirect-after-POST pattern to prevent duplicate submissions
|
||||
|
||||
**No JavaScript Requirement** (Edge Case from spec.md line 105):
|
||||
- "What happens when a user's browser doesn't support JavaScript"
|
||||
- Answer: Application works fully without JavaScript (no JS used)
|
||||
- File uploads work via standard HTML `<input type="file" multiple>`
|
||||
|
||||
**Best Practices Applied**:
|
||||
- Flask app factory pattern for testability
|
||||
- Blueprint organization for routes
|
||||
- Environment-based configuration
|
||||
- CSRF protection via Flask-WTF (even for simple forms)
|
||||
|
||||
**Dependencies**:
|
||||
- Flask (includes Jinja2)
|
||||
- Flask-WTF for CSRF protection
|
||||
|
||||
---
|
||||
|
||||
## 7. Python Dependency Management
|
||||
|
||||
### Decision: requirements.txt with pinned versions for reproducibility
|
||||
|
||||
**Rationale**:
|
||||
- Simplest dependency management for Flask application
|
||||
- No need for Poetry/Pipenv complexity in MVP
|
||||
- Pin exact versions for reproducibility
|
||||
- Virtual environment assumed for isolation
|
||||
|
||||
**Core Dependencies** (estimated):
|
||||
```
|
||||
Flask==3.0.0
|
||||
Flask-Login==0.6.3
|
||||
Flask-Limiter==3.5.0
|
||||
Flask-WTF==1.2.1
|
||||
anthropic==0.8.0
|
||||
clamd==1.0.2
|
||||
bcrypt==4.1.2
|
||||
PyYAML==6.0.1
|
||||
pytest==7.4.3
|
||||
pytest-flask==1.3.0
|
||||
```
|
||||
|
||||
**Development Dependencies**:
|
||||
- pytest, pytest-flask for testing
|
||||
- black for code formatting
|
||||
- flake8 for linting
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack Summary
|
||||
|
||||
| Component | Technology | Rationale |
|
||||
|-----------|-----------|-----------|
|
||||
| **Web Framework** | Flask 3.0+ | Lightweight, simple, widely supported |
|
||||
| **Template Engine** | Jinja2 (built-in) | Server-side rendering, no JS needed |
|
||||
| **AI Provider** | Claude API (Anthropic) | User-specified, abstracted for future flexibility |
|
||||
| **Authentication** | Flask-Login + bcrypt | Standard session management, secure passwords |
|
||||
| **Rate Limiting** | Flask-Limiter | Prevent abuse, simple IP-based approach |
|
||||
| **Malware Scanning** | ClamAV + clamd | Open-source, reliable, on-premise |
|
||||
| **Storage** | Filesystem (YAML + Markdown) | Matches user requirements, simple, no DB |
|
||||
| **Testing** | pytest + pytest-flask | Industry standard, good Flask integration |
|
||||
| **Python Version** | 3.11+ | Modern, stable, good performance |
|
||||
| **Deployment** | Gunicorn (WSGI) | Production-ready Flask server |
|
||||
|
||||
---
|
||||
|
||||
## Non-Functional Requirements Research
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
**Concurrent Submissions** (SC-012):
|
||||
- Target: 100 concurrent submissions without errors
|
||||
- Flask + Gunicorn with 4-8 worker processes should handle this
|
||||
- File I/O is bottleneck: consider async I/O if performance issues arise
|
||||
- AI analysis happens asynchronously (background task) to not block submission response
|
||||
|
||||
**Dashboard Performance** (SC-008):
|
||||
- Target: Load 1000 items in <3 seconds
|
||||
- File-based approach: Index product feedback directories, cache counts
|
||||
- Implement pagination (50 items per page)
|
||||
- Use lazy loading for file attachments (links, not embedded content)
|
||||
|
||||
**AI Analysis Time** (SC-007):
|
||||
- Target: <30 seconds for 95% of submissions
|
||||
- Claude Haiku model typically responds in 2-5 seconds for translation/categorization
|
||||
- Timeout: 45 seconds before marking as failed
|
||||
- Queue-based processing if needed (Python `queue` module or simple file-based queue)
|
||||
|
||||
### Security Considerations
|
||||
|
||||
**Anonymity Enforcement** (FR-055, SC-010):
|
||||
- Do NOT log IP addresses in feedback metadata
|
||||
- Rate limiting uses IP for abuse prevention only, not stored with feedback
|
||||
- No session cookies for anonymous submission
|
||||
- No analytics/tracking scripts
|
||||
|
||||
**File Upload Security**:
|
||||
- Validate MIME types server-side (don't trust client)
|
||||
- ClamAV scanning before storage
|
||||
- Sanitize filenames to prevent directory traversal
|
||||
- Store outside web root, serve via Flask route with access control
|
||||
|
||||
**HTTPS Requirement** (FR-064):
|
||||
- Deployment guide must specify reverse proxy (nginx) with TLS
|
||||
- Redirect HTTP to HTTPS
|
||||
- HSTS headers recommended
|
||||
|
||||
---
|
||||
|
||||
## Open Questions for Implementation Phase
|
||||
|
||||
1. **Asynchronous AI Analysis**: Should analysis happen synchronously (user waits) or asynchronously (background job)?
|
||||
- **Recommendation**: Asynchronous - return success immediately, process in background
|
||||
- Implement simple file-based queue or use Python `threading` for MVP
|
||||
|
||||
2. **Admin Bootstrap**: How is the first admin user created?
|
||||
- **Recommendation**: CLI command or config file initialization script
|
||||
|
||||
3. **Email Notifications**: Out of scope (line 255) but commonly requested
|
||||
- **Recommendation**: Document as future enhancement, design hooks for extensibility
|
||||
|
||||
4. **Logging Strategy**: Structured logs for operational monitoring?
|
||||
- **Recommendation**: Python `logging` module, JSON format, separate file per environment
|
||||
|
||||
---
|
||||
|
||||
## Research Validation
|
||||
|
||||
All NEEDS CLARIFICATION items from Technical Context have been resolved:
|
||||
|
||||
| Item | Resolution | Document Section |
|
||||
|------|------------|------------------|
|
||||
| AI Integration | Pluggable interface, Claude as default | §1 |
|
||||
| File Upload - Malware Scanning | ClamAV + clamd | §2 |
|
||||
| File Upload - Storage Location | Filesystem: `data/products/.../feedback/.../attachments/` | §3 |
|
||||
| Authentication | Flask-Login + server-side sessions + bcrypt | §4 |
|
||||
| Rate Limiting | Flask-Limiter, 10/hour/IP | §5 |
|
||||
|
||||
**Next Phase**: Proceed to Phase 1 (data-model.md, contracts, quickstart.md)
|
||||
Reference in New Issue
Block a user