openapi: 3.1.0
info:
  title: BATbern Speakers API
  version: 1.0.0
  description: |
    Speaker Coordination API for BATbern Event Management Platform.

    Event-scoped operations on the speaker_pool entries that participate in a
    specific event's workflow:
    - **Speaker Status Management**: PUT/GET status on speaker_pool entries (Story 5.4, ADR-009)
    - **Speaker Content Submission**: title, abstract, materials (Story 5.5)
    - **Content Quality Review**: Abstract and material review workflow (Story 5.5)
    - **Speaker Outreach Tracking**: organizer outreach attempts (Story 5.3)

    **ADR-009 (Unified Speaker Workflow)**: there is no stand-alone `Speaker`
    entity. A speaker is a `User` with the `SPEAKER` role on user_profiles
    (managed by company-user-management-service). Speaker identity in this API
    is the `PRIMARY_SPEAKER` `session_users.username` joined via
    `speaker_pool.session_id` — the cross-service ID per ADR-003. The legacy
    `speaker_pool.username` and `speaker_pool.email` columns were dropped in
    Story 11.E.9 (Flyway V103); use `PrimarySpeakerResolver.resolve(pool)` for
    the live identity. User-profile fields (email, firstName, lastName, bio,
    profilePictureUrl) are NOT duplicated here; they are fetched via UserApiClient
    (HTTP enrichment per ADR-004) when a response needs to project them.

    **Status**: IMPLEMENTED (Story 11.C.1: Speaker entity + table dropped; this API now
    serves only event-scoped operations on speaker_pool entries).

  contact:
    name: BATbern Platform Team
    email: platform@batbern.ch
  license:
    name: Proprietary
    identifier: Proprietary

servers:
  - url: https://api.batbern.ch/api/v1
    description: Production
  - url: https://api.batbern.ch/api/v1
    description: Staging
  - url: http://localhost:8080/api/v1
    description: Development

tags:
  - name: Speaker Status
    description: Speaker workflow status management (Story 5.4, ADR-009)
  - name: Speaker Content
    description: Speaker content submission - title, abstract, materials (Story 5.5)
  - name: Quality Review
    description: Content quality review workflow - approve/reject (Story 5.5)
  - name: Speaker Outreach
    description: Speaker outreach tracking and contact history (Story 5.3)

