Files
Reklamator/specs/001-build-an-application/contracts/admin_routes.md
T
gurixandClaude c4ae7a0fa6 Rename API contracts to web routes for clarity
Changed terminology from "API" to "Routes" to better reflect server-rendered HTML approach:
- Renamed submission_api.md → submission_routes.md
- Renamed dashboard_api.md → dashboard_routes.md
- Renamed admin_api.md → admin_routes.md
- Updated headers to clarify "Response Type: Server-rendered HTML (no JavaScript required)"
- Updated references in plan.md and quickstart.md

This clarifies that the application uses traditional web routes with form submissions
and HTML responses, not REST API endpoints with JSON.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 13:47:40 +02:00

13 KiB

Admin Routes Contract

Scope: Product and user management web routes (User Story P4) Authentication: Required (admin role only) Response Type: Server-rendered HTML (no JavaScript required)


GET /admin/products

Display list of all registered products.

Request

Authentication: Required (admin role)

Response

Success (200 OK):

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

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):

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/1.1 302 Found
Location: /admin/products

Error (400 Bad Request): Validation failure

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):

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/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/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/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):

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):

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/1.1 302 Found
Location: /admin/users

Error (400 Bad Request): Validation failure

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/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/1.1 302 Found
Location: /admin/users

Error (400 Bad Request): Cannot delete self

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