openapi: 3.1.0
info:
  title: Vstrechi API
  version: 0.1.0
  description: API contract for the Vstrechi MVP backend.
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://meets.cc/api/backend
    description: Staging HTTPS REST API.
  - url: https://meets.cc
    description: Staging web and Yandex OAuth origin.
tags:
  - name: health
    description: Runtime health checks.
  - name: auth
    description: M2 dev authentication and session endpoints.
  - name: users
    description: Current user and profile endpoints.
  - name: events
    description: Event creation, details, lifecycle and discovery endpoints.
  - name: attendance
    description: M3 event participation, invitations and RSVP endpoints.
  - name: feed
    description: M4 feed endpoint. Until graph modules exist, feed uses published public events.
  - name: event_posts
    description: M11 event announcements, posts and comments.
  - name: coordination
    description: M5 date coordination endpoints for event planning.
  - name: reviews
    description: M7 event reviews after completed meetings.
  - name: moderation
    description: M7 user reports and future admin-lite moderation endpoints.
  - name: blocks
    description: M7 user-to-user blocking endpoints.
  - name: calendar
    description: M8 social calendar entries, invites, reminders and event links.
  - name: notifications
    description: Persistent user notifications and unread state.
  - name: payments
    description: M6 payment profile and future payment flow endpoints.
  - name: images
    description: Controlled image proxy for externally hosted user and event media.