paths:
  /events/{eventCode}/speakers/{speakerId}/status:
    put:
      tags:
        - Speaker Status
      summary: Update speaker status
      description: |
        Update the workflow status of a speaker for a specific event.
        Story 5.4 (original) + Story 11.B.3 (8-state model tighten).

        Per ADR-009 the legal transitions are:
        - IDENTIFIED → CONTACTED, DECLINED
        - CONTACTED → DECLINED (transition to READY requires POST /promote — has email payload)
        - INVITED → ACCEPTED, DECLINED
        - ACCEPTED → CONTENT_SUBMITTED, DECLINED
        - CONTENT_SUBMITTED → QUALITY_REVIEWED, DECLINED
        - QUALITY_REVIEWED → DECLINED
        - DECLINED is terminal — no transitions out.

        Notes:
        - Setting newStatus = READY via this endpoint returns 400 READY_REQUIRES_PROMOTE_ENDPOINT.
          Use POST /api/v1/events/{eventCode}/speakers/{speakerId}/promote instead (Story 11.D.1).
        - Setting newStatus to any of SLOT_ASSIGNED, CONFIRMED, OVERFLOW, WITHDREW, TENTATIVE
          (legacy 10-state values) returns 400 INVALID_SPEAKER_WORKFLOW_STATE — those states
          were removed per ADR-009 §0.7.
        - Same-state re-affirm (current == newStatus) is permitted and writes an audit-trail
          self-transition row to speaker_status_history (no side-effects fire).

      operationId: updateSpeakerStatus
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
        - name: speakerId
          in: path
          required: true
          description: Speaker pool ID (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateStatusRequest'
      responses:
        '200':
          description: Status updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SpeakerStatusResponse'
        '400':
          description: |
            Bad request. Returned for:
            - newStatus is one of the 5 removed legacy values (INVALID_SPEAKER_WORKFLOW_STATE).
            - newStatus = READY (READY_REQUIRES_PROMOTE_ENDPOINT — use POST /promote).
            - Reason field exceeds 2000 characters (validation error).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role
        '404':
          description: Speaker not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: |
            Invalid state transition or workflow validation failed. Returned for:
            - Illegal state-machine transition (code INVALID_STATE_TRANSITION),
              e.g. attempting QUALITY_REVIEWED → ACCEPTED.
            - Business rule violation (code WORKFLOW_VALIDATION_FAILED),
              e.g. no accepted speakers when publishing agenda.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /events/{eventCode}/speakers/{speakerId}/status/history:
    get:
      tags:
        - Speaker Status
      summary: Get speaker status history
      description: |
        Retrieve the complete status change history for a speaker.
        Story 5.4: Speaker Status Management (AC3-4, AC15)

      operationId: getSpeakerStatusHistory
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
        - name: speakerId
          in: path
          required: true
          description: Speaker pool ID (UUID)
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Status history retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/StatusHistoryItem'
        '404':
          description: Speaker not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role

  /events/{eventCode}/speakers/status-summary:
    get:
      tags:
        - Speaker Status
      summary: Get status dashboard summary
      description: |
        Get aggregated speaker status counts and metrics for the status dashboard.
        Story 5.4: Speaker Status Management (AC5-6, AC13)

        Returns:
        - Status counts by workflow state
        - Acceptance rate percentage
        - Threshold status (min/max slots)
        - Overflow detection

      operationId: getStatusSummary
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
      responses:
        '200':
          description: Status summary retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusSummaryResponse'
        '404':
          description: Event not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role

  # Speaker Content Submission (Story 5.5)
  /events/{eventCode}/speakers/{speakerId}/content:
    get:
      tags:
        - Speaker Content
      summary: Get speaker content
      description: |
        Retrieve existing presentation content for a speaker.
        Story 5.5: Speaker Content Submission (AC34)

        Handles orphaned session references by resetting speaker state.
        Returns material information if uploaded.

      operationId: getSpeakerContent
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
        - name: speakerId
          in: path
          required: true
          description: Speaker pool ID (UUID)
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Speaker content retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SpeakerContentResponse'
        '404':
          description: Speaker not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role

    post:
      tags:
        - Speaker Content
      summary: Submit speaker content
      description: |
        Submit speaker content (presentation title and abstract).
        Story 5.5: Speaker Content Submission (AC6-10, AC33-34, AC37)

        Creates a session with the presentation details and links the speaker.
        Updates speaker status to CONTENT_SUBMITTED.

      operationId: submitSpeakerContent
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
        - name: speakerId
          in: path
          required: true
          description: Speaker pool ID (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitContentRequest'
      responses:
        '201':
          description: Content submitted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SpeakerContentResponse'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Speaker not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Invalid state - speaker must be in ACCEPTED state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role

  # Quality Review Queue (Story 5.5)
  /events/{eventCode}/speakers/review-queue:
    get:
      tags:
        - Quality Review
      summary: Get quality review queue
      description: |
        Get review queue showing all speakers with CONTENT_SUBMITTED status.
        Story 5.5: Speaker Content Submission (AC11)

        Returns speakers pending quality review, ordered by submission date (oldest first).

      operationId: getReviewQueue
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
      responses:
        '200':
          description: Review queue retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SpeakerPoolEntry'
        '404':
          description: Event not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role

  # Quality Review Action (Story 5.5)
  /events/{eventCode}/speakers/{speakerId}/review:
    post:
      tags:
        - Quality Review
      summary: Approve or reject speaker content
      description: |
        Approve or reject speaker content quality review.
        Story 5.5: Speaker Content Submission (AC13-14, AC17)

        - APPROVE: Updates speaker status to QUALITY_REVIEWED
        - REJECT: Requires feedback, sends notification to speaker linking to the
          Cognito-secured speaker portal for revisions

        Auto-updates to CONFIRMED when both QUALITY_REVIEWED AND slot is assigned.

      operationId: reviewSpeakerContent
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
        - name: speakerId
          in: path
          required: true
          description: Speaker pool ID (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReviewRequest'
      responses:
        '204':
          description: Review action completed successfully
        '400':
          description: Validation error (e.g., feedback required for rejection)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Speaker not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Invalid state - speaker must be in CONTENT_SUBMITTED state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role

  # Speaker Outreach Tracking (Story 5.3)
  /events/{eventCode}/speakers/{speakerId}/outreach:
    get:
      tags:
        - Speaker Outreach
      summary: Get outreach history for speaker
      description: |
        Get the complete outreach/contact history for a speaker.
        Story 5.3: Speaker Outreach Tracking
      operationId: getOutreachHistory
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
        - name: speakerId
          in: path
          required: true
          description: Speaker pool ID (UUID)
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Outreach history retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/OutreachHistory'
        '404':
          description: Speaker not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role

    post:
      tags:
        - Speaker Outreach
      summary: Record outreach attempt
      description: |
        Record a new outreach/contact attempt with a speaker.
        Story 5.3: Speaker Outreach Tracking
      operationId: recordOutreach
      security:
        - CognitoAuth: [ORGANIZER]
      parameters:
        - name: eventCode
          in: path
          required: true
          description: Event code (ADR-003)
          schema:
            type: string
            example: BATbern56
        - name: speakerId
          in: path
          required: true
          description: Speaker pool ID (UUID)
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordOutreachRequest'
      responses:
        '201':
          description: Outreach recorded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutreachHistory'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Speaker not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
        '403':
          description: Forbidden - requires ORGANIZER role

  # Story 6.3 + Story 11.C.2 + Story 11.E.3: Speaker self-service content submission
  # (Cognito Bearer auth).
  # Code review 2026-05-18 (D5 / AC12): the path now carries `eventCode` as a path parameter
  # per Resolved Q#1, the `token` body field is gone (replaced by Cognito Bearer in the
  # Authorization header), and the security scheme is `CognitoAuth` with the SPEAKER scope
  # per ADR-009 §Decision 3. The legacy /speaker-portal/content/submit + magic-link token
  # path no longer exists.
  /speaker-portal/events/{eventCode}/content/submit:
    post:
      tags:
        - Speaker Content
      summary: Submit speaker content via the speaker portal (Cognito-authenticated)
      description: |
        Speaker self-service content submission used by the Cognito-authenticated speaker
        portal. Refactored by Story 11.C.2 to share the consolidated
        `ContentSubmissionService.submit(...)` backend write path with the organizer-on-behalf
        endpoint (`POST /events/{eventCode}/speakers/{speakerId}/content`).

        **Auth**: Cognito Bearer JWT with the SPEAKER scope. The controller carries
        `@PreAuthorize("hasRole('SPEAKER')")` and resolves the speaker pool by
        `(authPrincipal.username, eventCode)` via `SpeakerPortalAuthorizationService` before
        delegating to the shared service. A SPEAKER token whose username has no pool row for
        the requested `eventCode` receives 403; an unauthenticated request receives 401.

        **State precondition**: speaker must be in ACCEPTED (first submission) or
        CONTENT_SUBMITTED (resubmission). Returns 422 otherwise.

        **Downstream effects** (identical to the organizer endpoint, modulo the audit
        principal):
        - `speaker_content_submissions` row inserted with the next version.
        - Session created or updated with the submitted title/abstract.
        - `speaker_pool.status` transitions to CONTENT_SUBMITTED (or same-state self-transition
          on resubmission) via `SpeakerWorkflowService.transition()` — the sole status writer
          per ADR-009.
        - `speaker_status_history` row written with `changed_by_username = speaker.username`.
        - `SpeakerContentSubmittedEvent` published.

      operationId: submitSpeakerPortalContent
      security:
        - CognitoAuth: [SPEAKER]
      parameters:
        - name: eventCode
          in: path
          required: true
          schema:
            type: string
            maxLength: 20
            pattern: '^BATbern[0-9]+$'
          description: |
            Meaningful event identifier (ADR-003) — e.g. `BATbern56`. Per code review
            2026-05-18 (P20) the auth service caps length to avoid passing oversized
            strings into the JPA JOIN.
          example: "BATbern56"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContentSubmitRequest'
            examples:
              minimalSubmit:
                summary: Title + abstract only
                value:
                  title: "Zero Trust Security in Enterprise Environments"
                  contentAbstract: "This talk explores Zero Trust principles..."
              fullSubmit:
                summary: Submit with bio, portrait, presentation upload
                value:
                  title: "Zero Trust Security in Enterprise Environments"
                  contentAbstract: "This talk explores Zero Trust principles..."
                  bio: "Experienced security architect with 15 years in the industry."
                  profilePictureUrl: "https://cdn.batbern.ch/users/jane.smith.jpg"
                  presentationUploadId: "upload-abc-123"
      responses:
        '201':
          description: Content submitted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContentSubmitResponse'
        '400':
          description: Validation error (blank fields, unknown property, etc.)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthenticated — no Cognito Bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: |
            Forbidden — caller lacks SPEAKER role, OR has SPEAKER role but no pool row for
            the requested eventCode (SpeakerPortalAuthorizationService).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: |
            Conflict — event is in EVENT_COMPLETED state, or the resolved speaker_pool row
            lacks a canonical username (provisioning invariant violation, code review D2/P1).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Invalid state — speaker not in ACCEPTED or CONTENT_SUBMITTED
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  schemas:
    SpeakerWorkflowState:
      type: string
      enum:
        - IDENTIFIED
        - CONTACTED
        - READY
        - INVITED
        - ACCEPTED
        - CONTENT_SUBMITTED
        - QUALITY_REVIEWED
        - DECLINED
      description: |
        Speaker coordination workflow state (UPPER_CASE per project-context.md §"Enum Value Flow").
        Per ADR-009 (Unified Speaker Workflow) — 8 states; SLOT_ASSIGNED, CONFIRMED, OVERFLOW, WITHDREW removed.
        - IDENTIFIED: brainstorm-list entry; no User, no Cognito user.
        - CONTACTED: organizer reaching out; still brainstorming; no User, no Cognito user.
        - READY: real speaker identified with email; User provisioning happens at the transition INTO this state.
          Reached ONLY via POST /events/{code}/speakers/{speakerId}/promote (Story 11.D.1), never via PUT /status.
        - INVITED: formal invitation sent; speaker can authenticate via Cognito.
          READY → INVITED blocked when count(ACCEPTED) + count(INVITED) >= max_slots.
        - ACCEPTED: speaker committed via the portal (or via organizer-on-behalf).
        - CONTENT_SUBMITTED: title + abstract submitted (by speaker or by organizer).
        - QUALITY_REVIEWED: moderator approved content. Terminal happy state.
          Combined with session.start_time IS NOT NULL, makes the speaker publishable.
        - DECLINED: terminal "not happening"; reachable from every non-terminal state.

        Derived flags (NOT persisted — computed at read time):
        - isSlotAssigned := session.start_time IS NOT NULL
        - isPublishable  := status == QUALITY_REVIEWED AND isSlotAssigned

    Pagination:
      type: object
      properties:
        page:
          type: integer
          description: Current page (0-indexed)
        limit:
          type: integer
          description: Items per page
        totalItems:
          type: integer
          description: Total number of items
        totalPages:
          type: integer
          description: Total number of pages

    # Story 5.4: Speaker Status Management Schemas

    UpdateStatusRequest:
      type: object
      description: Request to update speaker status (Story 5.4 AC1-4)
      required:
        - newStatus
      properties:
        newStatus:
          $ref: '#/components/schemas/SpeakerWorkflowState'
        reason:
          type: string
          description: Optional reason for status change (max 2000 characters)
          maxLength: 2000
          example: "Speaker confirmed availability via email"

    SpeakerStatusResponse:
      type: object
      description: Response after status update (Story 5.4)
      properties:
        speakerId:
          type: string
          format: uuid
          description: Speaker pool ID
        eventCode:
          type: string
          description: Event code (ADR-003)
          example: BATbern56
        currentStatus:
          $ref: '#/components/schemas/SpeakerWorkflowState'
        previousStatus:
          $ref: '#/components/schemas/SpeakerWorkflowState'
        changedByUsername:
          type: string
          description: Organizer who made the change
          example: john.doe
        changeReason:
          type: string
          description: Reason for the change
          example: "Speaker confirmed availability"
        changedAt:
          type: string
          format: date-time
          description: When the change occurred
        isSlotAssigned:
          type: boolean
          description: |
            Derived flag (ADR-009 §0.1, Story 11.B.3 AC6): true when the speaker has a sessionId
            assigned (sessionId != null). Uses sessionId-based fallback — see SpeakerPoolEntry for
            the strict session.start_time definition.
        isPublishable:
          type: boolean
          description: |
            Derived flag (ADR-009 §0.1, Story 11.B.3 AC6): true when
            currentStatus == QUALITY_REVIEWED AND isSlotAssigned.

    StatusHistoryItem:
      type: object
      description: Single timeline entry — either a workflow state change (Story 5.4 AC3-4) or a content-review event interleaved by the same endpoint.
      properties:
        id:
          type: string
          format: uuid
          description: History record ID
        kind:
          type: string
          enum: [STATUS_CHANGE, CONTENT_REJECTED]
          default: STATUS_CHANGE
          description: |
            Discriminates the entry type. `STATUS_CHANGE` is a row from `speaker_status_history`
            (previousStatus → newStatus). `CONTENT_REJECTED` is a synthesised row sourced from
            `session_content_history` where reviewerFeedback is non-null — content rejection is
            intentionally NOT a state transition (ADR-009), so it never lands in
            speaker_status_history. Surfacing it here keeps the organizer's History tab honest
            (used by UnifiedHistoryPanel).
        previousStatus:
          $ref: '#/components/schemas/SpeakerWorkflowState'
        newStatus:
          $ref: '#/components/schemas/SpeakerWorkflowState'
        changedByUsername:
          type: string
          description: Organizer who made the change (or `reviewedBy` for CONTENT_REJECTED).
          example: john.doe
        changeReason:
          type: string
          description: Reason for the change (or `reviewerFeedback` for CONTENT_REJECTED).
        changedAt:
          type: string
          format: date-time
          description: When the change occurred (or `reviewedAt` for CONTENT_REJECTED).

    StatusSummaryResponse:
      type: object
      description: Status dashboard summary (Story 5.4 AC5-6, AC13)
      properties:
        eventCode:
          type: string
          description: Event code (ADR-003)
          example: BATbern56
        statusCounts:
          type: object
          description: Count of speakers in each status
          additionalProperties:
            type: integer
          example:
            IDENTIFIED: 3
            CONTACTED: 5
            READY: 2
            ACCEPTED: 6
            DECLINED: 1
        totalSpeakers:
          type: integer
          description: Total number of speakers
          example: 17
        acceptedCount:
          type: integer
          description: Number of accepted speakers
          example: 6
        declinedCount:
          type: integer
          description: Number of declined speakers
          example: 1
        pendingCount:
          type: integer
          description: Number of pending speakers (not accepted/declined)
          example: 10
        acceptanceRate:
          type: number
          format: double
          description: Acceptance rate percentage
          example: 35.29
        minSlotsRequired:
          type: integer
          description: Minimum speakers required for event
          example: 6
        maxSlotsAllowed:
          type: integer
          description: Maximum speakers allowed for event
          example: 8
        thresholdMet:
          type: boolean
          description: Whether minimum threshold is met
          example: true
        overflowDetected:
          type: boolean
          description: Whether accepted count exceeds maximum (AC13)
          example: false

    ErrorResponse:
      type: object
      description: Standard error response format
      properties:
        timestamp:
          type: string
          format: date-time
          description: When the error occurred
        path:
          type: string
          description: Request path that caused the error
        status:
          type: integer
          description: HTTP status code
          example: 400
        error:
          type: string
          description: Error type
          example: "Bad Request"
        message:
          type: string
          description: Error message
          example: "Validation failed: reason must not exceed 2000 characters"
        correlationId:
          type: string
          format: uuid
          description: Unique error correlation ID for tracing
        severity:
          type: string
          enum: [LOW, MEDIUM, HIGH, CRITICAL]
          description: Error severity level
        details:
          type: object
          description: Additional error details
          additionalProperties: true

    # Speaker Outreach Schemas (Story 5.3)
    OutreachHistory:
      type: object
      description: Record of outreach/contact attempt with a speaker
      required:
        - id
        - speakerPoolId
        - contactDate
        - contactMethod
        - organizerUsername
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: Outreach record ID
        speakerPoolId:
          type: string
          format: uuid
          description: Speaker pool entry ID
        contactDate:
          type: string
          format: date-time
          description: When the contact was made
        contactMethod:
          $ref: '#/components/schemas/ContactMethod'
        notes:
          type: string
          maxLength: 2000
          description: Notes about the outreach attempt
        organizerUsername:
          type: string
          description: Username of organizer who made contact
          example: john.doe
        createdAt:
          type: string
          format: date-time
          description: When the record was created

    ContactMethod:
      type: string
      enum:
        - email
        - phone
        - in_person
      description: Method of contact used

    RecordOutreachRequest:
      type: object
      description: Request to record an outreach attempt
      required:
        - contactMethod
        - contactDate
      properties:
        contactMethod:
          $ref: '#/components/schemas/ContactMethod'
        contactDate:
          type: string
          format: date-time
          description: When the contact was made
        notes:
          type: string
          maxLength: 2000
          description: Optional notes about the outreach

    # Story 5.5: Speaker Content Submission Schemas

    SubmitContentRequest:
      type: object
      description: |
        Organizer-on-behalf request to submit speaker content (Story 5.5 AC6-10; refactored
        by Story 11.C.2 to drop ad-hoc user-creation fields). Creates/updates a session with
        the presentation details and transitions the speaker to CONTENT_SUBMITTED via the
        consolidated ContentSubmissionService.

        Story 11.C.2 changes:
        - **Removed** legacy ad-hoc user-identity fields (`username`, `speakerName`, `email`,
          `company`). The speaker's identity is established on `speaker_pool` at CONTACTED →
          READY per ADR-009.
        - **Added** optional `bio`, `profilePictureUrl`, `presentationUploadId` so the
          organizer drawer can submit on-behalf content with CV/portrait/upload reference.
        - `additionalProperties: false` enforces strict request validation — payloads with
          unknown fields (including the removed legacy fields) yield 400.
      additionalProperties: false
      required:
        - presentationTitle
        - presentationAbstract
      properties:
        presentationTitle:
          type: string
          description: Presentation title (becomes session.title)
          maxLength: 255
          example: "Zero Trust Security in Enterprise Environments"
        presentationAbstract:
          type: string
          description: Presentation abstract (becomes session.description). Max 1000 characters.
          maxLength: 1000
          example: "This talk explores the implementation of Zero Trust security principles in large enterprise environments, covering identity verification, micro-segmentation, and continuous monitoring strategies."
        bio:
          type: string
          maxLength: 5000
          description: |
            Optional speaker bio. When present, patched onto `User.bio` for the resolved
            speaker username via the User Management Service (Story 11.C.2 — AR14).
          example: "Experienced security architect with 15 years in the industry."
        profilePictureUrl:
          type: string
          format: uri
          maxLength: 2048
          description: |
            Optional speaker portrait URL (typically a CloudFront URL produced by a separate
            presigned-upload flow). When present, patched onto `User.profile_picture_url`.
          example: "https://cdn.batbern.ch/users/jane.smith.jpg"
        presentationUploadId:
          type: string
          description: |
            Optional upload ID from a separate presigned-URL upload (Story 6.3 materials flow).
            When present, the consolidated service links the uploaded file to the session via
            the existing materials-confirm pathway.
          example: "upload-abc-123"

    SpeakerContentResponse:
      type: object
      description: |
        Response for speaker content operations (Story 5.5 AC6-10).
        Returns session details and speaker status after content submission or retrieval.
      properties:
        speakerPoolId:
          type: string
          format: uuid
          description: Speaker pool entry ID
        eventId:
          type: string
          format: uuid
          description: Event ID
        sessionId:
          type: string
          format: uuid
          description: Created session ID (AC7)
        presentationTitle:
          type: string
          description: Presentation title (from session.title)
          example: "Zero Trust Security in Enterprise Environments"
        presentationAbstract:
          type: string
          description: Presentation abstract (from session.description)
          example: "This talk explores..."
        username:
          type: string
          description: Speaker username (ADR-003)
          example: john.doe
        speakerName:
          type: string
          description: Speaker full name
          example: John Doe
        company:
          type: string
          description: Speaker company
          example: Google Switzerland
        status:
          $ref: '#/components/schemas/SpeakerWorkflowState'
        hasContent:
          type: boolean
          description: Whether content exists for this speaker. False if session was deleted (AC34).
        warning:
          type: string
          description: Warning message if issues detected (AC34 - "Content was lost. Please resubmit.")
        submittedAt:
          type: string
          format: date-time
          description: When content was submitted
        hasMaterial:
          type: boolean
          description: Whether presentation material has been uploaded
        materialUrl:
          type: string
          format: uri
          description: Material download URL (CloudFront CDN URL)
          example: "https://cdn.batbern.ch/materials/presentation.pdf"
        materialFileName:
          type: string
          description: Material filename for display
          example: "zero-trust-presentation.pdf"

    # Story 6.3 + Story 11.C.2 + Story 11.E.3: Speaker-portal content submission DTO.
    # Code review 2026-05-18 (D5 / AC12): the `token` field was removed when the portal moved
    # to Cognito Bearer auth. `eventCode` lives in the URL path (Resolved Q#1), not the body.
    ContentSubmitRequest:
      type: object
      description: |
        Speaker self-service content submission via the Cognito-authenticated speaker portal.
        Story 11.C.2 added optional `bio`, `profilePictureUrl`, `presentationUploadId` so the
        speaker can patch their User profile + attach an uploaded presentation in a single
        submit. Story 11.E.3 removed the magic-link `token` field — auth is now the Cognito
        Bearer JWT in the `Authorization` header.
      additionalProperties: false
      required:
        - title
        - contentAbstract
      properties:
        title:
          type: string
          maxLength: 200
          description: Presentation title.
          example: "Zero Trust Security in Enterprise Environments"
        contentAbstract:
          type: string
          maxLength: 1000
          description: Presentation abstract.
          example: "This talk explores Zero Trust principles..."
        bio:
          type: string
          maxLength: 5000
          description: |
            Optional speaker bio. When present, patched onto `User.bio` for the resolved
            speaker username via the User Management Service (Story 11.C.2 — AR14).
          example: "Experienced security architect with 15 years in the industry."
        profilePictureUrl:
          type: string
          format: uri
          maxLength: 2048
          description: |
            Optional speaker portrait URL (typically a CloudFront URL produced by a separate
            presigned-upload flow). When present, patched onto `User.profile_picture_url`.
          example: "https://cdn.batbern.ch/users/jane.smith.jpg"
        presentationUploadId:
          type: string
          description: |
            Optional upload ID from a separate presigned-URL upload (Story 6.3 materials flow).
            When present, the consolidated service links the uploaded file to the session.
          example: "upload-abc-123"

    ContentSubmitResponse:
      type: object
      description: Response after a successful speaker-portal content submission.
      required:
        - submissionId
        - version
        - status
        - sessionTitle
      properties:
        submissionId:
          type: string
          format: uuid
          description: The content submission record ID.
        version:
          type: integer
          format: int32
          description: Submission version (1 for first, increments on resubmission).
          example: 1
        status:
          type: string
          description: Submission status — always "SUBMITTED" after a successful submit.
          example: "SUBMITTED"
        sessionTitle:
          type: string
          description: Session title (for confirmation display).
          example: "Zero Trust Security in Enterprise Environments"

    ReviewRequest:
      type: object
      description: Request for quality review action (Story 5.5 AC13-14)
      required:
        - action
      properties:
        action:
          $ref: '#/components/schemas/ReviewAction'
        feedback:
          type: string
          description: Feedback for rejection (required when action is REJECT)
          maxLength: 2000
          example: "Please provide more technical details about the implementation approach."

    ReviewAction:
      type: string
      enum:
        - APPROVE
        - REJECT
      description: |
        Quality review action:
        - APPROVE: Accept the content, update status to QUALITY_REVIEWED
        - REJECT: Reject the content, requires feedback, sends notification linking to
          the Cognito-secured speaker portal for revisions

    SpeakerPoolEntry:
      type: object
      description: Speaker pool entry for review queue
      properties:
        id:
          type: string
          format: uuid
          description: Speaker pool entry ID
        eventId:
          type: string
          format: uuid
          description: Event ID
        speakerName:
          type: string
          description: Speaker name
          example: John Doe
        company:
          type: string
          description: Speaker company
          example: Google Switzerland
        status:
          $ref: '#/components/schemas/SpeakerWorkflowState'
        isSlotAssigned:
          type: boolean
          description: |
            Derived flag (ADR-009 §0.1, Story 11.B.3): true when the speaker has an
            assigned session whose start_time is non-null. NOT a persisted column —
            computed at read time.
        isPublishable:
          type: boolean
          description: |
            Derived flag (ADR-009 §0.1, Story 11.B.3): true when
            status == QUALITY_REVIEWED AND isSlotAssigned. Gates the AGENDA_PUBLISHED
            event-workflow transition. NOT a persisted column.
        contentStatus:
          type: string
          enum:
            - NOT_STARTED
            - IN_PROGRESS
            - SUBMITTED
            - REVISION_NEEDED
          description: Content submission status
        sessionId:
          type: string
          format: uuid
          description: Associated session ID (if content submitted)
        submittedAt:
          type: string
          format: date-time
          description: When content was submitted
        notes:
          type: string
          description: Notes (e.g., rejection feedback)

  securitySchemes:
    CognitoAuth:
      type: oauth2
      description: AWS Cognito authentication with role-based access control
      flows:
        implicit:
          authorizationUrl: https://auth.batbern.ch/oauth2/authorize
          scopes:
            ORGANIZER: Organizer role - can manage speakers, events, and content
            ATTENDEE: Attendee role - read-only access to public speaker info
            SPEAKER: Speaker role - can manage own profile and content
