PATCH /api/threads/{id}/visibility
Update thread visibility (private/public/unlisted)
PATCH
Authentication
Required: JWT Bearer token JWT Claims Extraction (Lines 346-350):Path Parameters
Thread UUIDFormat: Valid GUIDValidation: Route constraint
:guid (Line 339)Request Body
New visibility levelValidation (Lines 381-400):Allowed Values:
"private": Owner-only access"public": Public access whenpublic_sharingfeature enabled"unlisted": Accessible via direct link whenpublic_sharingfeature enabled
Authorization
Ownership Verification (Lines 358-378):- ONLY thread owner can change visibility (Line 369)
- Explicit message: “Only the thread owner can change visibility” (Line 375)
- No delegation or admin override
Side Effects
Database Mutations (Line 402):- UPDATE
threadsSETvisibility = {request.Visibility},updated_at = NOW()WHEREthread_id = {threadId}
- Changing TO
"private": Thread becomes owner-only - Changing TO
"public": Thread becomes publicly accessible (ifpublic_sharingfeature enabled) - Changing TO
"unlisted": Thread accessible via direct link (ifpublic_sharingfeature enabled)
- Existing messages/comparisons/votes unaffected
- Thread remains in owner’s thread list
- Share URLs become invalid/valid based on new visibility
Permissions
Who Can Modify Visibility:- Thread owner only
- Other authenticated users
- Public thread viewers
- Admins (not documented as exception)
Visibility Semantics
private
- Access: Owner only, always
- Feature Flag: Irrelevant (owner-only regardless)
- Sharing: Cannot be shared
public
- Access: Anyone if
public_sharing = true, owner only iffalse - Discovery: May be listed in public directories (implementation-dependent)
- Indexing: May be indexed by search engines
unlisted
- Access: Anyone with link if
public_sharing = true, owner only iffalse - Discovery: Not listed publicly
- Indexing: Implementation-dependent
Edge Cases
- Thread doesn’t exist: 404 (Lines 359-367)
- User is not owner: 403 (Lines 369-378)
- Visibility is null: 400 (Lines 381-389)
- Visibility is empty: 400 (Lines 381-389)
- Invalid visibility value: 400 (Lines 391-400)
- Case variations (
"Public","PUBLIC"): Accepted, lowercased (Line 392) - Same visibility as current: Update proceeds (no change detection)
- Thread currently has public viewers: Changing to private immediately denies access
Error Conditions
| Code | HTTP | Cause | Controller Line |
|---|---|---|---|
| N/A | 401 | JWT missing or invalid | Middleware |
| N/A | 401 | User ID claim missing | 352-355 |
INVALID_REQUEST | 400 | Visibility null/empty | 381-389 |
INVALID_REQUEST | 400 | Invalid visibility value | 391-400 |
NOT_FOUND | 404 | Thread doesn’t exist | 359-367 |
FORBIDDEN | 403 | Not thread owner | 369-378 |
VISIBILITY_UPDATE_ERROR | 500 | Service exception | 411-419 |
Behavioral Guarantees
Atomicity: Single UPDATE query (atomic) Idempotency: NOT idempotentupdated_attimestamp changes on every call- Even if visibility unchanged
- Next GET request reflects new visibility
- Access control updated instantly
Validation Order
- User ID from JWT (401 if missing)
- Thread existence (404 if not found)
- Ownership (403 if not owner)
- Visibility value validation (400 if invalid)
- Update execution (500 if fails)
Security Implications
Public Exposure Risk:- Changing private → public exposes thread to world (if feature enabled)
- No confirmation required
- No warning for sensitive content
- Private → public/unlisted is one-way exposure
- Changing back to private doesn’t “un-share” (content may be cached elsewhere)
- Changing public → private immediately denies access to non-owners
- No grace period
Feature Flag Dependency
Depends on:public_sharing feature flag
Behavior Matrix:
| Flag | Visibility | Effect on Access |
|---|---|---|
| ON | private | Owner only |
| ON | public | Public access |
| ON | unlisted | Link-based access |
| OFF | private | Owner only |
| OFF | public | Owner only (flag overrides visibility) |
| OFF | unlisted | Owner only (flag overrides visibility) |
public_sharing = false
Response Format
Success Response (Lines 404-409):- Includes
visibilityfield echoing normalized value (lowercased) - Example: Request
"PUBLIC"→ Response"public"