paths:
  /healthz:
    get:
      tags:
        - health
      operationId: getHealthz
      summary: Check API, PostgreSQL and Redis availability.
      security: []
      responses:
        "200":
          description: API dependencies are available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "503":
          description: One or more API dependencies are unavailable.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
  /images/proxy:
    get:
      tags:
        - images
      operationId: proxyImage
      summary: Proxy an external HTTPS image through the controlled image proxy.
      description: Fetches only HTTPS image URLs, rejects private or local destinations and returns only image content.
      security: []
      parameters:
        - name: url
          in: query
          required: true
          schema:
            type: string
            format: uri
      responses:
        "200":
          description: Proxied image bytes.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "400":
          $ref: "#/components/responses/BadRequest"
        "413":
          description: Image is larger than the proxy limit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "415":
          description: Upstream response is not an image.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Image is unavailable.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/dev/start:
    post:
      tags:
        - auth
      operationId: startDevAuthChallenge
      summary: Start a dev email authentication challenge.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartDevAuthChallengeRequest"
      responses:
        "200":
          description: Dev authentication challenge created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StartDevAuthChallengeResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/dev/verify:
    post:
      tags:
        - auth
      operationId: verifyDevAuthChallenge
      summary: Verify a dev email challenge and create a session.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VerifyDevAuthChallengeRequest"
      responses:
        "200":
          description: Challenge verified and session created.
          headers:
            Set-Cookie:
              description: HttpOnly refresh token cookie.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/phone/call/start:
    post:
      tags:
        - auth
      operationId: startPhoneCallAuth
      summary: Start phone authentication through SMS.RU Call Auth.
      description: Creates a five-minute phone call challenge and returns the number the user must call from the submitted phone.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartPhoneCallAuthRequest"
      responses:
        "200":
          description: Phone call challenge created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StartPhoneCallAuthResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/phone/call/status:
    post:
      tags:
        - auth
      operationId: completePhoneCallAuth
      summary: Check phone call authentication status.
      description: Polls the provider status. When verified, creates the normal refresh session and sets the refresh cookie.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompletePhoneCallAuthRequest"
      responses:
        "200":
          description: Current phone call auth state. Verified responses include an auth session.
          headers:
            Set-Cookie:
              description: HttpOnly refresh token cookie when status is verified.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PhoneCallAuthStatusResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/yandex/start:
    get:
      tags:
        - auth
      operationId: startYandexOAuth
      summary: Start the Yandex OAuth login flow.
      security: []
      parameters:
        - name: return_to
          in: query
          required: false
          schema:
            type: string
            default: /profile
          description: Local path to redirect to after successful login.
      responses:
        "303":
          description: Redirect to Yandex OAuth authorization page.
          headers:
            Location:
              description: Yandex OAuth authorization URL.
              schema:
                type: string
                format: uri
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/yandex/callback:
    get:
      tags:
        - auth
      operationId: completeYandexOAuth
      summary: Complete the Yandex OAuth callback and create a session.
      security: []
      parameters:
        - name: code
          in: query
          required: false
          schema:
            type: string
        - name: state
          in: query
          required: false
          schema:
            type: string
        - name: error
          in: query
          required: false
          schema:
            type: string
      responses:
        "303":
          description: Redirect to the app after success or back to login with an OAuth error.
          headers:
            Set-Cookie:
              description: HttpOnly refresh token cookie on success.
              schema:
                type: string
            Location:
              description: Local redirect target.
              schema:
                type: string
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/refresh:
    post:
      tags:
        - auth
      operationId: refreshAuthSession
      summary: Rotate a refresh session.
      description: Accepts the HttpOnly refresh cookie. Native mobile clients may send `refresh_token` in the JSON body when cookie storage is unavailable.
      security:
        - cookieAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefreshSessionRequest"
      responses:
        "200":
          description: Session rotated.
          headers:
            Set-Cookie:
              description: HttpOnly refresh token cookie.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RefreshSessionResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/sessions:
    get:
      tags:
        - auth
      operationId: listAuthSessions
      summary: List active refresh sessions for the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Active sessions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthSessionsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/sessions/{session_id}:
    delete:
      tags:
        - auth
      operationId: revokeAuthSession
      summary: Revoke one active refresh session owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: session_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Session revoked.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/sessions/revoke-others:
    post:
      tags:
        - auth
      operationId: revokeOtherAuthSessions
      summary: Revoke every active refresh session except the current one.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "204":
          description: Other sessions revoked.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /auth/logout:
    post:
      tags:
        - auth
      operationId: logoutAuthSession
      summary: Revoke the current refresh session.
      security:
        - cookieAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefreshSessionRequest"
      responses:
        "204":
          description: Session revoked or already absent.
          headers:
            Set-Cookie:
              description: Expired refresh token cookie.
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /me:
    get:
      tags:
        - users
      operationId: getCurrentUser
      summary: Get the current user and profile.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Current user and profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurrentUser"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /me/profile:
    patch:
      tags:
        - users
      operationId: updateCurrentUserProfile
      summary: Update the current user's profile.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateProfileRequest"
      responses:
        "200":
          description: Updated current user and profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurrentUser"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /users/profiles:
    get:
      tags:
        - users
      operationId: listPublicUserProfiles
      summary: Search public user profiles.
      security: []
      parameters:
        - name: q
          in: query
          schema:
            type: string
            maxLength: 120
        - name: city
          in: query
          schema:
            type: string
            maxLength: 120
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Opaque cursor returned by the previous page.
          schema:
            type: string
      responses:
        "200":
          description: Public profiles.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicProfilesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "500":
          $ref: "#/components/responses/InternalError"
  /users/{user_id}/profile:
    get:
      tags:
        - users
      operationId: getPublicUserProfile
      summary: Get a public user profile.
      security: []
      parameters:
        - $ref: "#/components/parameters/UserID"
      responses:
        "200":
          description: Public profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicProfile"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /users/{user_id}/follow:
    parameters:
      - $ref: "#/components/parameters/UserID"
    get:
      tags:
        - users
      operationId: getUserFollowStatus
      summary: Get follow status between the current user and a public profile.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Follow status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FollowStatus"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - users
      operationId: followUser
      summary: Follow a public profile.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Follow status after following.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FollowStatus"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - users
      operationId: unfollowUser
      summary: Stop following a public profile.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Follow status after unfollowing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FollowStatus"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /me/payment-profile:
    get:
      tags:
        - payments
      operationId: getCurrentUserPaymentProfile
      summary: Get the current user's organizer payment profile.
      description: M6 placeholder profile for paid event organizer verification; no production payout account is created.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Organizer payment profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizerPaymentProfile"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - payments
      operationId: updateCurrentUserPaymentProfile
      summary: Update the current user's organizer payment profile placeholder.
      description: Allows self-setting the MVP placeholder status to unverified or verified; blocked is reserved for provider/admin state.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateOrganizerPaymentProfileRequest"
      responses:
        "200":
          description: Updated organizer payment profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganizerPaymentProfile"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events:
    get:
      tags:
        - events
      operationId: listEvents
      summary: List published public events for discovery.
      security: []
      parameters:
        - name: q
          in: query
          schema:
            type: string
            maxLength: 120
        - name: category
          in: query
          schema:
            type: string
            maxLength: 80
        - name: city
          in: query
          schema:
            type: string
            maxLength: 200
        - name: price
          in: query
          description: Price filter. Omit or use `any` to include both free and paid public events.
          schema:
            type: string
            enum:
              - any
              - free
              - paid
        - name: lat
          in: query
          description: Latitude for nearby event filtering. Must be combined with `lng` and `radius_km`.
          schema:
            type: number
            format: double
            minimum: -90
            maximum: 90
        - name: lng
          in: query
          description: Longitude for nearby event filtering. Must be combined with `lat` and `radius_km`.
          schema:
            type: number
            format: double
            minimum: -180
            maximum: 180
        - name: radius_km
          in: query
          description: Radius in kilometers for nearby event filtering. Must be combined with `lat` and `lng`.
          schema:
            type: number
            format: double
            exclusiveMinimum: 0
            maximum: 500
        - name: organizer_id
          in: query
          schema:
            type: string
            format: uuid
        - name: participant_user_id
          in: query
          description: Public attendance filter; incognito attendance is excluded.
          schema:
            type: string
            format: uuid
        - name: starts_from
          in: query
          schema:
            type: string
            format: date-time
        - name: starts_to
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Published public events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - events
      operationId: createEvent
      summary: Create a draft event for the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEventRequest"
      responses:
        "201":
          description: Draft event created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDetails"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/map:
    get:
      tags:
        - events
      operationId: listEventMapItems
      summary: List published public events with coordinates for map discovery.
      security: []
      parameters:
        - name: bbox
          in: query
          description: Bounding box as min_lng,min_lat,max_lng,max_lat.
          schema:
            type: string
            example: "13.0,52.0,14.0,53.0"
        - name: category
          in: query
          schema:
            type: string
            maxLength: 80
        - name: price
          in: query
          description: Price filter. Omit or use `any` to include both free and paid public map events.
          schema:
            type: string
            enum:
              - any
              - free
              - paid
        - name: lat
          in: query
          description: Latitude for nearby map filtering. Must be combined with `lng` and `radius_km`.
          schema:
            type: number
            format: double
            minimum: -90
            maximum: 90
        - name: lng
          in: query
          description: Longitude for nearby map filtering. Must be combined with `lat` and `radius_km`.
          schema:
            type: number
            format: double
            minimum: -180
            maximum: 180
        - name: radius_km
          in: query
          description: Radius in kilometers for nearby map filtering. Must be combined with `lat` and `lng`.
          schema:
            type: number
            format: double
            exclusiveMinimum: 0
            maximum: 500
        - name: starts_from
          in: query
          schema:
            type: string
            format: date-time
        - name: starts_to
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: Published public map events.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventMapResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}:
    parameters:
      - $ref: "#/components/parameters/EventID"
    get:
      tags:
        - events
      operationId: getEvent
      summary: Get event details visible to the current viewer.
      security: []
      responses:
        "200":
          description: Event details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDetails"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Event missing or hidden by visibility policy.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - events
      operationId: updateEvent
      summary: Update an event owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateEventRequest"
      responses:
        "200":
          description: Updated event details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDetails"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/publish:
    parameters:
      - $ref: "#/components/parameters/EventID"
    post:
      tags:
        - events
      operationId: publishEvent
      summary: Publish a draft event owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Published event details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDetails"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Paid event organizer payment profile is not verified.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/cancel:
    parameters:
      - $ref: "#/components/parameters/EventID"
    post:
      tags:
        - events
      operationId: cancelEvent
      summary: Cancel an event owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Canceled event details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDetails"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/join:
    parameters:
      - $ref: "#/components/parameters/EventID"
    post:
      tags:
        - attendance
      operationId: joinEvent
      summary: Join a published public event.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/JoinEventRequest"
      responses:
        "200":
          description: Current user's participant row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventParticipant"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - attendance
      operationId: leaveEvent
      summary: Leave a public event joined by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Updated participant row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventParticipant"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/payment-intents:
    parameters:
      - $ref: "#/components/parameters/EventID"
    post:
      tags:
        - payments
      operationId: createEventPaymentIntent
      summary: Start a checkout payment intent for a paid public event.
      description: Creates an MVP dev-provider payment intent only; successful attendance confirmation is handled by future webhook processing.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "201":
          description: Payment intent created with checkout URL placeholder.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentIntent"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Event is not available for paid checkout by the current user.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /payments/intents/{payment_intent_id}:
    parameters:
      - $ref: "#/components/parameters/PaymentIntentID"
    get:
      tags:
        - payments
      operationId: getPaymentIntent
      summary: Get the current user's payment intent.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Payment intent visible to the current user.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentIntent"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /payments/webhooks/{provider}:
    parameters:
      - $ref: "#/components/parameters/PaymentProvider"
    post:
      tags:
        - payments
      operationId: processPaymentWebhook
      summary: Process a payment provider webhook.
      description: M6 dev-provider webhook endpoint with signature verification placeholder and idempotency on provider event id.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
              description: Provider-specific webhook payload. Dev provider expects id, type and provider_payment_id.
      responses:
        "200":
          description: Webhook processed or ignored as a duplicate.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentWebhookResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          description: Webhook signature is invalid.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/posts:
    get:
      tags:
        - event_posts
      operationId: listEventPostsBatch
      summary: List event post threads for multiple events.
      description: Batch helper for the unified feed. Public published events are readable anonymously; private events require current-user event access.
      security:
        - {}
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: event_id
          in: query
          required: false
          schema:
            type: array
            maxItems: 50
            items:
              type: string
              format: uuid
          style: form
          explode: true
      responses:
        "200":
          description: Event post threads grouped by event id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventPostThreadsBatchResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/posts:
    parameters:
      - $ref: "#/components/parameters/EventID"
    get:
      tags:
        - event_posts
      operationId: listEventPosts
      summary: List event announcements, posts and comments.
      description: Public published events are readable anonymously. Private events require current-user event access.
      security:
        - {}
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Event post threads.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventPostThreadsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - event_posts
      operationId: createEventPost
      summary: Create an event announcement, important organizer message or post.
      description: Only the event organizer can create event posts in the M11 slice.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEventPostRequest"
      responses:
        "201":
          description: Created event post.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventPost"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/posts/{post_id}/comments:
    parameters:
      - $ref: "#/components/parameters/EventID"
      - name: post_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags:
        - event_posts
      operationId: createEventPostComment
      summary: Comment on an event post.
      description: Authenticated users who can view the event can comment. `parent_comment_id` creates a threaded reply.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEventPostCommentRequest"
      responses:
        "201":
          description: Created event post comment.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventPostComment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/posts/{post_id}/like:
    parameters:
      - $ref: "#/components/parameters/EventID"
      - name: post_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags:
        - event_posts
      operationId: likeEventPost
      summary: Like an event post or announcement.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Updated like state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventPostLikeState"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - event_posts
      operationId: unlikeEventPost
      summary: Remove the current user's like from an event post or announcement.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Updated like state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventPostLikeState"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/reviews:
    parameters:
      - $ref: "#/components/parameters/EventID"
    get:
      tags:
        - reviews
      operationId: listEventReviews
      summary: List reviews visible to the current user.
      description: Public published event reviews are visible to authenticated users; private event reviews require event access.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Event reviews.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventReviewsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - reviews
      operationId: upsertEventReview
      summary: Create or update the current user's review for a completed event.
      description: Allowed only for authenticated non-organizer participants after the event has completed.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpsertEventReviewRequest"
      responses:
        "201":
          description: Review created or updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventReview"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /reports:
    post:
      tags:
        - moderation
      operationId: createReport
      summary: Report an event, user or review.
      description: Creates an open moderation report. Reporter identity comes from the current auth session; status and moderation action cannot be supplied by the reporter.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateReportRequest"
      responses:
        "201":
          description: Report created with open status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Report"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /admin/reports:
    get:
      tags:
        - moderation
      operationId: listAdminReports
      summary: List moderation reports for admin-lite.
      description: Requires current user to be present in the moderation admin allowlist.
      security:
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/ReportStatus"
      responses:
        "200":
          description: Moderation reports.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"
  /admin/reports/{report_id}:
    parameters:
      - $ref: "#/components/parameters/ReportID"
    patch:
      tags:
        - moderation
      operationId: updateAdminReportStatus
      summary: Update a moderation report status and create an audit action.
      description: Requires current user to be present in the moderation admin allowlist. Every successful status update creates one moderation action row.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateReportStatusRequest"
      responses:
        "200":
          description: Report status updated with audit action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UpdateReportStatusResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /users/{user_id}/block:
    parameters:
      - $ref: "#/components/parameters/UserID"
    get:
      tags:
        - blocks
      operationId: getUserBlockStatus
      summary: Read current user's block status for another user.
      description: Returns whether the authenticated user currently blocks the target user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Current block status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserBlockStatus"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - blocks
      operationId: blockUser
      summary: Block another user.
      description: Creates or keeps an existing user-to-user block. Self-block is invalid.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: User block created or already present.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserBlock"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - blocks
      operationId: unblockUser
      summary: Unblock another user.
      description: Removes a user-to-user block if present.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "204":
          description: User is unblocked or was not blocked.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/invitations:
    parameters:
      - $ref: "#/components/parameters/EventID"
    post:
      tags:
        - attendance
      operationId: inviteEventUser
      summary: Invite an existing user by phone to a private event.
      description: Uses `invited_phone` for Yandex phone-only accounts. `email` remains accepted for legacy/dev users.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEventInvitationRequest"
      responses:
        "201":
          description: Invitation created or reset to pending.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventInvitation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/rsvp:
    parameters:
      - $ref: "#/components/parameters/EventID"
    patch:
      tags:
        - attendance
      operationId: respondEventInvitation
      summary: Respond to the current user's event invitation.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RSVPEventInvitationRequest"
      responses:
        "200":
          description: Updated invitation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventInvitation"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/date-mode:
    parameters:
      - $ref: "#/components/parameters/EventID"
    patch:
      tags:
        - coordination
      operationId: setEventDateMode
      summary: Set event date mode to fixed or vote.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetEventDateModeRequest"
      responses:
        "204":
          description: Date mode updated.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/date-options:
    parameters:
      - $ref: "#/components/parameters/EventID"
    get:
      tags:
        - coordination
      operationId: listEventDateOptions
      summary: List date options visible to the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Event date options.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDateOptionsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - coordination
      operationId: createEventDateOption
      summary: Add a date option for an event owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEventDateOptionRequest"
      responses:
        "201":
          description: Date option created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDateOption"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/date-options/{option_id}/votes:
    parameters:
      - $ref: "#/components/parameters/EventID"
      - $ref: "#/components/parameters/DateOptionID"
    post:
      tags:
        - coordination
      operationId: voteEventDateOption
      summary: Vote for one date option visible to the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Current user's date vote.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDateVote"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - coordination
      operationId: removeEventDateOptionVote
      summary: Remove the current user's vote from one date option.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "204":
          description: Vote removed.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/date-options/{option_id}/select:
    parameters:
      - $ref: "#/components/parameters/EventID"
      - $ref: "#/components/parameters/DateOptionID"
    post:
      tags:
        - coordination
      operationId: selectEventDateOption
      summary: Select the final event date option as organizer.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Selected date option.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventDateOption"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/messages:
    parameters:
      - $ref: "#/components/parameters/EventID"
    get:
      tags:
        - coordination
      operationId: listEventChatMessages
      summary: List chat messages visible to the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: before
          in: query
          description: Return messages created before this timestamp.
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Event chat messages.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventChatMessagesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - coordination
      operationId: sendEventChatMessage
      summary: Send a chat message to an event visible to the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendEventChatMessageRequest"
      responses:
        "201":
          description: Chat message sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventChatMessage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/ws:
    parameters:
      - $ref: "#/components/parameters/EventID"
    get:
      tags:
        - coordination
      operationId: openEventChatWebSocket
      summary: Authenticate event chat WebSocket access.
      description: M5 placeholder endpoint authenticates and checks event chat access, but realtime WebSocket broadcast is not implemented yet.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Chat WebSocket access is authorized; realtime WebSocket transport is not implemented yet.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventChatWebSocketAuthResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/reminders:
    parameters:
      - $ref: "#/components/parameters/EventID"
    get:
      tags:
        - coordination
      operationId: getEventReminderSettings
      summary: Get reminder settings visible to the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Event reminder settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventReminderSettings"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - coordination
      operationId: setEventReminderSettings
      summary: Update reminder settings as event organizer.
      description: Stores reminder settings only; external email, SMS or push delivery is outside M5.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetEventReminderSettingsRequest"
      responses:
        "200":
          description: Updated event reminder settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventReminderSettings"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /calendar/entries:
    get:
      tags:
        - calendar
      operationId: listCalendarEntries
      summary: List the current user's calendar entries.
      security:
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: event_id
          in: query
          schema:
            type: string
            format: uuid
        - name: starts_from
          in: query
          schema:
            type: string
            format: date-time
        - name: starts_to
          in: query
          schema:
            type: string
            format: date-time
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/CalendarEntryStatus"
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: Calendar entries.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarEntriesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - calendar
      operationId: createCalendarEntry
      summary: Create a manual calendar entry for the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCalendarEntryRequest"
      responses:
        "201":
          description: Calendar entry created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarEntry"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /calendar/entries/{entry_id}:
    parameters:
      - $ref: "#/components/parameters/CalendarEntryID"
    get:
      tags:
        - calendar
      operationId: getCalendarEntry
      summary: Get a calendar entry owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Calendar entry.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarEntry"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - calendar
      operationId: updateCalendarEntry
      summary: Update a calendar entry owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCalendarEntryRequest"
      responses:
        "200":
          description: Calendar entry updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarEntry"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - calendar
      operationId: cancelCalendarEntry
      summary: Cancel a calendar entry owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Calendar entry canceled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarEntry"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /calendar/entries/{entry_id}/invites:
    parameters:
      - $ref: "#/components/parameters/CalendarEntryID"
    post:
      tags:
        - calendar
      operationId: createCalendarInvite
      summary: Invite a user to a calendar entry owned by the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCalendarInviteRequest"
      responses:
        "201":
          description: Calendar invite created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarInvite"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /calendar/invites:
    get:
      tags:
        - calendar
      operationId: listCalendarInvites
      summary: List calendar invites visible to the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - in: query
          name: role
          schema:
            type: string
            enum:
              - all
              - incoming
              - outgoing
            default: all
        - in: query
          name: status
          schema:
            $ref: "#/components/schemas/CalendarInviteStatus"
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: Calendar invites.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarInvitesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /calendar/invites/{invite_id}:
    parameters:
      - $ref: "#/components/parameters/CalendarInviteID"
    get:
      tags:
        - calendar
      operationId: getCalendarInvite
      summary: Get a calendar invite visible to the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Calendar invite with entry, inviter and visible participant details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarInviteDetails"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - calendar
      operationId: respondCalendarInvite
      summary: Respond to a calendar invite as the invited user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RespondCalendarInviteRequest"
      responses:
        "200":
          description: Calendar invite response saved.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarInvite"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /calendar/entries/{entry_id}/reminders:
    parameters:
      - $ref: "#/components/parameters/CalendarEntryID"
    get:
      tags:
        - calendar
      operationId: getCalendarReminderSettings
      summary: Get calendar reminder settings visible to the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Calendar reminder settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarReminderSettings"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      tags:
        - calendar
      operationId: setCalendarReminderSettings
      summary: Update reminder settings for a calendar entry owned by the current user.
      description: Stores reminder settings only; external email, SMS or push delivery is outside M8.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetCalendarReminderSettingsRequest"
      responses:
        "200":
          description: Calendar reminder settings updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarReminderSettings"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /events/{event_id}/calendar-entry:
    parameters:
      - $ref: "#/components/parameters/EventID"
    post:
      tags:
        - calendar
      operationId: linkEventAttendanceToCalendar
      summary: Create or refresh the current user's event-linked calendar entry.
      description: The backend links only published events with start time where the current user is the organizer or a going participant.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Event-linked calendar entry created or refreshed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarEntry"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - calendar
      operationId: cancelEventAttendanceCalendarEntry
      summary: Cancel the current user's event-linked calendar entry after leaving.
      description: The backend cancels the linked calendar entry only when the current user no longer has a going participant record for the event.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Event-linked calendar entry canceled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarEntry"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
  /feed:
    get:
      tags:
        - feed
      operationId: listFeed
      summary: List feed events.
      description: Returns feed events. `scope=all` uses a transparent recommendation score over engagement, freshness, event timing and optional viewer signals. `scope=going`, `scope=friends`, and `scope=following` are personal and require the current user.
      security:
        - {}
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: scope
          in: query
          description: Feed filter scope. `friends` uses mutual follows, `following` uses profiles followed by the current user, and `going` includes public and incognito attendance.
          schema:
            type: string
            default: all
            enum:
              - all
              - friends
              - groups
              - following
              - going
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Feed items.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /feed/preferences:
    get:
      tags:
        - feed
      operationId: listFeedPreferences
      summary: List current user's persisted feed preferences.
      security:
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: item_key
          in: query
          description: Feed item key such as `announcement:{event_id}` or `post:{post_id}`. May be repeated. Omit to return all preferences for the current user.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
      responses:
        "200":
          description: Feed preferences.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedPreferencesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags:
        - feed
      operationId: updateFeedPreference
      summary: Persist one feed preference for the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateFeedPreferenceRequest"
      responses:
        "200":
          description: Updated feed preference.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedPreference"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /notifications:
    get:
      tags:
        - notifications
      operationId: listNotifications
      summary: List persistent notifications for the current user.
      security:
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: unread
          in: query
          schema:
            type: boolean
            default: false
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 40
        - name: cursor
          in: query
          description: Opaque keyset cursor returned by the previous page.
          schema:
            type: string
      responses:
        "200":
          description: Notification list and unread count.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /notifications/unread-count:
    get:
      tags:
        - notifications
      operationId: getNotificationUnreadCount
      summary: Get the current user's unread notification count.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Unread notification count.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationUnreadCountResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /notifications/read-all:
    post:
      tags:
        - notifications
      operationId: markAllNotificationsRead
      summary: Mark all current user's notifications as read.
      security:
        - bearerAuth: []
        - cookieAuth: []
      responses:
        "200":
          description: Number of notifications marked read.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MarkAllNotificationsReadResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /notifications/{notification_id}/read:
    post:
      tags:
        - notifications
      operationId: markNotificationRead
      summary: Mark one notification as read.
      security:
        - bearerAuth: []
        - cookieAuth: []
      parameters:
        - name: notification_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Updated notification.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Notification"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"
