openapi: "3.0.3"
info:
  title: "BATbern Partner Meetings API"
  description: "Partner meeting coordination endpoints — Story 8.3"
  version: "1.0.0"

servers:
  - url: "/api/v1"
    description: "API Gateway base URL"

tags:
  - name: "Partner Meetings"
    description: "Partner meeting coordination — CRUD and invites (Story 8.3)"
  - name: "Partner Meeting Rsvps"
    description: "Partner meeting RSVP listing (Story 8.3)"

paths:
  /partner-meetings:
    get:
      summary: "List all partner meetings"
      description: |
        Returns all partner meetings sorted by meeting date descending.
        ORGANIZER role required.

        Bounded collection (two meetings per event year) — intentionally unpaginated.
      operationId: "listPartnerMeetings"
      tags: ["Partner Meetings"]
      responses:
        "200":
          description: "List of partner meetings"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PartnerMeetingDTO"
        "403":
          description: "Forbidden — ORGANIZER role required"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
      security:
        - bearerAuth: []

    post:
      summary: "Create a new partner meeting"
      description: |
        Creates a partner meeting record linked to a BATbern event by event code.
        Date is auto-filled from the linked event. ORGANIZER role required.
      operationId: "createPartnerMeeting"
      tags: ["Partner Meetings"]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateMeetingRequest"
      responses:
        "201":
          description: "Meeting created"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerMeetingDTO"
        "400":
          description: "Invalid request"
        "403":
          description: "Forbidden — ORGANIZER role required"
      security:
        - bearerAuth: []

  /partner-meetings/{meetingId}:
    get:
      summary: "Get a single partner meeting"
      operationId: "getPartnerMeeting"
      tags: ["Partner Meetings"]
      parameters:
        - name: meetingId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: "Meeting details"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerMeetingDTO"
        "403":
          description: "Forbidden"
        "404":
          description: "Meeting not found"
      security:
        - bearerAuth: []

    patch:
      summary: "Update meeting agenda or notes"
      description: |
        Updates the agenda (before meeting) or notes (after meeting).
        ORGANIZER role required.
      operationId: "updatePartnerMeeting"
      tags: ["Partner Meetings"]
      parameters:
        - name: meetingId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateMeetingRequest"
      responses:
        "200":
          description: "Meeting updated"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerMeetingDTO"
        "400":
          description: "Invalid request"
        "403":
          description: "Forbidden"
        "404":
          description: "Meeting not found"
      security:
        - bearerAuth: []

    delete:
      summary: "Delete a partner meeting"
      description: |
        Deletes a partner meeting. If a calendar invite was already sent, a METHOD:CANCEL
        ICS is dispatched asynchronously to all partners and organizers so their calendar
        clients remove the entry. RSVPs are cascade-deleted (V9 ON DELETE CASCADE).
        ORGANIZER role required.
      operationId: "deletePartnerMeeting"
      tags: ["Partner Meetings"]
      parameters:
        - name: meetingId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: "Meeting deleted"
        "403":
          description: "Forbidden"
        "404":
          description: "Meeting not found"
      security:
        - bearerAuth: []

  /partner-meetings/{meetingId}/send-invite:
    post:
      summary: "Send calendar invite to all partner contacts"
      description: |
        Generates an ICS file (RFC 5545) with two VEVENTs — the partner meeting and
        the linked BATbern event — and sends it via SES to all partner contacts.
        Returns 202 Accepted immediately; email is dispatched asynchronously.
        ORGANIZER role required.
      operationId: "sendPartnerMeetingInvite"
      tags: ["Partner Meetings"]
      parameters:
        - name: meetingId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "202":
          description: "Invite queued for sending"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SendInviteResponse"
        "403":
          description: "Forbidden"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: "Meeting not found"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
      security:
        - bearerAuth: []

  /partner-meetings/{meetingId}/rsvps:
    get:
      summary: "List RSVPs for a partner meeting"
      description: |
        Returns the RSVP list and accepted/declined/tentative summary for a meeting — Story 10.27 (AC7).
        ORGANIZER role required.

        RSVPs are recorded by an internal, VPC-only callback `POST /internal/partner-meetings/rsvps`
        (no JWT — secured by the Service Connect private DNS namespace) that ingests inbound iCal
        REPLY emails. That callback is intentionally NOT exposed through the API gateway and is
        therefore not part of this public contract.
      operationId: "getMeetingRsvps"
      tags: ["Partner Meeting Rsvps"]
      parameters:
        - name: meetingId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: "RSVP list and summary"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeetingRsvpListResponse"
        "403":
          description: "Forbidden — ORGANIZER role required"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: "Meeting not found"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
      security:
        - bearerAuth: []

