openapi: 3.1.0
info:
  title: Member Center API
  version: 0.1.0
  description: OAuth2/OIDC + Newsletter Subscription API
servers:
  - url: /api
security:
  - BearerAuth: []

paths:
  /oauth/authorize:
    get:
      summary: OAuth2 Authorization Endpoint
      description: Authorization Code + PKCE flow
      security: []
      parameters:
        - in: query
          name: client_id
          required: true
          schema: { type: string }
        - in: query
          name: redirect_uri
          required: true
          schema: { type: string }
        - in: query
          name: response_type
          required: true
          schema: { type: string, enum: [code] }
        - in: query
          name: scope
          required: true
          schema: { type: string }
        - in: query
          name: code_challenge
          required: true
          schema: { type: string }
        - in: query
          name: code_challenge_method
          required: true
          schema: { type: string, enum: [S256] }
        - in: query
          name: state
          required: false
          schema: { type: string }
      responses:
        '302':
          description: Redirect to client with code

  /oauth/token:
    post:
      summary: OAuth2 Token Endpoint
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: '#/components/schemas/AuthorizationCodeTokenRequest'
                - $ref: '#/components/schemas/RefreshTokenRequest'
      responses:
        '200':
          description: Token response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'

  /.well-known/openid-configuration:
    get:
      summary: OIDC Discovery
      responses:
        '200':
          description: OIDC discovery document

  /.well-known/jwks.json:
    get:
      summary: JWKS
      responses:
        '200':
          description: JSON Web Key Set

  /auth/register:
    post:
      summary: Register user
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
      responses:
        '200':
          description: Registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'

  /auth/login:
    post:
      summary: API login
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/PasswordTokenRequest'
      responses:
        '200':
          description: Token response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'

  /auth/refresh:
    post:
      summary: Refresh token
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/RefreshTokenRequest'
      responses:
        '200':
          description: Token response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'

  /auth/logout:
    post:
      summary: Logout (revoke refresh token)
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                refresh_token:
                  type: string
      responses:
        '204':
          description: Logged out

  /auth/password/forgot:
    post:
      summary: Request password reset
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ForgotPasswordRequest'
      responses:
        '204':
          description: Email sent

  /auth/password/reset:
    post:
      summary: Reset password
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordRequest'
      responses:
        '204':
          description: Password reset

  /auth/email/verify:
    get:
      summary: Verify email
      security: []
      parameters:
        - in: query
          name: token
          required: true
          schema: { type: string }
        - in: query
          name: email
          required: true
          schema: { type: string, format: email }
      responses:
        '200':
          description: Email verified

  /user/profile:
    get:
      summary: Get current user profile
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'

  /newsletter/subscribe:
    post:
      summary: Subscribe (unauthenticated allowed)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubscribeRequest'
      responses:
        '200':
          description: Pending subscription
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PendingSubscriptionResponse'

  /newsletter/confirm:
    get:
      summary: Confirm subscription (double opt-in)
      security: []
      parameters:
        - in: query
          name: token
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Active subscription
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'

  /newsletter/unsubscribe:
    post:
      summary: Unsubscribe single list
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token: { type: string }
      responses:
        '200':
          description: Unsubscribed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'

  /newsletter/unsubscribe-token:
    post:
      summary: Issue unsubscribe token
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [list_id, email]
              properties:
                list_id: { type: string }
                email: { type: string, format: email }
      responses:
        '200':
          description: Unsubscribe token issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  unsubscribe_token: { type: string }

  /newsletter/preferences:
    get:
      summary: Get preferences
      parameters:
        - in: query
          name: list_id
          required: true
          schema: { type: string }
        - in: query
          name: email
          required: true
          schema: { type: string, format: email }
      responses:
        '200':
          description: Preferences
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'

    post:
      summary: Update preferences
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [list_id, email, preferences]
              properties:
                list_id: { type: string }
                email: { type: string, format: email }
                preferences: { type: object }
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'

  /admin/tenants:
    get:
      summary: List tenants
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: List
    post:
      summary: Create tenant
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Tenant'
      responses:
        '201':
          description: Created

  /admin/tenants/{id}:
    get:
      summary: Get tenant
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Tenant
    put:
      summary: Update tenant
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Tenant'
      responses:
        '200':
          description: Updated
    delete:
      summary: Delete tenant
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '204':
          description: Deleted

  /admin/newsletter-lists:
    get:
      summary: List newsletter lists
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: List
    post:
      summary: Create list
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterList'
      responses:
        '201':
          description: Created

  /admin/newsletter-lists/{id}:
    get:
      summary: Get list
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: List
    put:
      summary: Update list
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterList'
      responses:
        '200':
          description: Updated
    delete:
      summary: Delete list
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '204':
          description: Deleted

  /admin/oauth-clients:
    get:
      summary: List OAuth clients
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: List
    post:
      summary: Create OAuth client
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OAuthClient'
      responses:
        '201':
          description: Created

  /admin/oauth-clients/{id}:
    get:
      summary: Get OAuth client
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: OAuth client
    put:
      summary: Update OAuth client
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OAuthClient'
      responses:
        '200':
          description: Updated
    delete:
      summary: Delete OAuth client
      security: [{ BearerAuth: [] }]
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '204':
          description: Deleted

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: /api/oauth/authorize
          tokenUrl: /api/oauth/token
          scopes:
            openid: OpenID Connect
            email: Email
            profile: Basic profile
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    TokenResponse:
      type: object
      properties:
        access_token: { type: string }
        refresh_token: { type: string }
        id_token: { type: string }
        token_type: { type: string, example: Bearer }
        expires_in: { type: integer }

    RegisterRequest:
      type: object
      required: [email, password]
      properties:
        email: { type: string, format: email }
        password: { type: string }

    PasswordTokenRequest:
      type: object
      required: [grant_type, username, password]
      properties:
        grant_type: { type: string, enum: [password] }
        username: { type: string, format: email }
        password: { type: string }
        scope: { type: string }
        client_id: { type: string }

    AuthorizationCodeTokenRequest:
      type: object
      required: [grant_type, code, redirect_uri, code_verifier]
      properties:
        grant_type: { type: string, enum: [authorization_code] }
        code: { type: string }
        redirect_uri: { type: string }
        code_verifier: { type: string }
        client_id: { type: string }

    RefreshRequest:
      type: object
      required: [refresh_token, client_id]
      properties:
        refresh_token: { type: string }
        client_id: { type: string }

    RefreshTokenRequest:
      type: object
      required: [grant_type, refresh_token]
      properties:
        grant_type: { type: string, enum: [refresh_token] }
        refresh_token: { type: string }
        client_id: { type: string }

    ForgotPasswordRequest:
      type: object
      required: [email]
      properties:
        email: { type: string, format: email }

    ResetPasswordRequest:
      type: object
      required: [email, token, new_password]
      properties:
        email: { type: string, format: email }
        token: { type: string }
        new_password: { type: string }

    UserProfile:
      type: object
      properties:
        id: { type: string }
        email: { type: string, format: email }
        email_verified: { type: boolean }
        created_at: { type: string, format: date-time }

    SubscribeRequest:
      type: object
      required: [list_id, email]
      properties:
        list_id: { type: string }
        email: { type: string, format: email }
        preferences: { type: object }
        source: { type: string }

    Subscription:
      type: object
      properties:
        id: { type: string }
        list_id: { type: string }
        email: { type: string, format: email }
        status: { type: string, enum: [pending, active, unsubscribed] }
        preferences: { type: object }
        created_at: { type: string, format: date-time }

    PendingSubscriptionResponse:
      allOf:
        - $ref: '#/components/schemas/Subscription'
        - type: object
          properties:
            confirm_token: { type: string }

    Tenant:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        domains: { type: array, items: { type: string } }
        status: { type: string }

    NewsletterList:
      type: object
      properties:
        id: { type: string }
        tenant_id: { type: string }
        name: { type: string }
        status: { type: string }

    OAuthClient:
      type: object
      properties:
        id: { type: string }
        tenant_id: { type: string }
        name: { type: string }
        redirect_uris: { type: array, items: { type: string } }
        client_type: { type: string, enum: [public, confidential] }