components:
  parameters:
    EventID:
      name: event_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    UserID:
      name: user_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ReportID:
      name: report_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DateOptionID:
      name: option_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    CalendarEntryID:
      name: entry_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    CalendarInviteID:
      name: invite_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    PaymentIntentID:
      name: payment_intent_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    PaymentProvider:
      name: provider
      in: path
      required: true
      schema:
        type: string
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: opaque
      description: Short-lived opaque access token returned by sign-in and refresh endpoints.
    cookieAuth:
      type: apiKey
      in: cookie
      name: vstrechi_refresh_token
  responses:
    BadRequest:
      description: Invalid request.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Missing or invalid authentication.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: Action is not allowed for the current user or event state.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    RateLimited:
      description: Too many authentication attempts.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ServiceUnavailable:
      description: Service is temporarily unavailable or disabled.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    InternalError:
      description: Internal server error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    HealthResponse:
      type: object
      additionalProperties: false
      required:
        - status
        - postgres
        - redis
      properties:
        status:
          type: string
          enum:
            - ok
            - error
        postgres:
          type: string
          enum:
            - ok
            - error
        redis:
          type: string
          enum:
            - ok
            - error
    StartDevAuthChallengeRequest:
      type: object
      additionalProperties: false
      required:
        - email
      properties:
        email:
          type: string
          format: email
    StartDevAuthChallengeResponse:
      type: object
      additionalProperties: false
      required:
        - challenge_id
        - email
        - dev_code
        - expires_at
      properties:
        challenge_id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        dev_code:
          type: string
          description: Dev-only verification code for local/staging.
          minLength: 6
          maxLength: 6
        expires_at:
          type: string
          format: date-time
    VerifyDevAuthChallengeRequest:
      type: object
      additionalProperties: false
      required:
        - email
        - code
      properties:
        email:
          type: string
          format: email
        code:
          type: string
          minLength: 6
          maxLength: 6
    StartPhoneCallAuthRequest:
      type: object
      additionalProperties: false
      required:
        - phone
      properties:
        phone:
          type: string
          example: "+79991234567"
    StartPhoneCallAuthResponse:
      type: object
      additionalProperties: false
      required:
        - challenge_id
        - phone
        - call_phone
        - call_phone_pretty
        - expires_at
      properties:
        challenge_id:
          type: string
          format: uuid
        phone:
          type: string
          example: "+79991234567"
        call_phone:
          type: string
          example: "78005008275"
        call_phone_pretty:
          type: string
          example: "+7 (800) 500-8275"
        expires_at:
          type: string
          format: date-time
    CompletePhoneCallAuthRequest:
      type: object
      additionalProperties: false
      required:
        - challenge_id
      properties:
        challenge_id:
          type: string
          format: uuid
    PhoneCallAuthStatusResponse:
      type: object
      additionalProperties: false
      required:
        - status
        - expires_at
      properties:
        status:
          type: string
          enum:
            - pending
            - verified
            - expired
        expires_at:
          type: string
          format: date-time
        auth:
          $ref: "#/components/schemas/AuthResponse"
    AuthResponse:
      type: object
      additionalProperties: false
      required:
        - current_user
        - session_id
        - refresh_token
        - access_token
        - access_token_expires_at
        - token_type
        - expires_at
      properties:
        current_user:
          $ref: "#/components/schemas/CurrentUser"
        session_id:
          type: string
          format: uuid
        refresh_token:
          type: string
          description: Returned for dev and native mobile clients; also set as HttpOnly cookie for browser/WebView clients.
        access_token:
          type: string
          description: "Short-lived opaque token for `Authorization: Bearer <token>` on protected API requests."
        access_token_expires_at:
          type: string
          format: date-time
        token_type:
          type: string
          const: Bearer
        expires_at:
          type: string
          format: date-time
    RefreshSessionRequest:
      type: object
      additionalProperties: false
      properties:
        refresh_token:
          type: string
          description: Optional native mobile fallback when the refresh cookie is unavailable.
    RefreshSessionResponse:
      type: object
      additionalProperties: false
      required:
        - session_id
        - refresh_token
        - access_token
        - access_token_expires_at
        - token_type
        - expires_at
      properties:
        session_id:
          type: string
          format: uuid
        refresh_token:
          type: string
        access_token:
          type: string
          description: Rotated short-lived opaque access token.
        access_token_expires_at:
          type: string
          format: date-time
        token_type:
          type: string
          const: Bearer
        expires_at:
          type: string
          format: date-time
    AuthSessionsResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/AuthSession"
    AuthSession:
      type: object
      additionalProperties: false
      required:
        - id
        - current
        - device_label
        - expires_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        current:
          type: boolean
        device_label:
          type: string
          description: Coarse device/browser label derived from request headers without exposing raw user-agent or IP.
        last_seen_at:
          type:
            - string
            - "null"
          format: date-time
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CurrentUser:
      type: object
      additionalProperties: false
      required:
        - user
        - profile
      properties:
        user:
          $ref: "#/components/schemas/User"
        profile:
          $ref: "#/components/schemas/Profile"
    User:
      type: object
      additionalProperties: false
      required:
        - id
      properties:
        id:
          type: string
          format: uuid
        email:
          type:
            - string
            - "null"
          format: email
        phone:
          type:
            - string
            - "null"
    Profile:
      type: object
      additionalProperties: false
      required:
        - user_id
        - username
        - display_name
        - avatar_url
        - city
        - interests
      properties:
        user_id:
          type: string
          format: uuid
        username:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 30
          pattern: "^[a-z0-9_]+$"
        display_name:
          type:
            - string
            - "null"
          maxLength: 80
        avatar_url:
          type:
            - string
            - "null"
          format: uri
        city:
          type:
            - string
            - "null"
          maxLength: 120
        interests:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 80
    PublicProfile:
      type: object
      additionalProperties: false
      required:
        - user_id
        - username
        - display_name
        - avatar_url
        - city
        - interests
      properties:
        user_id:
          type: string
          format: uuid
        username:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 30
          pattern: "^[a-z0-9_]+$"
        display_name:
          type:
            - string
            - "null"
          maxLength: 80
        avatar_url:
          type:
            - string
            - "null"
          format: uri
        city:
          type:
            - string
            - "null"
          maxLength: 120
        interests:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 80
    PublicProfilesResponse:
      type: object
      additionalProperties: false
      required:
        - items
        - next_cursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/PublicProfile"
        next_cursor:
          type:
            - string
            - "null"
    FollowStatus:
      type: object
      additionalProperties: false
      required:
        - viewer_user_id
        - target_user_id
        - following
        - followed_by
        - friends
      properties:
        viewer_user_id:
          type: string
          format: uuid
        target_user_id:
          type: string
          format: uuid
        following:
          type: boolean
        followed_by:
          type: boolean
        friends:
          type: boolean
    UpdateProfileRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        username:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 30
          pattern: "^[a-z0-9_]+$"
        display_name:
          type:
            - string
            - "null"
          maxLength: 80
        avatar_url:
          type:
            - string
            - "null"
          format: uri
          maxLength: 500
        city:
          type:
            - string
            - "null"
          maxLength: 120
        interests:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 80
    OrganizerPaymentProfile:
      type: object
      additionalProperties: false
      required:
        - user_id
        - status
        - provider_account_id
        - created_at
        - updated_at
      properties:
        user_id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/OrganizerPaymentProfileStatus"
        provider_account_id:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    UpdateOrganizerPaymentProfileRequest:
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - unverified
            - verified
    OrganizerPaymentProfileStatus:
      type: string
      enum:
        - unverified
        - verified
        - blocked
    PaymentIntent:
      type: object
      additionalProperties: false
      required:
        - id
        - event_id
        - user_id
        - amount_minor
        - currency
        - platform_fee_amount_minor
        - status
        - provider
        - provider_payment_id
        - checkout_url
        - fiscal_receipt_status
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        amount_minor:
          type: integer
          minimum: 1
        currency:
          type: string
          minLength: 3
          maxLength: 3
        platform_fee_amount_minor:
          type: integer
          minimum: 0
          description: Platform fee snapshot in minor currency units.
        status:
          $ref: "#/components/schemas/PaymentIntentStatus"
        provider:
          type: string
        provider_payment_id:
          type: string
        checkout_url:
          type: string
        fiscal_receipt_status:
          $ref: "#/components/schemas/FiscalReceiptStatus"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PaymentIntentStatus:
      type: string
      enum:
        - pending
        - requires_action
        - succeeded
        - failed
        - canceled
    FiscalReceiptStatus:
      type: string
      enum:
        - not_required
        - pending
        - succeeded
        - failed
    PaymentWebhookResponse:
      type: object
      additionalProperties: false
      required:
        - event
        - intent
        - duplicate
      properties:
        event:
          $ref: "#/components/schemas/PaymentWebhookEvent"
        intent:
          anyOf:
            - $ref: "#/components/schemas/PaymentIntent"
            - type: "null"
        duplicate:
          type: boolean
    PaymentWebhookEvent:
      type: object
      additionalProperties: false
      required:
        - id
        - provider
        - provider_event_id
        - event_type
        - payload_hash
        - processed_at
        - created_at
      properties:
        id:
          type: string
          format: uuid
        provider:
          type: string
        provider_event_id:
          type: string
        event_type:
          type: string
        payload_hash:
          type: string
        processed_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
    CreateEventRequest:
      type: object
      additionalProperties: false
      required:
        - title
        - visibility
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 140
        description:
          type:
            - string
            - "null"
          maxLength: 4000
        category:
          type:
            - string
            - "null"
          maxLength: 80
        image_url:
          description: HTTP(S) image URL or uploaded image data URL.
          type:
            - string
            - "null"
          format: uri
          maxLength: 1500000
        starts_at:
          type:
            - string
            - "null"
          format: date-time
        ends_at:
          type:
            - string
            - "null"
          format: date-time
        visibility:
          $ref: "#/components/schemas/EventVisibility"
        pricing_mode:
          $ref: "#/components/schemas/EventPricingMode"
        price_amount_minor:
          type:
            - integer
            - "null"
          minimum: 1
        price_currency:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 3
        location:
          anyOf:
            - $ref: "#/components/schemas/EventLocationInput"
            - type: "null"
    UpdateEventRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 140
        description:
          type:
            - string
            - "null"
          maxLength: 4000
        category:
          type:
            - string
            - "null"
          maxLength: 80
        image_url:
          description: HTTP(S) image URL or uploaded image data URL.
          type:
            - string
            - "null"
          format: uri
          maxLength: 1500000
        starts_at:
          type:
            - string
            - "null"
          format: date-time
        ends_at:
          type:
            - string
            - "null"
          format: date-time
        visibility:
          $ref: "#/components/schemas/EventVisibility"
        pricing_mode:
          $ref: "#/components/schemas/EventPricingMode"
        price_amount_minor:
          type:
            - integer
            - "null"
          minimum: 1
        price_currency:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 3
        location:
          anyOf:
            - $ref: "#/components/schemas/EventLocationInput"
            - type: "null"
    EventDetails:
      type: object
      additionalProperties: false
      required:
        - event
        - location
        - current_participant
        - participant_count
        - feed_context
      properties:
        event:
          $ref: "#/components/schemas/Event"
        location:
          anyOf:
            - $ref: "#/components/schemas/EventLocation"
            - type: "null"
        current_participant:
          anyOf:
            - $ref: "#/components/schemas/EventParticipant"
            - type: "null"
        participant_count:
          type: integer
          minimum: 0
          description: Count of going participant-role attendees, including incognito participation without exposing identities.
        feed_context:
          $ref: "#/components/schemas/FeedContext"
    FeedEventDetails:
      type: object
      additionalProperties: false
      required:
        - event
        - location
        - participant_count
        - feed_context
      properties:
        event:
          $ref: "#/components/schemas/Event"
        location:
          anyOf:
            - $ref: "#/components/schemas/EventLocation"
            - type: "null"
        participant_count:
          type: integer
          minimum: 0
          description: Count of going participant-role attendees, including incognito participation without exposing identities.
        feed_context:
          $ref: "#/components/schemas/FeedContext"
    FeedContext:
      type: object
      additionalProperties: false
      required:
        - friend_attending
        - friend_attendee_count
        - following_organizer
        - viewer_going
      properties:
        friend_attending:
          type: boolean
        friend_attendee_count:
          type: integer
          minimum: 0
          description: Number of mutual friends with public going participation for this event.
        friend_attendees:
          type: array
          items:
            $ref: "#/components/schemas/FeedFriendAttendee"
          description: Up to 24 mutual friends with public going participation, ordered by join time.
        following_organizer:
          type: boolean
        viewer_going:
          type: boolean
        reasons:
          type: array
          items:
            $ref: "#/components/schemas/FeedReason"
          description: Optional explanation chips for why this event appears or ranks in the feed.
    FeedFriendAttendee:
      type: object
      additionalProperties: false
      required:
        - user_id
        - username
        - display_name
        - avatar_url
      properties:
        user_id:
          type: string
        username:
          type:
            - string
            - "null"
        display_name:
          type:
            - string
            - "null"
        avatar_url:
          type:
            - string
            - "null"
    FeedReason:
      type: object
      additionalProperties: false
      required:
        - label
        - tone
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 80
        tone:
          type: string
          enum:
            - accent
            - warm
            - muted
    EventListResponse:
      type: object
      additionalProperties: false
      required:
        - items
        - next_cursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/EventDetails"
        next_cursor:
          type:
            - string
            - "null"
          format: date-time
    EventMapResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/EventDetails"
    FeedResponse:
      type: object
      additionalProperties: false
      required:
        - items
        - next_cursor
        - scope
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/FeedEventDetails"
        next_cursor:
          type:
            - string
            - "null"
          format: date-time
        scope:
          type: string
          enum:
            - all
            - friends
            - groups
            - following
            - going
    FeedPreferencesResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/FeedPreference"
    FeedPreference:
      type: object
      additionalProperties: false
      required:
        - item_key
        - item_kind
        - target_id
        - saved
        - hidden
        - not_interesting
        - updated_at
      properties:
        item_key:
          type: string
          example: announcement:00000000-0000-0000-0000-000000000000
        item_kind:
          type: string
          enum:
            - announcement
            - post
        target_id:
          type: string
          format: uuid
        saved:
          type: boolean
        hidden:
          type: boolean
        not_interesting:
          type: boolean
        updated_at:
          type: string
          format: date-time
    UpdateFeedPreferenceRequest:
      type: object
      additionalProperties: false
      required:
        - item_key
      properties:
        item_key:
          type: string
          example: post:00000000-0000-0000-0000-000000000000
        saved:
          type: boolean
        hidden:
          type: boolean
        not_interesting:
          type: boolean
    Event:
      type: object
      additionalProperties: false
      required:
        - id
        - organizer_id
        - title
        - description
        - category
        - image_url
        - starts_at
        - ends_at
        - visibility
        - status
        - date_mode
        - selected_date_option_id
        - pricing_mode
        - price_amount_minor
        - price_currency
        - published_at
        - canceled_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        organizer_id:
          type: string
          format: uuid
        title:
          type: string
          maxLength: 140
        description:
          type:
            - string
            - "null"
          maxLength: 4000
        category:
          type:
            - string
            - "null"
          maxLength: 80
        image_url:
          description: HTTP(S) image URL or uploaded image data URL.
          type:
            - string
            - "null"
          format: uri
          maxLength: 1500000
        starts_at:
          type:
            - string
            - "null"
          format: date-time
        ends_at:
          type:
            - string
            - "null"
          format: date-time
        visibility:
          $ref: "#/components/schemas/EventVisibility"
        status:
          $ref: "#/components/schemas/EventStatus"
        date_mode:
          $ref: "#/components/schemas/EventDateMode"
        selected_date_option_id:
          type:
            - string
            - "null"
          format: uuid
        pricing_mode:
          $ref: "#/components/schemas/EventPricingMode"
        price_amount_minor:
          type:
            - integer
            - "null"
          minimum: 1
        price_currency:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 3
        published_at:
          type:
            - string
            - "null"
          format: date-time
        canceled_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    EventLocationInput:
      type: object
      additionalProperties: false
      properties:
        title:
          type:
            - string
            - "null"
          maxLength: 200
        address:
          type:
            - string
            - "null"
          maxLength: 200
        city:
          type:
            - string
            - "null"
          maxLength: 200
        latitude:
          type:
            - number
            - "null"
          minimum: -90
          maximum: 90
        longitude:
          type:
            - number
            - "null"
          minimum: -180
          maximum: 180
    EventLocation:
      type: object
      additionalProperties: false
      required:
        - title
        - address
        - city
        - latitude
        - longitude
      properties:
        title:
          type:
            - string
            - "null"
          maxLength: 200
        address:
          type:
            - string
            - "null"
          maxLength: 200
        city:
          type:
            - string
            - "null"
          maxLength: 200
        latitude:
          type:
            - number
            - "null"
        longitude:
          type:
            - number
            - "null"
    EventVisibility:
      type: string
      enum:
        - public
        - private
    EventStatus:
      type: string
      enum:
        - draft
        - published
        - canceled
    EventDateMode:
      type: string
      enum:
        - fixed
        - vote
    EventPricingMode:
      type: string
      enum:
        - free
        - paid
    EventParticipant:
      type: object
      additionalProperties: false
      required:
        - event_id
        - user_id
        - role
        - status
        - participation_visibility
        - created_at
        - updated_at
      properties:
        event_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        role:
          type: string
          enum:
            - organizer
            - participant
        status:
          type: string
          enum:
            - going
            - left
            - waitlisted
        participation_visibility:
          type: string
          enum:
            - public
            - incognito
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    JoinEventRequest:
      type: object
      additionalProperties: false
      properties:
        participation_visibility:
          type: string
          default: public
          enum:
            - public
            - incognito
    EventPostThreadsResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/EventPostThread"
    EventPostThreadsBatchResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/EventPostThreadsBatchItem"
    EventPostThreadsBatchItem:
      type: object
      additionalProperties: false
      required:
        - event_id
        - threads
      properties:
        event_id:
          type: string
          format: uuid
        threads:
          type: array
          items:
            $ref: "#/components/schemas/EventPostThread"
    EventPostThread:
      type: object
      additionalProperties: false
      required:
        - post
        - comments
      properties:
        post:
          $ref: "#/components/schemas/EventPost"
        comments:
          type: array
          items:
            $ref: "#/components/schemas/EventPostComment"
        reasons:
          type: array
          items:
            $ref: "#/components/schemas/FeedReason"
          description: Optional explanation chips for why this thread appears or ranks in the unified feed.
    EventPost:
      type: object
      additionalProperties: false
      required:
        - id
        - event_id
        - author_user_id
        - author
        - kind
        - title
        - body
        - image_url
        - like_count
        - viewer_liked
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        author_user_id:
          type: string
          format: uuid
        author:
          $ref: "#/components/schemas/EventPostAuthor"
        kind:
          type: string
          enum:
            - announcement
            - post
            - important
        title:
          type: string
          maxLength: 140
        body:
          type: string
          maxLength: 4000
        image_url:
          type:
            - string
            - "null"
          format: uri
          maxLength: 2048
        like_count:
          type: integer
          minimum: 0
        viewer_liked:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    EventPostComment:
      type: object
      additionalProperties: false
      required:
        - id
        - event_id
        - post_id
        - author_user_id
        - author
        - parent_comment_id
        - body
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        post_id:
          type: string
          format: uuid
        author_user_id:
          type: string
          format: uuid
        author:
          $ref: "#/components/schemas/EventPostAuthor"
        parent_comment_id:
          type:
            - string
            - "null"
          format: uuid
        body:
          type: string
          maxLength: 2000
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    EventPostAuthor:
      type: object
      additionalProperties: false
      required:
        - user_id
        - display_name
      properties:
        user_id:
          type: string
          format: uuid
        display_name:
          type:
            - string
            - "null"
          maxLength: 80
    EventPostLikeState:
      type: object
      additionalProperties: false
      required:
        - post_id
        - like_count
        - viewer_liked
      properties:
        post_id:
          type: string
          format: uuid
        like_count:
          type: integer
          minimum: 0
        viewer_liked:
          type: boolean
    CreateEventPostRequest:
      type: object
      additionalProperties: false
      required:
        - title
        - body
      properties:
        kind:
          type: string
          default: post
          enum:
            - announcement
            - post
            - important
        title:
          type: string
          maxLength: 140
        body:
          type: string
          maxLength: 4000
        image_url:
          type:
            - string
            - "null"
          format: uri
          maxLength: 2048
    CreateEventPostCommentRequest:
      type: object
      additionalProperties: false
      required:
        - body
      properties:
        parent_comment_id:
          type:
            - string
            - "null"
          format: uuid
        body:
          type: string
          maxLength: 2000
    UpsertEventReviewRequest:
      type: object
      additionalProperties: false
      required:
        - rating
      properties:
        rating:
          type: integer
          minimum: 1
          maximum: 5
        body:
          type:
            - string
            - "null"
          maxLength: 2000
    EventReviewsResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/EventReview"
    EventReview:
      type: object
      additionalProperties: false
      required:
        - id
        - event_id
        - reviewer_user_id
        - rating
        - body
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        reviewer_user_id:
          type: string
          format: uuid
        rating:
          type: integer
          minimum: 1
          maximum: 5
        body:
          type:
            - string
            - "null"
          maxLength: 2000
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CreateReportRequest:
      type: object
      additionalProperties: false
      required:
        - target_type
        - target_id
        - reason
      properties:
        target_type:
          $ref: "#/components/schemas/ReportTargetType"
        target_id:
          type: string
          format: uuid
        reason:
          $ref: "#/components/schemas/ReportReason"
        details:
          type:
            - string
            - "null"
          maxLength: 2000
    Report:
      type: object
      additionalProperties: false
      required:
        - id
        - reporter_user_id
        - target_type
        - target_id
        - reason
        - details
        - status
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        reporter_user_id:
          type: string
          format: uuid
        target_type:
          $ref: "#/components/schemas/ReportTargetType"
        target_id:
          type: string
          format: uuid
        reason:
          $ref: "#/components/schemas/ReportReason"
        details:
          type:
            - string
            - "null"
          maxLength: 2000
        status:
          $ref: "#/components/schemas/ReportStatus"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ReportsResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Report"
    ReportTargetType:
      type: string
      enum:
        - event
        - user
        - review
        - event_post
        - event_post_comment
    ReportReason:
      type: string
      enum:
        - spam
        - harassment
        - inappropriate
        - safety
        - other
    ReportStatus:
      type: string
      enum:
        - open
        - reviewing
        - resolved
        - dismissed
    UpdateReportStatusRequest:
      type: object
      additionalProperties: false
      required:
        - status
        - action_type
      properties:
        status:
          $ref: "#/components/schemas/ReportStatus"
        action_type:
          $ref: "#/components/schemas/ModerationActionType"
        note:
          type:
            - string
            - "null"
          maxLength: 2000
    UpdateReportStatusResponse:
      type: object
      additionalProperties: false
      required:
        - report
        - action
      properties:
        report:
          $ref: "#/components/schemas/Report"
        action:
          $ref: "#/components/schemas/ModerationAction"
    ModerationAction:
      type: object
      additionalProperties: false
      required:
        - id
        - report_id
        - moderator_user_id
        - action_type
        - note
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        report_id:
          type: string
          format: uuid
        moderator_user_id:
          type: string
          format: uuid
        action_type:
          $ref: "#/components/schemas/ModerationActionType"
        note:
          type:
            - string
            - "null"
          maxLength: 2000
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ModerationActionType:
      type: string
      enum:
        - none
        - warning
        - content_hidden
        - user_restricted
    UserBlock:
      type: object
      additionalProperties: false
      required:
        - blocker_user_id
        - blocked_user_id
        - created_at
        - updated_at
      properties:
        blocker_user_id:
          type: string
          format: uuid
        blocked_user_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    UserBlockStatus:
      type: object
      additionalProperties: false
      required:
        - is_blocked
      properties:
        is_blocked:
          type: boolean
    CreateEventInvitationRequest:
      type: object
      additionalProperties: false
      properties:
        invited_phone:
          type:
            - string
            - "null"
        email:
          type:
            - string
            - "null"
          format: email
    RSVPEventInvitationRequest:
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - accepted
            - declined
            - tentative
    SetEventDateModeRequest:
      type: object
      additionalProperties: false
      required:
        - mode
      properties:
        mode:
          type: string
          enum:
            - fixed
            - vote
    CreateEventDateOptionRequest:
      type: object
      additionalProperties: false
      required:
        - starts_at
      properties:
        starts_at:
          type: string
          format: date-time
        ends_at:
          type:
            - string
            - "null"
          format: date-time
        note:
          type:
            - string
            - "null"
          maxLength: 500
    EventDateOptionsResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/EventDateOption"
    EventDateOption:
      type: object
      additionalProperties: false
      required:
        - id
        - event_id
        - proposed_by_user_id
        - starts_at
        - ends_at
        - note
        - selected_at
        - vote_count
        - current_user_voted
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        proposed_by_user_id:
          type: string
          format: uuid
        starts_at:
          type: string
          format: date-time
        ends_at:
          type:
            - string
            - "null"
          format: date-time
        note:
          type:
            - string
            - "null"
          maxLength: 500
        selected_at:
          type:
            - string
            - "null"
          format: date-time
        vote_count:
          type: integer
          minimum: 0
        current_user_voted:
          type: boolean
          description: Whether the authenticated user has voted for this option.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    EventDateVote:
      type: object
      additionalProperties: false
      required:
        - option_id
        - event_id
        - user_id
        - created_at
        - updated_at
      properties:
        option_id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    SendEventChatMessageRequest:
      type: object
      additionalProperties: false
      required:
        - body
      properties:
        body:
          type: string
          minLength: 1
          maxLength: 2000
    EventChatMessagesResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/EventChatMessage"
    EventChatMessage:
      type: object
      additionalProperties: false
      required:
        - id
        - event_id
        - author_user_id
        - body
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        author_user_id:
          type: string
          format: uuid
        body:
          type: string
          maxLength: 2000
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    EventChatWebSocketAuthResponse:
      type: object
      additionalProperties: false
      required:
        - status
        - websocket
      properties:
        status:
          type: string
          enum:
            - authorized
        websocket:
          type: string
          enum:
            - not_implemented
    SetEventReminderSettingsRequest:
      type: object
      additionalProperties: false
      required:
        - enabled
        - remind_at
      properties:
        enabled:
          type: boolean
        remind_at:
          type:
            - string
            - "null"
          format: date-time
    EventReminderSettings:
      type: object
      additionalProperties: false
      required:
        - event_id
        - updated_by_user_id
        - enabled
        - remind_at
        - scheduled_at
        - created_at
        - updated_at
      properties:
        event_id:
          type: string
          format: uuid
        updated_by_user_id:
          type: string
          format: uuid
        enabled:
          type: boolean
        remind_at:
          type:
            - string
            - "null"
          format: date-time
        scheduled_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    EventInvitation:
      type: object
      additionalProperties: false
      required:
        - id
        - event_id
        - invited_user_id
        - invited_by_user_id
        - status
        - responded_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        invited_user_id:
          type: string
          format: uuid
        invited_by_user_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - pending
            - accepted
            - declined
            - tentative
        responded_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CalendarEntrySource:
      type: string
      enum:
        - manual
        - event
    CalendarEntryStatus:
      type: string
      enum:
        - active
        - canceled
    CalendarInviteStatus:
      type: string
      enum:
        - pending
        - accepted
        - declined
        - tentative
    CreateCalendarEntryRequest:
      type: object
      additionalProperties: false
      required:
        - title
        - starts_at
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type:
            - string
            - "null"
          maxLength: 2000
        starts_at:
          type: string
          format: date-time
        ends_at:
          type:
            - string
            - "null"
          format: date-time
        timezone:
          type:
            - string
            - "null"
          maxLength: 100
        location_text:
          type:
            - string
            - "null"
          maxLength: 500
    UpdateCalendarEntryRequest:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type:
            - string
            - "null"
          maxLength: 2000
        starts_at:
          type: string
          format: date-time
        ends_at:
          type:
            - string
            - "null"
          format: date-time
        timezone:
          type:
            - string
            - "null"
          maxLength: 100
        location_text:
          type:
            - string
            - "null"
          maxLength: 500
    CalendarEntriesResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/CalendarEntry"
    CalendarEntry:
      type: object
      additionalProperties: false
      required:
        - id
        - owner_user_id
        - source
        - event_id
        - title
        - description
        - starts_at
        - ends_at
        - timezone
        - location_text
        - status
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        owner_user_id:
          type: string
          format: uuid
        source:
          $ref: "#/components/schemas/CalendarEntrySource"
        event_id:
          type:
            - string
            - "null"
          format: uuid
        title:
          type: string
          maxLength: 200
        description:
          type:
            - string
            - "null"
          maxLength: 2000
        starts_at:
          type: string
          format: date-time
        ends_at:
          type:
            - string
            - "null"
          format: date-time
        timezone:
          type:
            - string
            - "null"
          maxLength: 100
        location_text:
          type:
            - string
            - "null"
          maxLength: 500
        status:
          $ref: "#/components/schemas/CalendarEntryStatus"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CreateCalendarInviteRequest:
      type: object
      additionalProperties: false
      properties:
        invited_user_id:
          type: string
          format: uuid
          description: Existing low-level invite target. Prefer invited_phone in the web app.
        invited_phone:
          type: string
          description: Phone number of an existing Vstrechi user.
    RespondCalendarInviteRequest:
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - accepted
            - declined
            - tentative
    CalendarInvite:
      type: object
      additionalProperties: false
      required:
        - id
        - entry_id
        - invited_user_id
        - invited_by_user_id
        - status
        - responded_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        entry_id:
          type: string
          format: uuid
        invited_user_id:
          type: string
          format: uuid
        invited_by_user_id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/CalendarInviteStatus"
        responded_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CalendarInviteProfile:
      type: object
      additionalProperties: false
      required:
        - user_id
        - username
        - display_name
        - avatar_url
      properties:
        user_id:
          type: string
          format: uuid
        username:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 30
          pattern: "^[a-z0-9_]+$"
        display_name:
          type:
            - string
            - "null"
          maxLength: 80
        avatar_url:
          type:
            - string
            - "null"
          format: uri
    CalendarInviteParticipant:
      type: object
      additionalProperties: false
      required:
        - invite
        - profile
      properties:
        invite:
          $ref: "#/components/schemas/CalendarInvite"
        profile:
          anyOf:
            - $ref: "#/components/schemas/CalendarInviteProfile"
            - type: "null"
    CalendarInviteWithEntry:
      type: object
      additionalProperties: false
      required:
        - invite
        - entry
        - inviter_profile
      properties:
        invite:
          $ref: "#/components/schemas/CalendarInvite"
        entry:
          $ref: "#/components/schemas/CalendarEntry"
        inviter_profile:
          anyOf:
            - $ref: "#/components/schemas/CalendarInviteProfile"
            - type: "null"
    CalendarInviteDetails:
      type: object
      additionalProperties: false
      required:
        - id
        - entry_id
        - invited_user_id
        - invited_by_user_id
        - status
        - responded_at
        - created_at
        - updated_at
        - entry
        - inviter_profile
        - participants
      properties:
        id:
          type: string
          format: uuid
        entry_id:
          type: string
          format: uuid
        invited_user_id:
          type: string
          format: uuid
        invited_by_user_id:
          type: string
          format: uuid
        status:
          $ref: "#/components/schemas/CalendarInviteStatus"
        responded_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        entry:
          $ref: "#/components/schemas/CalendarEntry"
        inviter_profile:
          anyOf:
            - $ref: "#/components/schemas/CalendarInviteProfile"
            - type: "null"
        participants:
          type: array
          items:
            $ref: "#/components/schemas/CalendarInviteParticipant"
    CalendarInvitesResponse:
      type: object
      additionalProperties: false
      required:
        - items
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/CalendarInviteWithEntry"
    SetCalendarReminderSettingsRequest:
      type: object
      additionalProperties: false
      required:
        - enabled
        - remind_at
      properties:
        enabled:
          type: boolean
        remind_at:
          type:
            - string
            - "null"
          format: date-time
        offset_minutes:
          type: array
          maxItems: 5
          description: Preset reminder offsets in minutes before entry start, e.g. 4320, 1440 and 120.
          items:
            type: integer
            minimum: 1
    CalendarReminderSettings:
      type: object
      additionalProperties: false
      required:
        - entry_id
        - updated_by_user_id
        - enabled
        - remind_at
        - offset_minutes
        - scheduled_at
        - created_at
        - updated_at
      properties:
        entry_id:
          type: string
          format: uuid
        updated_by_user_id:
          type: string
          format: uuid
        enabled:
          type: boolean
        remind_at:
          type:
            - string
            - "null"
          format: date-time
        offset_minutes:
          type: array
          items:
            type: integer
            minimum: 1
        scheduled_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    NotificationKind:
      type: string
      enum:
        - social_follow
        - calendar_invite
        - calendar_upcoming
        - event_upcoming
        - event_update
        - event_post_comment
        - event_post_like
        - event_post_mention
        - event_post_important
    Notification:
      type: object
      additionalProperties: false
      required:
        - id
        - recipient_user_id
        - actor_user_id
        - kind
        - title
        - body
        - href
        - meta
        - read_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        recipient_user_id:
          type: string
          format: uuid
        actor_user_id:
          type:
            - string
            - "null"
          format: uuid
        kind:
          $ref: "#/components/schemas/NotificationKind"
        title:
          type: string
          maxLength: 160
        body:
          type: string
          maxLength: 500
        href:
          type: string
          maxLength: 500
        meta:
          type:
            - string
            - "null"
          maxLength: 160
        read_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    NotificationsResponse:
      type: object
      additionalProperties: false
      required:
        - items
        - unread_count
        - next_cursor
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Notification"
        unread_count:
          type: integer
          minimum: 0
        next_cursor:
          type:
            - string
            - "null"
    NotificationUnreadCountResponse:
      type: object
      additionalProperties: false
      required:
        - unread_count
      properties:
        unread_count:
          type: integer
          minimum: 0
    MarkAllNotificationsReadResponse:
      type: object
      additionalProperties: false
      required:
        - updated_count
      properties:
        updated_count:
          type: integer
          minimum: 0
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: string