components:
  schemas:
    PartnerMeetingDTO:
      type: object
      x-class-extra-annotation: '@com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)'
      properties:
        id:
          type: string
          format: uuid
          description: "Meeting ID"
        eventCode:
          type: string
          description: "Linked BATbern event code (ADR-003)"
          example: "BATbern57"
        meetingType:
          type: string
          enum: [SPRING, AUTUMN]
          description: "Whether this is the spring or autumn partner meeting"
        meetingDate:
          type: string
          format: date
          description: "Meeting date (same as the BATbern event date)"
        startTime:
          type: string
          example: "12:00"
          description: "Meeting start time (HH:mm)"
        endTime:
          type: string
          example: "14:00"
          description: "Meeting end time (HH:mm)"
        location:
          type: string
          description: "Meeting venue/location"
        agenda:
          type: string
          description: "Free-text agenda"
        notes:
          type: string
          description: "Post-meeting notes"
        inviteSentAt:
          type: string
          format: date-time
          nullable: true
          description: "Timestamp when calendar invite was sent, null if not yet sent"
        createdBy:
          type: string
          description: "Organizer username (ADR-003)"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
        - id
        - eventCode
        - meetingType
        - meetingDate
        - startTime
        - endTime
        - createdBy
        - createdAt
        - updatedAt

    CreateMeetingRequest:
      type: object
      properties:
        eventCode:
          type: string
          description: "BATbern event code to link the meeting to (ADR-003)"
          example: "BATbern57"
        meetingType:
          type: string
          enum: [SPRING, AUTUMN]
        startTime:
          type: string
          example: "12:00"
          description: "Meeting start time (HH:mm)"
        endTime:
          type: string
          example: "14:00"
          description: "Meeting end time (HH:mm)"
        location:
          type: string
          description: "Meeting venue"
        agenda:
          type: string
          description: "Initial agenda text"
      required:
        - eventCode
        - meetingType
        - startTime
        - endTime

    UpdateMeetingRequest:
      type: object
      description: "All fields optional — only provided fields are updated"
      properties:
        agenda:
          type: string
          nullable: true
          description: "Meeting agenda (free text)"
        notes:
          type: string
          nullable: true
          description: "Post-meeting notes (free text)"
        location:
          type: string
          nullable: true
        startTime:
          type: string
          description: "HH:mm format"
        endTime:
          type: string
          description: "HH:mm format"

    SendInviteResponse:
      type: object
      properties:
        message:
          type: string
          example: "Calendar invite is being sent to all partner contacts"
        meetingId:
          type: string
          format: uuid
        recipientCount:
          type: integer
          description: "Number of partner contacts the invite is being sent to"
      required:
        - message
        - meetingId
        - recipientCount

    MeetingRsvpListResponse:
      type: object
      description: "RSVP list + summary for a partner meeting — Story 10.27 (AC7)"
      properties:
        meetingId:
          type: string
          format: uuid
        inviteSentAt:
          type: string
          format: date-time
          nullable: true
          description: "When the calendar invite was sent, null if not yet sent"
        rsvps:
          type: array
          items:
            $ref: "#/components/schemas/RsvpDTO"
        summary:
          $ref: "#/components/schemas/RsvpSummary"

    RsvpDTO:
      type: object
      properties:
        attendeeEmail:
          type: string
          description: "Email of the partner contact who responded"
        status:
          type: string
          enum: [ACCEPTED, DECLINED, TENTATIVE]
          description: "iCal PARTSTAT mapped to an RSVP status"
        respondedAt:
          type: string
          format: date-time

    RsvpSummary:
      type: object
      description: "RSVP counts by status"
      properties:
        accepted:
          type: integer
        declined:
          type: integer
        tentative:
          type: integer

    # Shared-kernel types (imported, not generated)
    ErrorResponse:
      $ref: "./_shared.openapi.yml#/components/schemas/ErrorResponse"

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
